Straybird’s Blog

A blog for life and study

读书笔记(2) iOS与OSX多线程和内存管理

续上文,这一篇主要记录block中的一些点。block中最需要理解的就2个概念:匿名函数,局部变量,表面看起来很简单,但实际上深究起来,里面的涵义大有可以挖掘的地方。

访问局部变量

在block中可以使用局部变量,如下所示:

int i = 1;
^{
    NSLog(@"i is %d",i);
}();

第一反应也许你会觉得这有什么了不起的?block定义在i之后,可以访问不是很正常么?想想函数,再想想block这种匿名函数,就会明白这的确是有些不一样的。

这是怎么实现的呢?block的底层实际上时结构体,这个结构体包含了一些信息,有其函数实现,也有isa指针等。当在block中访问局部变量时,结构体会增加相应的成员变量,并在初始化的时候记录这些局部变量的值。那么为什么不能修改呢?看具体的实现就知道了,只提供了访问的方式,没提供修改的方式。

需要注意一点的是,block中不能访问c数组,原因也很明显了,c中不支持不能直接复制数组名。如想实现类似的功能,改成指针访问就可以了。

修改局部变量

修改局部变量的实现针对局部变量的类型分2种:

第一种是局部静态变量,这种的实现很简单,直接用指针获取代替变量获取,就可以实现修改了。

第二种是局部变量,也许你也会问这个不能跟局部静态变量一样吗?原因是block在局部变量的生命域结束之后还要能够存在。所以针对局部变量,情况更复杂一些。用 __block修饰的局部变量会被解析为struct类型,该struct类型中有一个指向自身的指针(__forwarding),当变量从栈中复制到堆中时,该指针也将指向堆中的变量。为了实现block在局部变量生命域结束之后还能存在,则必须在必要的时候对block从栈中复制到堆中。

局部对象

使用局部对象的情况跟变量略微有些不同,因为对象还会考虑所有权修饰符。block不能访问使用__autoreleasing的局部对象,访问__strong或者__weak时,其成员函数中也获取相应类型的变量。

还有一点需要注意,没有使用__block修饰符时,虽然不能让对象重定位,但可以修改,比如增加object之类的,这是什么原因,很简单了,block在获取的时候,是直接赋值的,对象是指针访问,不能修改指针的指向,但修改其指向的内容时没问题的,普通的指向变量的指针也是一样。

循环引用

使用block时一定要注意循环引用的问题,一般情况下2种方式解决,一种是使用__weak关键字,一种是使用__block(MRC中使用则不会retain,ARC中会,所以最需要设置为nil)。两种方式各有优劣,使用__weak要考虑__unsafe_unretain,使用__block,则如果block没有执行,循环引用一直存在。

block类型

block有3种,简单地说就是: global,stack和heap。

MRC时代,当block中当问局部变量时,默认是在stack上,所以需要的时候需要将其复制到heap上。相较而言,ARC时代对block进行内存管理就容易多了,因为默认就在heap上,也没有stack上这种类型了。

block在objective-c中的确是属于比较难一些的,但是究其根底,其实也还好,书中还有很多内容很精彩,尽管有些部分在ARC时代已经不适用了,但仍然很值得细细品味,我这里只记录了一些很简单的笔记。想自己看实现的也可以通过书中给出的简单的clang -rewrite-objc即可将objc代码转换成c/c++代码。最后附上一个block快速测试的一个链接,我也是看到别人的博客推荐,觉得很值得尝试下的。

Comments