【问题标题】:How to write a generic apply() function in Swift?如何在 Swift 中编写一个通用的 apply() 函数?
【发布时间】:2017-05-07 11:27:20
【问题描述】:

有什么方法可以在 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
     }

apply<T> 函数应该采用(T)->Void 类型的闭包,运行它并传递self,然后简单地返回self

另一种选择是为此使用运算符,例如“=>” (借鉴KotlinXtend 语言的想法)。

尝试像这样扩展NSObject

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

但它需要在闭包中显式声明参数类型:

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

这不方便,并且使整个想法不值得付出努力。类型在对象创建时已经指定,应该可以不显式重复。

谢谢!

【问题讨论】:

  • 我自己仍然对 Swift 中的泛型感到满意。但是,您无法对任何NSObject 执行您想要执行的操作。 addTarget(_:for:) 方法在 UIControl 中定义,setImage(_:for:) 特定于 UIButton
  • 你说得对,这正是我想将具体类型(可能作为泛型类型参数)放入 apply() 函数声明的原因。那么静态类型检查将不允许我调用错误的方法。
  • github.com/iwheelbuy/Decorator 如果它对你来说足够好 - 我可以发布答案
  • @AlexJenter 如果你有一个UIButton() 并且想用应用配置它,那么不需要任何样式。只需致电button.decorator.apply
  • @iWheelBuy:我明白了。谢谢。

标签: ios swift functional-programming


【解决方案1】:

HasApply 协议

首先让我们定义HasApply 协议

protocol HasApply { }

及相关扩展

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

接下来让NSObject符合HasApply

extension NSObject: HasApply { }

就是这样

让我们测试一下

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

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

注意事项

我不会使用NSObject(它是Objective-C 做事方式的一部分,我认为它会在将来的某个时候被删除)。我更喜欢UIView 之类的东西。

extension UIView: HasApply { }

【讨论】:

  • 那是天才。有用。谢谢!我会接受这个答案,因为它最直接地回答了所提出的问题。不过 Alain T. 和 iWheelBuy 的回答也很棒。
  • 很好的简单使用闭包。 (投票。)我想多了。干得好
【解决方案2】:

我遇到了同样的问题,最后用操作员解决了:

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

let myObject = UIButton() <-< { $0.isHidden = false }

【讨论】:

  • 哇,太好了,谢谢!这有一个额外的好处,它不需要类型是 NSObject 或 UIView 的后代。
【解决方案3】:

有一个名为Thenvery good and simple Cocoapods library 可以做到这一点。只是它使用then 而不是apply。只需导入 Then然后,您就可以按照 OP 的要求进行操作:

import Then

myObject.then {
    $0.objectMethod()
}

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

以下是协议的实现方式: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
    }

【讨论】:

  • 非常好的图书馆!还添加了方便的“with”和“do”方法。非常感谢
  • 可能值得注意:这并没有真正解决扩展Any 的限制。他们只是手动扩展一大堆不同的类型以符合Then
【解决方案4】:

如果您对自定义运算符不过敏,Alain 会给出一个很好的答案。如果您不想使用这些,我能想到的最佳选择是:

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

然后允许您使用:

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

虽然不太一样,但总体来说效果很好,并且具有 T 完全不受约束的优点。这是一个明显人为的例子,但是:

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

甚至允许:

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

【讨论】:

    【解决方案5】:

    如果对象必须使用非可选的 init 创建:

    let button = UIButton()
    Optional(button).map {
        $0.isEnabled = true
    }
    

    如果对象必须使用可选的 init 创建:

    let button = UIButton(coder: coder)
    button.map {
        $0.isEnabled = true
    }
    

    如果存在可选对象,我们可以使用地图功能:

    optionalObject.map {
        $0.property1 = true
        $0.property2 = true
    }
    

    如果对象必须在使用前强制转换:

    (optionalObject as? NewType).map {
        $0.property1 = true
        $0.property2 = true
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-28
      • 2017-03-25
      • 2019-10-21
      • 2018-08-15
      • 2017-04-17
      相关资源
      最近更新 更多