最近用swift写app,发现很多特性都很有意思,并且很实用,比如closure, Optional类型等等。closure可以看做是objc中的block,c++和python中的lambda,熟悉函数式编程的同学可以很快上手。
在进行异步的网络调用时,需要把closure作为参数传递给执行网络请求的函数。比如NSURLSessionDataTask,需要把网络请求执行完毕之后的逻辑写在completionHandler里面。
1 2 3 4 |
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler |
很多时候我们需要在UIViewContronller里面来执行网络请求,请求完成后再更新ui上的数据,那么这里的closure中肯定会访问到self.someProperty或者self.someMethod()。这样closure本身就有一个对self的强引用(strong referrence)。另外,closure在swift中跟class一样,也是引用类型(referrence type)。这样一来closure和self就互相引用,产生strong referrence cycle。closure和self都无法被deallocate,造成资源的浪费。
为了解决这个问题,就要用到closure的捕获列表。语法如下:
1 2 3 |
{[capture list] (params) -> returnType in statements } |
捕获列表中可以使用两种类型:unowned和weak。因为一开始没有弄清楚这两种的区别,直接在代码中使用了unowned self,造成app在运行过程中出现“attempted to retain deallocated object”错误。
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 |
let getImage = {[unowned self](data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in let sizeOrder = ["Large", "Medium 800", "Medium 640"] var largestImgUrl: String? let json = JSON(data: data) for (index: String, subJson: JSON) in json["sizes"]["size"] { for size in sizeOrder { if (subJson["label"].string == size) { largestImgUrl = subJson["source"].string break } } } let photoUrl = NSURL(string: largestImgUrl!) var manager: SDWebImageManager = SDWebImageManager.sharedManager() manager.downloadImageWithURL( photoUrl, options: SDWebImageOptions.ProgressiveDownload, progress: nil) {[unowned self]( cellImage: UIImage!, error: NSError!, cacheType: SDImageCacheType, finished: Bool, imageUrl: NSURL!) -> Void in self.photoView?.image = cellImage } } |
这段代码中第29行可以看到,在closure中访问self时并没有判断self是否还存在。所以当网络请求执行完再更新ui的时候就会出现“attempted to retain deallocated object”。unowned和weak的一个区别在于weak self是optional类型。这样一来就可以利用optional chaining来做一个保护机制。正确的实现代码如下:
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 |
let getImage = {[weak self](data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in let sizeOrder = ["Large", "Medium 800", "Medium 640"] var largestImgUrl: String? let json = JSON(data: data) for (index: String, subJson: JSON) in json["sizes"]["size"] { for size in sizeOrder { if (subJson["label"].string == size) { largestImgUrl = subJson["source"].string break } } } let photoUrl = NSURL(string: largestImgUrl!) var manager: SDWebImageManager = SDWebImageManager.sharedManager() manager.downloadImageWithURL( photoUrl, options: SDWebImageOptions.ProgressiveDownload, progress: nil) {[weak self]( cellImage: UIImage!, error: NSError!, cacheType: SDImageCacheType, finished: Bool, imageUrl: NSURL!) -> Void in if let photoView = self?.photoView { photoView.image = cellImage } else { NSLog("photoView nil") } } } |
这里的29行使用了swift中的if let结构,如果photoView是有值的,则继续执行后面的逻辑,否则会走到else分支中。这样便很好的解决了上面的问题。
参考: