Swift之自动闭包(@autoclosure、@noescape、@escape、??)

@autoclosure(自动闭包)
 1:自动闭包,顾名思义是一种自动创建的闭包,用于包装函数参数的表达式,可以说是一种简便语法.
 2:自动闭包不接受任何参数,被调用时会返回被包装在其中的表达式的值。
 3:自动闭包的好处之二是让你能够延迟求值,因为代码段不会被执行直到你调用这个闭包,这样你就可以控制代码什么时候执行。
 4:含有autoclosure特性的声明同时也具有noescape的特性,及默认是非逃逸闭包,除非传递可选参数escaping.如果传递了该参数,那么将可以在闭包之外进行操作闭包,形式为:请使用@autoclosure(escaping)。

下面一起来看一个简单例子:比如我们有一个方法接受一个闭包,当闭包执行的结果为true的时候进行打印:

func printIfTrue(predicate: ()-> Bool){
    if predicate(){
       print("the result is true")
    }
}

//1直接调用方法
printIfTrue { () -> Bool in
    return 2 > 1
}
//2闭包在圆括号内
printIfTrue({ return 2 > 1 })
//3:使用尾部闭包方式,闭包体在圆括号之外
printIfTrue(){ return 2 > 1 }
//4:在 Swift 中对闭包的用法可以进行一些简化,在这种情况下我们可以省略掉 return,写成:
printIfTrue({ 2 > 1})
//5:还可以更近一步,因为这个闭包是最后一个参数,所以可以使用尾随闭包 (trailing closure) 的方式把大括号拿出来,然后省略括号,变成:
printIfTrue{2 > 1}


但是不管哪种方式,表达上不太清晰,看起来不舒服。于是@autoclosure就登场了。我们可以改换方法参数,在参数名前面加上@autoclosure关键字:


func printIfTrue(@autoclosure predicate: ()-> Bool){
    if predicate(){
        print("the result is true")
    }
}

printIfTrue(2 > 1)
//直接进行调用了,Swift 将会把 2 > 1 这个表达式自动转换为 () -> Bool。这样我们就得到了一个写法简单,表意清楚的式子。

如果有多个闭包,那么就有优势了,而@autoclosure是可以修饰任何位置的参数:


func printInformation(@autoclosure predicate1: ()-> Bool,@autoclosure predicate2: ()-> Bool){
    
    if predicate1() && predicate2(){
        print("the result is true")
    }else{
        print("the result is false")
    }
}
printInformation( 3 > 2, predicate2: 4 > 1)


@noescape和 @escape

对于autoclosure属性来讲,还有2个相关的属性要了解一下。也就是@noescape和@escape。这2个属性都是用来修饰闭包的。@noescape意思是非逃逸的闭包,而@escape则相反。默认情况下,闭包是@escape的。表示此闭包还可以被其他闭包调用。比如我们常用的异步操作

func executeAsyncOp(asyncClosure: () -> ()) -> Void {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        asyncClosure()
    }
}

其中asyncClosure在dispatch_async中的闭包中调用,完成异步的操作。因为闭包默认是@escape的,以上代码是可以运行的。但是当我们在asyncClosure前面加入@noescape属性时候,编译器就会报错:

func executeAsyncOp(@noescape asyncClosure: () -> ()) -> Void {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        asyncClosure()
    }
}
error:closure use of @noescape parameter 'asyncClosure' may allow it to escape asyncClosure()

@noescape属性是在 Swift1.2中引入的,把传入闭包参数的调用限制在调用的函数体内,对性能有一定的提升,同时将闭包标注为@noescape使你能在闭包中隐式地引用self。

 Swift标准库中很多方法,都用了@noescape属性,比如 Array对应的方法 mapfilter reduce

func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]

func filter(@noescape includeElement: (Self.Generator.Element) -> Bool) -> [Self.Generator.Element]

func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) -> T) -> T


但是注意:@noescape在Swift3中是默认的并且建议移除@noescape。事实上,@noescape在swift3中已经被废弃,以后都不会用到它了,而@escape改为@escaping。

下面看一下@escaping

Swift中我们可以定义一个接受函数作为参数的函数,而在调用时,使用闭包的方式来传递这个参数是常见手段,如下:

func doWork(closure: () -> ()) {
    closure()
    print("end")
}

doWork {
    print("doWork")
}
//doWork
//end
这种最简单的形式的闭包其实还默认隐藏了一个假设,那就是参数中closure的内容会在doWork返回前就完成, 由执行结果也可以看出来。也就是说,对于closure的调用是同步行为,那么如果进行异步呢?大家应该会猜到结果,异步不就是立马返回嘛。

那么我们改变一下代码,将closure放到一个Dispatch中去,让它在doWorkAsync返回后被调用的话,我们就需要在closure的类型前加上 @escaping标记来表明这个闭包是会“逃逸”出该方法的:

func doWorkAsync(closure: @escaping () -> ()) {
    DispatchQueue.main.async {
        closure()
    }
    print("end")
}

doWorkAsync {
   print("doWork")
}

//end
//doWork

在使用闭包调用这个两个方法时,也会有一些行为的不同。我们知道闭包是可以捕获其中的变量的。对于doWork参数里这样的没有逃逸行为的闭包,因为闭包的作用域不会超过函数本身,所以我们不需要担心在闭包内持有self等。而接受@escaping的doWorkAsync则有所不同。由于需要确保闭包内的成员依然有效,如果在闭包内引用了self及其成员的话,Swift将强制我们明确地写出self。我们可以对比下面的两个用例的不同之处:

class Person {
    var name = "Jack"
    
    func method1() {
        doWork {
            print("name = \(name)")
        }
        name = "Rose"
    }
    
    func method2() {
        doWorkAsync {
            print("name = \(self.name)")
        }
        name = "Rose"
    }
    
    func method3() {
        doWorkAsync { [weak self] in
            print("name = \(String(describing: self?.name))")
        }
        name = "Rose"
    }
}
显然,method1中调用者不需要考虑self.name的持有情况,使用起来相当直接。对name的打印输出的是原始值。而method2中由于闭包可逃逸,Swift 强制我们写明self,以起到提醒作用,我们就需要考虑self的持有情况。在这个例子中,我们让闭包持有了self,打印的值是最后对name赋值后的Rose。如果不使用self,出现错误:



如果我们不希望在闭包中持有self ,可以使用 [weak self]的方式来处理,就是method3方法

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

protocol P {
    func work(b: @escaping () -> ())
}
//正常
class C: P {
    func work(b: @escaping () -> ()) {
        
    }
}
如果我们没有使用@escaping,出错:


??操作符


在Swift中,有一个非常有用的操作符,可以用来快速地对nil进行条件判断,那就是??。这个操作符可以判断输入并在当左侧的值是非nil的 Optional 值时返回其 value,当左侧是nil时返回右侧的值,比如:

var level: Int?
var startLevel = 1

var currentLevel = level ?? startLevel 
上面例子结果为1,例子中我们并没有设置过level,因此最后startLevel被赋值为currentLevel。??操作符其实有两种形式,如下:
func ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
func ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
在这里我们的输入满足的是后者,虽然表面上看startLevel只是一个Int,但是其实在使用时它被自动封装成了()->Int,我们可能简单猜测一下内部实现,如下:

func ??<T>(optinal: T?, defaultValue: @autoclosure () -> T) -> T {
    switch optinal {
    case .some(let value):
        return value
    case .none:
        return defaultValue()
    }
}
其实就是对可选值的应用。



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值