OC与Swift闭包对比总结

510 查看

最近在看Swift闭包截获变量时遇到了各种问题,总结之后发现主要是还用停留在OC时代的思维来思考Swift问题导致的。借此机会首先复习一下OC中关于block的细节,同时整理Swift中闭包的相关的问题。不管是目前使用OC还是Swift,又或者是从OC转向Swift,都可以阅读这篇文章并与我交流。

OC的block

OC的block已经有很多相关的文章介绍了,主要难点在于__block修饰符的作用和原理,以及循环引用问题。我们首先由浅入深举几个例子看一看__block修饰符,最后分析循环引用问题。这里的讨论都是基于ARC的。

截获基本类型

OC的block会截获外部变量,对于int等基本数据类型,block的内部会拷贝一份,简单来说,它的实现大概是这样的:

因为block内部拷贝了截获的变量的副本,所以生成block后再修改变量,不会影响被block截获的变量。同时block内部也不能修改这个变量。

修改基本类型

如果要想在block中修改被截获的基本类型变量,我们需要把它标记为__block

这是因为,对于被标记了__block的变量,block在截获它时,会保存一个指针。简单来说,它的实现大概是这样的:

由于block中一直有一个指针指向value,所以block内部对它的修改,可以影响到block外部的变量。因为block修改的就是那个外部变量而不是外部变量的副本。

上面关于block具体实现的例子只是一个简化模型,事实上并非如此,但本质类似。总的来说,只有由__block修饰符修饰的变量,在被block截获时才是可变的。关于这方面的详细解释,可以参考这三篇文章:

截获指针

block截获指针和截获基本类型是相似的,不过稍稍复杂一些。先看一个最简单的例子。

在截获基本类型时,block内部可能会有int capturedValue = value;这样的代码,类比到指针也是一样的,block内部也会有这样的代码:Person *capturedP = p;。在ARC下,这其实是强引用(retain)了block外部的p

由于block内部的p和外部的p指向的是同一块内存地址。所以在block外部修改p的属性,依然会影响到block内部截获的p

需要强调一点,这里的p依然不是可变的。修改pname不是改变p,只是改变p内部的属性:

改变指针

类比__block修饰符对基本类型的作用原理,由它修饰的指针,在被block截获时,截获的其实是这个指针的指针。比如我们把刚刚的例子修改一下: