Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a generic apply() function in Swift?

Is there any way to get the following working in Swift 3?

 let button = UIButton().apply {
        $0.setImage(UIImage(named: "UserLocation"), for: .normal)
        $0.addTarget(self, action: #selector(focusUserLocation), 
                     for: .touchUpInside)
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        $0.layer.cornerRadius = 5
     }

The apply<T> function should take a closure of type (T)->Void, run it passing self into it, and then simply return self.

Another option would be to use an operator for this like "=>" (borrowed the idea from Kotlin and Xtend languages).

Tried to do extension of NSObject like this:

extension NSObject {   
    func apply<T>(_ block: (T)->Void) -> T
    {
        block(self as! T)
        return self as! T
    }
}

But it requires explicit declaration of the parameter type in closure:

let button = UIButton().apply { (it: UIButton) in
        it.setImage(UIImage(named: "UserLocation"), for: .normal)
        it.addTarget(self, action: #selector(focusUserLocation), 
                     for: .touchUpInside)
        ...

This is not convenient and makes the whole idea not worth the effort. The type is already specified at object creation and it should be possible not to repeat it explicitly.

Thanks!

like image 615
Alex Jenter Avatar asked May 07 '17 11:05

Alex Jenter


People also ask

How do you make a generic function in Swift?

Here, the generic function is created with type constraints. This means addition() can only work with data types that conform to Numeric protocol ( Int , Double , and so on). Note: If we try to pass other types, say String , we'll get an error: argument type 'String' does not conform to the expected type 'Numeric' .

How do you declare a generic variable in Swift?

Generics allow you to declare a variable which, on execution, may be assigned to a set of types defined by us. In Swift, an array can hold data of any type. If we need an array of integers, strings, or floats, we can create one with the Swift standard library.

What are generics in Swift and write its usage?

Swift 4 language provides 'Generic' features to write flexible and reusable functions and types. Generics are used to avoid duplication and to provide abstraction. Swift 4 standard libraries are built with generics code. Swift 4s 'Arrays' and 'Dictionary' types belong to generic collections.

What is generic protocol in Swift?

Swift enables us to create generic types, protocols, and functions, that aren't tied to any specific concrete type — but can instead be used with any type that meets a given set of requirements.


3 Answers

The HasApply protocol

First of all lets define the HasApply protocol

protocol HasApply { }

and related extension

extension HasApply {
    func apply(closure:(Self) -> ()) -> Self {
        closure(self)
        return self
    }
}

Next let make NSObject conform to HasApply.

extension NSObject: HasApply { }

That's it

Let's test it

let button = UIButton().apply {
    $0.titleLabel?.text = "Tap me"
}

print(button.titleLabel?.text) // Optional("Tap me")

Considerations

I wouldn't use NSObject (it's part of the Objective-C way of doing things and I assume it will be removed at some point in the future). I would prefer something like UIView instead.

extension UIView: HasApply { }
like image 185
Luca Angeletti Avatar answered Oct 02 '22 20:10

Luca Angeletti


I had the same issue and ended up solving it with an operator:

infix operator <-< : AssignmentPrecedence
func <-<<T:AnyObject>(left:T, right:(T)->()) -> T
{
  right(left)
  return left
}

let myObject = UIButton() <-< { $0.isHidden = false }
like image 41
Alain T. Avatar answered Oct 02 '22 18:10

Alain T.


There's a very good and simple Cocoapods library available called Then that does exactly that. Only that it uses then instead of apply. Simply import Then and then you can do as the OP asked for:

import Then

myObject.then {
    $0.objectMethod()
}

let label = UILabel().then {
    $0.color = ...
}

Here's how the protocol is implemented: https://github.com/devxoul/Then/blob/master/Sources/Then/Then.swift

extension Then where Self: Any {
    public func then(_ block: (Self) throws -> Void) rethrows -> Self {
        try block(self)
        return self
    }
like image 7
Lars Blumberg Avatar answered Oct 02 '22 20:10

Lars Blumberg