1.一共有6种类型:
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
// 只有在调用copy的情况下才会生成MallocBlock,ARC下会默认把栈上的block复制到堆上
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
// 后面三种主要是用GC
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
2.block是oc对象,含有isa的结构体
3.block只有在主动调用copy的时候, 才可能会对外部变量(主要讲oc对象)的引用计数器造成影响,会对变量进行retain操作,(__block修饰后的变量在MRC下不会对oc对象进行retain操作,需要手动管理内存),其他所有情况都不会对其引用计数器造成改变。
但是这里需要明确的是,block结构体内部其实会const一份外部变量(以参数的形式传入结构体)作为其结构体的成员变量,也就是将变量的副本存到了block结构体,所以对原对象的引用计数器没有影响。
然后在block触发函数指针调用的时候可以直接进行值传递(注意这里不是赋值的意思,是指函数方法内会生成一个局部变量,也就是外部对象,对它进行一个值传递,赋值为结构体内的变量值,也叫做截取捕捉变量),但是修改内容以及复制都不被允许,因为const化了。(全局与静态,以及__block修饰 除外)。
其实道理上也可以理解,外部的变量的作用域与生命周期问题,即使block通过指针拿到该变量的修改复制权限,其实也很危险,但变量作用域到头释放的时候,block内部的指针就是野指针。
但是%p打地址的时候你会发现对象地址还是同一个(因为本质上还是同一个对象,外部release了,Block内部也用不了)。
4.__block修饰变量相当于在block结构体内做了一次指针引用。
针对oc对象,__block其实可以看作一种提醒编译器优化的标识,加上的话该oc对象会在block结构体内会生成一个__block的对象(含有isa的结构体,含有该对象,flag = 131 = BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT,,意思是使用结构体内部的copy/dispose辅助函数手动管理,内部无需再次retain,直接指针赋值即可),并且可以使该__block对象在栈和堆上都可以得到(fowarding指针),所以也就是,即使block从栈复制到堆上,所以加上__block修饰的外部变量,当copy到堆上的时候,外部变量不会被复制(或retain),而是直接赋值指针地址,因此该变量的引用计数器保持不变。
如果不加的话(flag = BLOCK_FIELD_IS_OBJECT ),当copy到堆上时,block会调用了[obj retain]方法,引用计数器会+1, 否则复制到堆上时,会对找不到该变量(因为局部变量受作用域影响,肯定比block的生命周期短),且在复制的过程中,通过fowarding指针可以找到堆上的该变量。
非oc对象复制的话,加__block的话,flag = BLOCK_FIELD_IS_BYREF,即使复制多次也只会复制一次,后面只是将该变量的引用计数器+1(但其实对于基本数据类型的话,我们也不会去关心引用计数器)。不加__block的话,相当于block结构体对基本数据类型对象做了一次const化到了结构体内,只能使用,不能修改和赋值。
5.block作为参数,或者作为返回值时,MRC下,一定要copy到堆上,否则会超出作用域会被释放,导致野指针。这里也是日常代码时常见场合,建议最好使用ARC,编译器会默认把block复制到堆上,也就是[[block copy] autorelease]。
6.block对于以参数形式传进来的对象,不会强引用。因为传进来的参数,根本不会进入block的结构体,只是以形参的方式在block触发函数指针时出现。
7.block的循环引用问题,主要发生在当把block作用成员变量时,切记,如果block内部使用了self,或者其他成员变量(基本数据类型也不行),都会引起循环引用,__weak(ARC)和__block(MRC)都可以解决该问题。
e.g.:
__weak _-typeof(self) weakSelf = self;
^{[weakSelf someMethod];}
对block的理解不深的情况下使用,很容易造成循环引用的泛滥。其实在苹果原生的设计系统中,在View与ViewController间的通讯主要还是使用delegate。
而使用block的场合主要是类方法,比如UIView:
[UIView animateWithDuration:1.0 animations:^{
// 动画效果
}];
8.全局变量,全部静态变量,静态变量,不需要加入__block也可以在block中被修改,全局的好理解, 作用域、生命周期都比block要大要长,所以在block内可以直接使用、修改。
静态变量(存储在.data区)也是同理,但是方案上,block在结构体内部获取了静态变量的指针,所以可以直接修改。
9.^{printf("Hello World!")} 在MRC下,clang后会发现,没有引入外部变量时,但是block结构体isa指针仍然为StackBlock,但开启ARC后,isa指针我GlobalStack指针。
推荐几篇好文&源码:(源码难看,但是还得看,否则都是道听途说)
1.http://www.jianshu.com/p/e03292674e60#
2.http://www.jianshu.com/p/51d04b7639f1
3.http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c
4.http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/Block_private.h
5.http://blog.ibireme.com/2013/11/27/objc-block/
6.http://mobile.51cto.com/hot-403914.htm
7.http://mobile.51cto.com/hot-403931.htm
8.http://mobile.51cto.com/hot-403935.htm
网友评论