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

久未见 提交于 2020-04-08 09:00:25

问题


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!


回答1:


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 { }



回答2:


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 }



回答3:


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
    }



回答4:


Alain has a good answer if you're not allergic to custom operators. If you'd rather not use those, the best alternative I could come up with was:

@discardableResult func apply<T>(_ it:T, f:(T)->()) -> T {
    f(it)
    return it
}

which then allows you to use:

let button = apply(UIButton()) { $0.setTitleText("Button") }

It's not quite the same, but works out pretty well in general and has the advantage that T is completely unrestrained. It's an obviously contrived example, but:

func apply<T,R>(_ it:T, f:(T)->R) -> R {
    return f(it)
}

even allows:

print("\(apply(32) { $0 + 4 })")



回答5:


This is example Generic With Protocol And Extension. I wish you to help you

protocol Container {
associatedtype ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }}

class Stack<S>: Container {
// original Stack<T> implementation
var items = [S]()
 func push(item: S) {
    items.append(item)}

 func pop() -> S {
    return items.removeLast()
}

// conformance to the Container protocol
 func append(item: S) {
    self.push(item: item)
}

var count: Int {
    return items.count
}

subscript(i: Int) -> S {
    return items[i]
}}

extension Stack {
var topItem: S? {
    return items.isEmpty ? nil : items[items.count - 1]
}}


var stringStack = Stack<String>()
var intStack    = Stack<Int>()


来源:https://stackoverflow.com/questions/43830927/how-to-write-a-generic-apply-function-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!