Swift 实例方法是柯里化函数

swift 里实例方法只是一种方法,以实例作为参数,并返回一个将被应用到该实例的函数。

我最近学习swift的特性,出乎我的意料。实例方式是柯里化函数,以实例作为第一个参数。你可能会问,什么是柯立化函数?

The basic idea behind currying is that a function can be partially applied, meaning that some of its parameter values can be specified (bound) before the function is called. Partial function application yields a new function.

原文的这段话翻译过来不好理解。下面换一种方式解释一下。

柯里化的背后基本思想是:把接受多个参数的方法变换成接受第一个参数的方法,并且返回接受余下的参数且返回结果的新方法。

这里读起来有点绕,举个例子,swift 中我们可以这样写出多个括号的方法:

func addTwoNumbers(a: Int)(num: Int) -> Int {
return a + num
}

let addToFour = addTwoNumbers(4) // addToFour 是一个 Int -> Int
let result = addToFour(num: 6) // result = 10
func addTwoNumbers(a: Int)(num: Int) -> Int {
return a + num
}

或者

func greaterThan(comparor: Int)(input : Int) -> Bool{
return input > comparor;
}
let greaterThan10 = greaterThan(10);
greaterThan10(input : 13)    // 结果是 true
greaterThan10(input : 9) // 结果是 false

柯里化是一种量产相似方法的好办法,可以通过柯里化一个方法模板来避免写出很多重复代码,也方便了今后维护。

下面继续来看文章。

示例

考虑这个简单的示例类,代表一个银行账户:

class BankAccount{
var balance: Double = 0.0
    func deposit(amount: Double){
balance += amount
}
}

显然我们可以创建这个类的一个实例并调用实例的存款()方法

let account = BankAccount()
account.deposit(100) // balance is now 100

到目前为止,都很简单。但是我们也可以这样做:

let depositor = BankAccount.deposit
depositor(account)(100) // balance is now 200

这是完全等同于上面。这是怎么回事?我们首先分配一个变量的方法。注意 BankAccount.deposit 后缺少括号 — 我们没有调用方法(如果调用了将产生一个错误,因为你不能调用一个实例方法的类型),只是引用它,就像在 C 语言中一个函数指针。第二步是调用存储在 depositor 变量中的函数,它的类型如下:

let depositor: BankAccount -> (Double) -> ()

换句话说,这个函数只有一个参数,BankAccount 实例,并且返回另一个函数。后一个函数接收一个 Double 并什么也不返回。你应该意识到 depositor()实例方法的签名在第二部分。

我希望你可以看到一个实例方法在 Swift 是一个简单类型的方法,以实例作为参数,并返回一个函数将被应用到实例。当然,我们也可以用一行代码表示,使得类型方法和实例方法之间的关系更加清晰:

BankAccount.deposit(account)(100)

通过传递实例到 BankAccount.deposit(),这个实例被函数绑定。第二步,函数和其他参数被调用。很酷,不是吗?

实现 swift 的 Target-Action

Christoffer Lernö 在 a post on the developer forums 中展示了如何用纯 swift 类型系统特征来实现 the target-action pattern

和 Cocoa 中的实现不同,Christoffer Lernö 的实现不依赖于 object-c 的动态消息调用机制。并且它是类型安全的,因为不依赖于 selectors。

Swift 中 Selector 只能使用字符串生成。这面临一个很严重的问题,就是难以重构,并且无法在编译期间进行检查,这是十分危险的行为。

这种模式比通常使用的函数闭包回调要好,特别是当接收对象必须保持在闭包内在不确定的时间里。使用闭包经常迫使 API 的调用者做一些额外的工作,以防止循环引用。使用 target-action模式,提供 API 的对象可以做 strong-weak,调用方可以清洁代码。

例如,一个控制类在 Swift 里使用 target-action 模式可能是这个样子(a dev forums post by Jens Jakob Jensen

2014/7/29 更新:action 属性在 TargetActionWrapper 是非可选的,target 必须是可选的因为它是 weak

protocol TargetAction {
func performAciton()
}
struct TargetActionWrapper<T:AnyObject> : TargetAction {
weak var target: T?
let action: (T)->()->()
    func performAciton() {
if let t = target {
action(t)()
}
}
}
enum ControlEvent {
case TouchUpInside
case ValueChanged
//....
}
class Control{
var actions = [ControlEvent: TargetAction]()
    func setTarget<T:AnyObject>(target:T,action:(T)->()->(),controlEvent:ControlEvent){
actions[controlEvent] = TargetActionWrapper(target: target, action: action)
}
    func removeTargetForControlEvent(controlEvent: ControlEvent)  {
actions[controlEvent] = nil
}
    func performActionForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent]?.performAciton()
}
}

使用

class MyViewController{
let button = Control()
    func viewDidLoad(){
button.setTarget(self, action: MyViewController.onButtonTap, controlEvent:.TouchUpInside)
}
    func onButtonTap(){
print("Button was tapped")
}
}

原文地址:

参考资料:

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.