swift_041(Swift的@noescape和@escaping)

来源:互联网 发布:php比较好的教程 编辑:程序博客网 时间:2024/05/18 11:46


@noescapeswift3.0中已经被废弃,在swift3.0@noescape被用作一个默认值。

@escaping属性写在参数类型的前面而不是参数名称的前面。这是swift3里一个新的点。


这里需要先介绍一下escape的概念。当一个闭包当做一个参数传进函数里,这个闭包是在这个函数执行完后执行的,这个时候我们就说这个闭包从函数逃出来了(escape)。这种场景很常见,比如我们进行一个异步的请求,请求时会传入一个handler,比如当请求成功后执行达到回调的目的。


众所周知swift的内存管理是引用计数。闭包里用到的数据都需要捕捉到闭包里,保证闭包执行时这些数据不会被释放还在内存里。Xcode为了让我们意识到闭包里用到的对象其实已经被retain了,就要求我们访问当前属性时显示声明self。这个时候如果新手就很容易犯引用循环的错误。闭包retainselfself如果又持有retain了闭包。最后就谁都释放不了,内存就泄露了。


swift里针对非escape@noescape表示。

比如map函数就使用了:

funcmap<T>(@noescape transform: (Self.Generator.Element)throws ->T)rethrows -> [T]


这样标记之后能看到的好处就是这个闭包里如果再使用self的属性不需要加self.了。对于编译器而言,在知道是noescape闭包后可以进行一些内存的优化。


非逃逸闭包(@noescape

如果这个闭包是在这个函数结束前内被调用,就是非逃逸的即noescape


逃逸闭包(@escaping)(Escaping Closure

如果一个闭包被作为一个参数传递给一个函数,并且在函数return之后才被唤起执行,那么这个闭包是逃逸闭包。如果这个闭包是在函数执行完后才被调用,调用的地方超过了这函数的范围,所以叫逃逸闭包@escaping


举个例子就是我们常用的masonry或者snapkit的添加约束的方法就是非逃逸的。因为这闭包马上就执行了。

publicfuncsnp_makeConstraints(file: String = #file, line: UInt = #line, @noescape closure: (make: ConstraintMaker) ->Void) -> Void {

ConstraintMaker.makeConstraints(view:self, file: file, line: line, closure: closure)

}


网络请求请求结束后的回调的闭包则是逃逸的,因为发起请求后过了一段时间后这个闭包才执行。比如这个Alamofire里的处理返回jsoncompletionHandler闭包,就是逃逸的。


publicfuncresponseJSON(

        queue queue: dispatch_queue_t? = nil,

  options: NSJSONReadingOptions = .AllowFragments,

        completionHandler: Response<AnyObject, NSError> -> Void)

->Self 

{

return response(

            queue: queue,

  responseSerializer:Request.JSONResponseSerializer(options: options),

            completionHandler: completionHandler

)

}

很多人在写闭包参数的时候总是忽略去判断这个闭包是否是逃逸的。这对闭包的内存管理优化不太友好,都被当做了逃逸闭包处理。所以在3中做出了一个对调的改变:所有的闭包都默认为非逃逸闭包,不再需要@noescape;如果是逃逸闭包,就用@escaping表示。比如下面的一段代码,callBack在函数执行完后1秒才执行,所以是逃逸闭包。

funcstartRequest(callBack: ()->Void ) {

DispatchQueue.global().asyncAfter(deadline:DispatchTime.now() + 1) { 

callBack()

}

}

这样就需要显示的声明@escaping才能编译通过。



重点

这里几点关于创建默认不可逃逸闭包的好处:最明显的好处就是编译器优化你的代码的性能和能力。如果编译器知道这个闭包是不可逃逸的,它可以关注内存管理的关键细节。

而且你可以在不可逃逸闭包里放心的使用self关键字,因为这个闭包总是在函数return之前执行,你不需要去使用一个弱引用去引用self.这对你而言是一个非常nice的功能。


参考:http://blog.csdn.net/yydev/article/details/52683329


  • @escaping

Swift3的行为更好。因为它默认是安全的:如果一个函数参数可能导致引用循环,那么它需要被显示地标记出来。@escaping标记可以作为一个警告,来提醒使用这个函数的开发者注意引用关系。非逃逸闭包可用被编译器高度优化,快速的执行路径将被作为基准而使用,除非你在有需要的时候显式地使用其他方法。


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

    print("header")

    block()

    print("footer")

}

doWork {

    print("work")

}

//控制台打印的消息如下:

//header

//work

//footer


对于上述的block调用是同步行为。我们修改一下代码,将block放到一个异步操作中,让它在doWork返回后被调用。这个时候我们就需要用@escaping标记表明这个闭包是会逃逸的。


func doWorkAsync(block: @escaping () -> ()) {

    DispatchQueue.main.async { 

        block()

    }

}

没有逃逸的闭包的作用域是不会超过函数本身的,所以说我们不需要担心在闭包内持有self。逃逸的闭包就不同了,因为需要确保闭包内的成员依然有效,如果在闭包内引用self以及self的成员的话,就要考虑闭包内持有self的情况了。

class S {

    var foo = "foo"


    func method1() {

        doWork {

            print(foo)

        }

        foo = "bar"

    }


    func method2() {

        doWorkAsync {

            print(self.foo)

        }

        foo = "bar"

    }

func method3() {

        doWorkAsync {

            [weak self] _ in

            print(self?.foo)

        }

        foo = "bar"

    }


}

S().method1()// foo

S().method2()// bar

S().method3()// nil

method1不需要考虑self .foo的持有情况,而method2需要考虑,我们让闭包持有了self,打印的值就是foo赋值之后的内容bar,如果我们不希望闭包内持有self的话,可以使用[weak self]的方法来表示. method3就是这样,在闭包执行的时候已经没有了对实例对象的引用,所有说输出是nil

  • weak unowned
  • 上面我用的是 [weak self],如果用[unowned self]表示的话,method3就需要稍做修改:funcmethod3() {
  •       doWorkAsync {
  •           [unowned self] _ in
  •           print(self.foo)
  •       }
  •       foo = "bar"
  •   }
    这两者都是用来防止循环引用的
  • unowned有点像oc里面的unsafe_unretained,而weak就是以前的weak
  • 对于这两者的使用,要视情况而定。用unowned的话,即使它原来的引用的内容被释放了,它仍然会保持对被已经释放了的对象的一个引用,它不能是Optional也不能是nil值,这个时候就会出现一个问题,如果你调用这个引用方法或者访问成员属性的话,就会出现崩溃。
  • weak要稍微友善一点,在引用的内容被释放之后,会自动将weak的成员标记为nil。有人要说,既然这样,那我全部使用weak。但是在可能的情况下,我们还是应该倾向于尽量减少出现Optional的可能性,这样有助于代码的简化。Apple给我们的建议是如果能够确定访问时不会被释放的话,尽量用unowned,如果存在被释放的可能性的话,就用weak


  • S().method1()// foo
  • S().method2()// bar
  • S().method3()// nil
    参考资料:http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground





原创粉丝点击