Block进阶

前言

在了解了Block的基础知识后,希望对它有更多的认识比如:

  • Block里的野指针错误为什么会出现?怎样避免?
  • Block里的循环引用的原理是什么?
  • BlockARCMRC下有什么不同?

在看iOS与OS X多线程和内存管理时,看到block那章,我就觉得看不懂了。这一章包含太多的底层源码,非常枯燥而且不好理解,因此难以集中精神。
因此在我对之的理解稍微清晰了一些时,我就记录了下来。

将环境改为MRC:打开XCode->点击工程->Build Phases->Compile Sources->点击要配置MRC环境的文件->添上-fno-objc-arc。若要改回ARC, 则将-fno-objc-arc改成-fobjc-arc.

image

image

Block的种类

NSStackBlock
  • 引用了外部变量的block
  • MRC下,NSStackBlock位于中,block函数体返回后,其位于的栈段会被清除,即block会无效,此时如果再次访问block,就会造成野指针错误。解决方法是:通过对block做copy操作,使之转换为位于中的NSMallocBlock
  • ARC下,与MRC不同的是,不需要再做copy操作,在ARC下,所有的block都会自动转到堆区,即NSMallocBlock。
e.g.
1
2
3
4
5
6
7
typedef void (^myBlock)();

char a = 'a';
myBlock block = ^ {
NSLog("%c", a);
}

NSMallocBlock

  • NSStackBlock copy得来,位于内存的区。

NSGlobalStack

  • 没有引用外部变量的block就是NSGlobalStack
  • 既不位于,也不位于,是程序段
e.g.
1
2
3
4
int (^sum)(int, int) = ^(int x, int y) {
return x + y;
}

野指针错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef void (^block)();

block arc_getBlock() {
char a = 'a';
void (^myBlock) = ^{
printf("%c",a);
};
NSLog(@"myBlock:%@",myBlock);
block block_copy = [[myBlock copy]autorelease];
NSLog("block_copy:%@",block_copy);
// return block_copy;
return myBlock;
}

void test() {
block myBlock = arc_getBlock();
myBlock();
}

arc_getBlock()最后返回的是block_copy就不会出现野指针错误,而返回myBlock就会出现野指针错误,因为myBlock在栈上,返回后,就被清理了。

按理说是这样,但我做测试的时候,发现两者都可以。

  • 全局和静态变量在内存中的位置是确定的,block copy时不会retain这些对象。
  • 局部变量在block copy时,会被retain,增加其引用计数。
  • 实例变量或属性在block copy时不是直接retain这个对象本身,而是retain self。


    下面来做一个稍大的测试以便更好地理解
    e.g

最后,这里有一个从OneVCat的文中看到的关于block的quiz,有兴趣可以做做。