• Instance Methods are Curried Functions in Swift


    An instance method in Swift is just a type method that takes the instance as an argument and returns a function which will then be applied to the instance.

    recently learned about a Swift feature that blew my mind a little. Instance methods are just curried functions that take the instance as the first argument. What’s a curried function, you ask?

    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.

    Example

    Consider this simple example of a class that represents a bank account:

    1
    2
    3
    4
    5
    6
    7
    class BankAccount {
        var balance: Double = 0.0
    
        func deposit(amount: Double) {
            balance += amount
        }
    }
    

    We can obviously create an instance of this class and call the deposit() method on that instance:

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

    So far, so simple. But we can also do this:

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

    This is totally equivalent to the above. What’s going on here? We first assign the method to a variable. Pay attention to the lack of parentheses after BankAccount.deposit — we are not calling the method here (which would yield an error because you can’t call an instance method on the type), just referencing it, much like a function pointer in C. The second step is then to call the function stored in the depositor variable. Its type is as follows:

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

    In other words, this function has a single argument, a BankAccount instance, and returns another function. This latter function takes a Double and returns nothing. You should recognize the signature of the deposit() instance method in this second part.

    I hope you can see that an instance method in Swift is simply a type method that takes the instance as an argument and returns a function which will then be applied to the instance. Of course, we can also do this in one line, which makes the relationship between type methods and instance methods even clearer:

    1
    BankAccount.deposit(account)(100) // balance is now 300
    

    By passing the instance to BankAccount.deposit(), the instance gets bound to the function. In a second step, that function is then called with the other arguments. Pretty cool, eh?

    Implementing Target-Action in Swift

    Christoffer Lernö shows in a post on the developer forums how this characteristic of Swift’s type system can be used to implement the target-action pattern in pure Swift. In contrast to the common implementation in Cocoa, Christoffer’s solution does not rely on Objective-C’s dynamic message dispatch mechanism. And it comes with full type safety because it does not rely on selectors.

    This pattern is often better than using plain closures for callbacks, especially when the receiving objects has to hold on to the closure for an indeterminate amount of time. Using closures often forces the caller of the API to do extra work to prevent strong reference cycles. With the target-action pattern, the object that provides the API can do the strong-weak dance internally, which leads to cleaner code on the calling side.

    For example, a Control class using target-action might look like this in Swift (adopted from a dev forums post by Jens Jakob Jensen):

    Update July 29, 2014: Made the action property in TargetActionWrapper non-optional. target must be optional because it is weak.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    protocol TargetAction {
        func performAction()
    }
    
    struct TargetActionWrapper<T: AnyObject> : TargetAction {
        weak var target: T?
        let action: (T) -> () -> ()
    
        func performAction() -> () {
            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]?.performAction()
        }
    }
    

    Usage:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class MyViewController {
        let button = Control()
    
        func viewDidLoad() {
            button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
        }
    
        func onButtonTap() {
            println("Button was tapped")
        }
    }
    
  • 相关阅读:
    ES6的新特性(18)——async 函数
    ES6的新特性(17)——Generator 函数的异步应用
    ES6的新特性(16)——Generator 函数的语法
    ES6的新特性(15)——Promise 对象
    ES6的新特性(14)——Iterator 和 for...of 循环
    ES6的新特性(13)——Symbol
    ES6的新特性(12)——Set 和 Map 数据结构
    ES6的新特性(11)——Class 的继承
    我的游戏学习日志22——游戏元素的解析(6)
    我的游戏学习日志21——游戏元素的解析(5)
  • 原文地址:https://www.cnblogs.com/motoyang/p/4931357.html
Copyright © 2020-2023  润新知