如何在Swift中编写泛型apply()函数?

时间:2022-11-02 16:52:51

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

在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.

apply 函数应该采用类型(T)->Void的闭包,将其传递给self,然后简单地返回self。

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

另一种选择是使用一个操作符,例如“=>”(借用了Kotlin和Xtend语言的概念)。

Tried to do extension of NSObject like this:

尝试扩展NSObject:

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!

谢谢!

5 个解决方案

#1


8  

The HasApply protocol

First of all lets define the HasApply protocol

首先让我们定义HasApply协议

protocol HasApply { }

and related extension

和相关扩展

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

Next let make NSObject conform to HasApply.

接下来让NSObject符合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.

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

extension UIView: HasApply { }

#2


4  

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


2  

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:

有一个非常好的简单的可可粉库,叫做Then,它就是这么做的。只是它使用而不是应用。简单导入,然后你可以按照OP要求做:

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.swift

协议的实现方式如下:https://github.com/devxoul/Then/blob/master/Sources/Then.swift

extension Then where Self: AnyObject {
    public func then(_ block: (Self) -> Void) -> Self {
        block(self)
        return self
    }
}

#4


0  

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:

它不是完全相同的,但总的来说效果很好,它的优点是T是完全不受限制的。这显然是人为的例子,但是:

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

even allows:

甚至允许:

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

#5


-1  

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>()

#1


8  

The HasApply protocol

First of all lets define the HasApply protocol

首先让我们定义HasApply协议

protocol HasApply { }

and related extension

和相关扩展

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

Next let make NSObject conform to HasApply.

接下来让NSObject符合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.

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

extension UIView: HasApply { }

#2


4  

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


2  

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:

有一个非常好的简单的可可粉库,叫做Then,它就是这么做的。只是它使用而不是应用。简单导入,然后你可以按照OP要求做:

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.swift

协议的实现方式如下:https://github.com/devxoul/Then/blob/master/Sources/Then.swift

extension Then where Self: AnyObject {
    public func then(_ block: (Self) -> Void) -> Self {
        block(self)
        return self
    }
}

#4


0  

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:

它不是完全相同的,但总的来说效果很好,它的优点是T是完全不受限制的。这显然是人为的例子,但是:

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

even allows:

甚至允许:

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

#5


-1  

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>()