为什么block使用copy(ARC下也可以使用strong)
为的是将block拷贝到堆上来
// MRC
1、block类似于函数指针,但是有时内联的(代码直接插入到调用者处,免去了普通函数调用的过程,效率更高)
2、block是一个代码片段,在OC中被看作是OC对象
3、block建议使用copy或者strong策略
4、block分为三种__NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
MRC默认情况下是__NSGlobalBlock__,使用(捕获)了外部非static和全局的变量会变成__NSStackBlock__,__NSStackBlock__的block使用了copy策略,就变成__NSMallocBlock__的了
// ARC下
1、如果使用
ARC默认情况下是__NSGlobalBlock__,使用(捕获)了外部非static和全局的变量会变成__NSStackBlock__,将__NSStackBlock__赋值给变量或者属性(非assign,使用assign还是__NSStackBlock__,使用了copy和strong都一样)系统默认将会发生copy操作到栈上,变成__NSMallocBlock__
5、在MRC和ARC下都不要使用assign策略
为什么将block拷贝到堆上来
1、block拷贝到堆上来基本是为了处理__NSStackBlock__,捕获外部变量(因为不捕获外部变量的时候,__NSGlobalBlock__是静态区的,这个内存区域是不会这几销毁,不存在内存问题)
2、__NSStackBlock__之所以会出现内存问题是因为,在栈区内存是系统自动管理的,会被自动释放掉(计数器为0的时候,以保证内存不至于太过消耗,甚至爆栈);自动释放后block的本质是一个结构体指针,原本指向它的指针地址还是在的这就是空指针,即便我们把block赋值为nil,地址还是0x0(空指针);一旦我们再区调用block无论是野指针还是指针都会crash。只有将block搞到堆上来,我们能购保证block的内存不会被自动释放,在我们不需要的情况下,block置为nil让ARC自动管理内存即可。
3、区别于OC可以给nil发送消息,OC会自动在发送消息的时候去判断接受消息的对象是不是nil,是nil发送函数objc_msgSend会直接return的,block是直接去寻址一个内存地址(void *FuncPtr)去执行的。
block为什么需要判空
// oc
int main() {
void(^testBlock)();
testBlock();
}
// clang -rewrite-objc
int main() {
void(*testBlock)();
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
}
// block结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
上面OC
代码直接运行会直接crash
。用clang
把OC
代码翻译成C++
代码,我们就会发现,testBlock();
被翻译为一个执行返回(void (*)(__block_impl *))
类型且有一个参数(__block_impl *)testBlock
的函数((__block_impl *)testBlock)->FuncPtr
;我们看到testBlock
强制转换成了结构体指针__block_impl *
,block最终调用的是__block_impl
结构体中的FuncPtr
;而此时的testBlock
并没有赋值只是一个nil
,地址是0x0
,加上FuncPtr
是结构体中的第四个成员变量,void *
占8个字节,int
类型占4个字节,那么FuncPtr
的执行地址是在0x0
的基础上向后移动8 + 4 + 4
,一共16个字节,就是0x10
;(在32位系统下void *
占4个字节,最终寻址地址应该是0x0c
)