is it possible to create a generic closure in Swift?
No, because variables and expressions can't be generic. There are only generic functions and generic types.
To clarify: In some languages you can have types with a universal quantifier, like forall a. a -> a
. But in Swift, types cannot have a universal quantifier. So expressions and values cannot be themselves generic. Function declarations and type declarations can be generic, but when you use such a generic function or an instance of such a generic type, some type (which could be a real type or a type variable) is chosen as the type argument, and thereafter the value you get is no longer itself generic.
How to create generic closures in Swift
You cannot pass a closure of type String->Void
to a parameter of type AnyObject->Void
.
However, you can define a generic function:
func saveWithCompletionObject<T>(obj : T, success : T -> Void, failure : Void -> Void) {
// ...
success(obj)
}
Now the compiler can verify that obj
has the same type
as the parameter of success
, for example:
func doSomething(success : String -> Void, failure : Void -> Void)
{
saveWithCompletionObject("Example", success, failure)
}
func doSomethingElse(success : Int -> Void, failure : Void -> Void)
{
saveWithCompletionObject(13, success, failure)
}
But I would recommend that saveWithCompletionObject
just takes a Void->Void
parameter (without generics):
func saveWithCompletionObject(success : Void -> Void, failure : Void -> Void) {
// ...
success()
}
and the caller wraps its closure:
func doSomething(success : String -> Void, failure : Void -> Void)
{
saveWithCompletionObject( { success("Example") } , failure)
}
func doSomethingElse(success : Int -> Void, failure : Void -> Void)
{
saveWithCompletionObject( { success(13) }, failure)
}
This is more flexible, e.g. for callback functions with more than one
parameter:
func andNowForSomethingCompletelyDifferent(success : (Int, Double) -> Void, failure : Void -> Void)
{
saveWithCompletionObject( { success(13, M_PI) }, failure)
}
iOS Generic swift closure
You can create your method something like:
func method1<Model: Codable>(onCompletion: @escaping (Model?, Error?) -> Void) -> Void {
let decoder = JSONDecoder()
let itemObj = try? decoder.decode(Model.self, from: data)
onCompletion(itemObj, nil)
}
And if you have your struct like:
struct ModelObject: Codable {
}
you can call this method like:
method1 { (model: ModelObject?, error) in
}
You have another struct like:
struct AnotherModelObject: Codable {
}
You can call it like:
method1 { (model: AnotherModelObject?, error) in
}
Swift generic closure
You can make the Cell
class generic, e.g.
class Cell<T : UITableViewCell> {
}
and then use T
instead of every UITableViewCell
.
Unfortunately you would have to have the same in both Section
and Form
classes, too. That would work for tables with one type of cells but it won't probably work for tables with multiple cell types. In that case you will always need casting somewhere.
Closure with generic parameters
I believe what you're asking for can't make sense (having nothing to do with Swift). While I'm interested in being proven wrong, I don't believe this could be reasonably created in any strongly typed language. (EDIT: continuing my research, I believe this would be possible in a language with first-class polymorphism, but I am not aware of any general-use languages that actually have this feature.)
let myClosure = {<S where S: MyProtocol, S: MySuperClass>(param: S) in ... }
What type would you expect myClosure
to be? A generic creates an abstract type. It does not become a real type until it is specialized. So myClosure
would be of an abstract type itself. That's like asking for an instance of an abstract class. The whole point of "abstract" is you can't construct one. The best you could say would be that myClosure
would itself be a type that you would need to instantiate into a real instance (but then let
doesn't make any sense; you don't let
types).
When you wrap this in a struct
, what you're really doing is creating an abstract type that you will specialize into a real type when you create an instance.
Now what would make sense IMO (but appears currently to be impossible), is this:
typealias Mapping<S> = S -> S
let identity: Mapping<Int> = { return $0 }
That makes sense because you're defining an abstract type (Mapping
), but then instantiating a concrete type Mapping<Int>
. Unfortunately, typealias
does not appear to support generics at this point, so a struct
is probably the best tool we have.
Note that while typealias
is a bust, it is clearly possible to specialize function variables themselves. This isn't a closure, I know, but may be useful in some of the same situations.
func Identity<T>(i:T) -> T {
return i
}
let identityInt:(Int -> Int) = Identity
identityInt(1) // => 1
Using this to explore the problem of abstract types a little more, consider:
func Identity<T>(i:T) -> T { return i }
let x = Identity
This fails to compile with the error:
error: cannot convert the expression's type '(T) -> T' to type '(T) -> T'
That's because the type (T) -> T
is not a concrete type, so you can't have one called x
. Compare that to identityInt
, which I explicitly specialized into a concrete type, and then could construct.
Swift generic closures sum
You are running into the issue of Swift generic coercion misunderstanding. Generic types in Swift are invariant, which means that Styler<A>
and Styler<B>
are completely unrelated types even if A
and B
are related (subclasses for instance).
This is why Style<UILabel>
and Styler<UIView>
are unrelated. However, closures (and hence functions) are variant (as explained here) - covariant on the return type, and contravariant on the parameter types, this is why your first example works.
Because of this, you can pass a UILabel
to a Styler<UIView>.apply
, since that is a simple function call, which accepts subclasses of the declared input argument type.
let styler1: Styler<UIView> = Styler { (label: UIView) -> Void in
label.backgroundColor = UIColor.red
}
let styler2: Styler<UIView> = Styler { (view: UIView) -> Void in
view.backgroundColor = UIColor.green
}
let styler3 = styler1 + styler2
styler1.apply(UILabel())
Related Topics
Problem with Swiftui and Foreach on Xcode Playground
How to Reconstruct Grayscale Image from Intensity Values
Uitextfield with a Placeholder Containing an Image and Text
Tintcolor Not Changing for UIbarbuttonitem for .Normal Stage in Case of iOS 13.2
Expressions Are Not Allowed at The Top Level
Loading Image from Remote Url Asynchronously in Swiftui Image Using Combine's Publisher
How to Provide Default Implementation of an Objective-C Protocol in a Swift Protocol Extension
How to Detect When Two Objects Touch in Spritekit
Why Are Implicitly Unwrapped Variables Now Printing Out as Some(...) in Swift 4.1
Why Swift Disallows Weak Reference for Non-Optional Type
Optional Protocol Requirements, I Can't Get It to Work
Using Nsdate to Get Date for Easter
Cannot Authenticate User for Aws Appsync with Swift Sdk