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
Check If Variable Is an Optional, and What Type It Wraps
Two Tables on One View in Swift
Alamofire: Send JSON with Array of Dictionaries
Why Can't You Assign an Optional to a Variable of Type 'Any' Without a Warning
Why Unsaferawpointer Shows Different Result When Function Signatures Differs in Swift
Count Elements of Array Matching Condition in Swift
Parameters After Opening Bracket
How to Integrate Uiactivityviewcontroller with Swiftui's Scrollview
Block Scroll Down in Scrollview - Swiftui
What Are 'Get' and 'Set' in Swift
Swiftui: How to Present View When Clicking on a Button
Get the Last Character of a String Without Using Array
Trouble Left-Aligning Uibutton Title (Ios/Swift)
Pointers, Pointer Arithmetic, and Raw Data in Swift
How Does Dictionary Use the Equatable Protocol in Swift
Suddenly Getting Compiler Crash "Arrayforcecast" in Swift Xcode Beta 6
Diagnosing Exc_Bad_Instruction in Swift Standard Library
How to Install Xcode on an External Hard Drive Along with the iPhone Simulator.App