多元组(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: () -> ()) {
//...
}
}