代码

多元组(Turple)

private func swapMe2<T>(a: inout T, b: inout T) {
    (a,b) = (b, a)
}

private func swapMe3<T>(a: T, b: T) -> (a: T, b: T){
    return (b,a)
}

var a = "qqq"
var b = "huhu"
swapMe2(a: &a, b: &b)
printLog("a: \(a), b: \(b)"

let (c,d) = swapMe3(a: a, b: b)
print("c: \(c), d: \(d)")

@autoclosure 和 ??

@autoclosure 做的事情就是 把一句表达式自动地封装成一个闭包 (closure)。这样有时候在语法上看起来就会非常漂亮。在参数名前面加上 @autoclosure 关键字:

    func logIfTrue(_ predicate: @autoclosure () -> Bool) {
        if predicate() {
            print("True")
        }
    }

直接写:

logIfTrue(2 > 1)

?? 这 个操作符可以判断输入并在当左侧的值是非 nil 的 Optional 值时返回其 value,当左侧是 nil 时返回右侧的值,比如:

    var level: Int?
    var startLevel = 1

    var currentLevel = level ?? startLevel

我们充满好奇心的点进 `??`的定义,可以看到两个版本:

 ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
 ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

在这里我们的输入满足的是后者,虽然表面上看 startLevel 只是一个 Int ,但是其实在使用时它被自动封装成了一个 () -> Int ,有了这个提示,我们不防猜测一下 ?? 的实现吧:

    //??的实现
//  ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
    func ww<T>(_ optional: T?, defaultValue: @autoclosure ()-> T) -> T {
        switch optional {
        case .some(let value):
            return value
        case .none:
            return defaultValue()
        }
    }

可能你会有疑问,为什么这里要使用 autoclosure,直接接受 作为参数并返回不行么,为何要 用 () -> T 这样的形式包装一遍,岂不是画蛇添足?其实这正是 autoclosure 的一个最值得称赞的地方。

如果我们直接使用 T ,那么就意味着在 `??` 操作符真正取值之前,我们就必须准备好一 个默认值传入到这个方法中,一般来说这不会有很大问题,但是如果这个默认值是通过一系列复杂计算得到的话,可能会成为浪费 -- 因为其实如果 optional不是 nil 的话,我们实际上是完全没有用到这个默认值,而会直接返回optional 解包后的值的。这样的开销是完全可以避免的,方法就是将默认值的计算推迟到 optional 判定为 nil 之后。

就这样,我们可以巧妙地绕过条件判断和强制转换,以很优雅的写法处理对optional 及默认值的取值了。最后要提一句的是,@autoclosure 并不支持带有输入参数的写法,也就是说只有形如 () -> T 的参数才能使用这个特性进行简化。

在 Swift 中,其实 &&|| 这两个操作符里也用到了@autoclosure:

//    &&(lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
    func ee(lhs: Bool, rhs: @autoclosure () -> Bool) -> Bool {
        if !lhs {
            return false
        } else if rhs() {
            return true
        } else {
            return false
        }
    }

//    ||(lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
    func hh(lhs: Bool, rhs: @autoclosure () -> Bool) -> Bool {
        if lhs {
            return true
        } else if rhs() {
            return true
        } else {
            return false
        }
    }

@escaping 和 @noescape

无论你将函数和闭包赋值给一个常量还是变量,你实际上是将常量或变量的值设置为对应函数或闭包的引用。

逃逸闭包:

当一个闭包作为一个参数传递到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping ,用来指明这个闭包是允许“逃逸”出 这个函数的。

非逃逸闭包

在Swift中可以在参数名前标注 @noescape 来指明这个闭包是不允许逃逸出这个函数的。因为 非逃逸闭包只能在函数体中被执行,不能脱离函数体执行,所以这使得编译器可以明确的知道运行时的上下文环境,进而做出优化

比如, sort(_:) 方法可以接受一个用于元素比较的闭包参数,它被指明为 @noescape ,因为排序结束后这个闭包就没用了。

闭包是一个逃逸闭包,这意味着它需要显式地引用 self ,闭包是一个非逃逸闭包,这意味着它可以隐式引用self。因为编译器知晓非逃逸闭包的上下文环境,所以非逃逸闭包中可以不写 self

func doWork(block: ()->()) {
    block()
}

func doWorkAsync(block: @escaping () -> ()) {
    DispatchQueue.main.async {//异步
        block()
    }
}

class S {

    var foo = "foo"

    func method1() {
        doWork {
            print(foo)
        }
        foo = "bar"
    }

    func method2() {
        doWorkAsync {
            print(self.foo)
        }

        foo = "bar"
    }

    func method3() {
        //如果我们不希望在闭包中持有 self  ,可以使用  [weak self]  的方式来表达:
        doWorkAsync {
            [weak self] in
            print(self?.foo)
        }

        foo = "bar"
    } 
}

S().method1() // foo
S().method2() // bar
S().method3() // nil

关于 @escaping 最后还有一点想要说明。如果你在协议或者父类中定义了一个接受@escaping为参数方法,那么在实现协议和类型或者是这个父类的子类中,对应的方法也必须被声明为@escaping,否则两个方法会被认为拥有不同的函数签名:

protocol P {
       func work(b: @escaping () -> ())
}

//可以编译
class C: P {
    func work(b: @escaping () -> ()) {
        print("in C")
        DispatchQueue.main.async {
            b()
        }
    }
}

//无法编译
class C1: P {
    func work(b: () -> ()) {
        //...
    }
}

results matching ""

    No results matching ""