Storing/Passing Function Types from Swift Protocols

Usage of protocols as array types and function parameters in swift

As of Swift 5.7 / Xcode 14 this can now elegantly be solved using any.

protocol SomeProtocol: Equatable {
func bla()
}

class SomeClass {
var protocols = [any SomeProtocol]()

func addElement(element: any SomeProtocol) {
protocols.append(element)
}

func removeElement(element: any SomeProtocol) {
if let index = find(protocols, element) {
protocols.remove(at: index)
}
}
}

How to pass generic protocol as function parameter

You must use Datatype as a generic constraint:

func set<T: Datatype>(key: String, type: T ,value: T.dataType)

Note how you also need to change the type of the value parameter to T.dataType, for this to be type safe. That is, the value you pass in must be the same type as the dataType of the type parameter.

How do i work in Swift 5 with protocol function parameters that use protocols with associated types (i.e. .pickerStyle())

pickerStyle is a generic method that accepts a concrete type (at compile-time) that conforms PickerStyle. So, it cannot be either SegmentedPickerStyle or WheelPickerStyle (determined at run-time) - it has to be one or the other.

So, one suggestion would be to create a view modifier and apply the picker style conditionally. The critical difference here is that it returns a conditional view of type _ConditionalContent<TrueContent, FalseContent>.

struct PickerStyleOption<P1: PickerStyle, P2: PickerStyle>: ViewModifier {
let predicate: () -> Bool
let style1: P1
let style2: P2

@ViewBuilder
func body(content: Content) -> some View {
if predicate() {
content
.pickerStyle(style1)
} else {
content
.pickerStyle(style2)
}
}
}

For convenience, you could create an extension:

extension View {
func pickerStyleOption<P1: PickerStyle, P2: PickerStyle>(
_ condition: @autoclosure @escaping () -> Bool,
then style1: P1,
else style2: P2) -> some View {

self.modifier(
PickerStyleOption(predicate: condition, style1: style1, style2: style2)
)
}
}

And use it like so:

Picker(...) {
...
}
.pickerStyleOption((productsObserver.product.productFamilies?.count ?? 0) < 5,
then: SegmentedPickerStyle(), else: WheelPickerStyle())

Swift: How to save a Decodable.Protocol object to a variable?

@Владислав Артемьев, I'm still not sure that I completely understand the problem because you haven't shared the code that takes the Decodable class. But the issues seems to be about how to pass a Decodable class.

I hope the following can help clarify how you can impose the right constraint on the generic and how you should declare the variable. You can paste it into a playground and experiment.

import Foundation

struct FakeToDo: Decodable {
var userId: Int
var id: Int
var title: String
var completed: Bool
}

enum URLMethods {
case GET
case POST
}

func networkRequest<T: Decodable> (
url: String,
timeout: Double = 30,
method: URLMethods = .GET,
data: [String : String]? = nil,
files: [URL]? = nil,
jsonType: T.Type,
success: @escaping (T) -> Void,
failure: @escaping (Error) -> Void
) -> URLSessionDataTask {

let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { (data, response, error) in
if error != nil {
failure(error!)
return
}

guard let data = data else { return }

let decoder = JSONDecoder()
guard let value = try? decoder.decode(T.self, from: data) else { return }

// get back on the main queue for UI
DispatchQueue.main.async {
success(value)
}
})
return task
}

class Example<T> where T: Decodable {
let type: T.Type

init(_ type: T.Type) {
self.type = type
}

public var decodableType: T.Type {
return type
}
}

let decodableType = Example(FakeToDo.self).decodableType

let url = "https://jsonplaceholder.typicode.com/todos/1"
let task = networkRequest(url: url, jsonType: decodableType,
success: { value in print(value) },
failure: { error in print(error) })

task.resume()

How to pass protocol with associated type (generic protocol) as parameter in Swift?

If you use typealias in a protocol to make it generic-like, then you cannot use it as a variable type until the associated type is resolved. As you have probably experienced, using a protocol with associated type to define a variable (or function parameter) results in a compilation error:

Protocol 'MyProtocol' can only be used as a generic constraint because it has Self os associated type requirements

That means you cannot use it as a concrete type.

So the only 2 ways I am aware of to use a protocol with associated type as a concrete type are:

  • indirectly, by creating a class that implements it. Probably not what you have planned to do
  • making explicit the associated type like you did in your func

See also related answer https://stackoverflow.com/a/26271483/148357

Usage of protocols as array types and function parameters in swift

As of Swift 5.7 / Xcode 14 this can now elegantly be solved using any.

protocol SomeProtocol: Equatable {
func bla()
}

class SomeClass {
var protocols = [any SomeProtocol]()

func addElement(element: any SomeProtocol) {
protocols.append(element)
}

func removeElement(element: any SomeProtocol) {
if let index = find(protocols, element) {
protocols.remove(at: index)
}
}
}

Swift extension storage for protocols

Any protocol object can be converted into a type-erased class. Build an AnySomeProtocol and store that.

private var sourceKey: UInt8 = 0

final class AnySomeProtocol: SomeProtocol {
func getData() -> String { return _getData() }
init(_ someProtocol: SomeProtocol) { _getData = someProtocol.getData }
private let _getData: () -> String
}

extension UIViewController: SomeProtocolInjectable {
var source: SomeProtocol! {
get {
return objc_getAssociatedObject(self, &sourceKey) as? SomeProtocol
}
set(newValue) {
objc_setAssociatedObject(self, &sourceKey, AnySomeProtocol(newValue), .OBJC_ASSOCIATION_RETAIN)
}
}
}

class MyViewController : UIViewController {
override func viewDidLoad() {
self.title = source.getData()
}
}

The caller can only use this to access the protocol methods. You can't force it back into its original type with as, but you should avoid that anyway.

As a side note, I'd really recommend making source return SomeProtocol? rather than SomeProtocol!. There's nothing here that promises that source will be set. You don't even set it until viewDidLoad.

Swift: Storing protocol instances

The return value of VectorProtocol is a protocol existential, which is basically a 3 word "box" around a value. Your value is only 3 words long, so it will fit in the box. (If it were larger than 3 words, it would be copied into heap storage, and the box would memory manage it).

These 3-word boxes are passed on the stack, so they don't require any heap memory. The three values are just put directly on the stack (together with a few other words of housekeeping).

If this were a class, then the existential box would just hold a pointer to it (along with a few other words of housekeeping).

This was all explained in the WWDC 2016 session 416, Understanding Swift Performance, but Apple appears to have removed that from developer.apple.com. You can still find some notes about it at WWDC Notes and the transcript is available through the Wayback Machine.



Related Topics



Leave a reply



Submit