block的本质,实际上是[带有自动变量值的匿名函数],block的不安全,除了循环引用,99%的崩溃都是因为你没有给block判空,其他问题,都是因为循环引用.
block的真正结构:
struct Block_descriptor_1{
uintptr_t reserved;
uintptr_t size;
}
struct Block_layout{
void isa;
volatitle int32_t flags;
int32_t reserved;
void(invoke)(void *,...);
struct Block_descriptor_1 descriptor;
}
在objc中,根据对象的定义,凡是首地址是isa的结构体指针,都可以认为是对象(id),这样在objc中,block实际上就算是对象.
那么既然block是个对象,name就应该有Class,那么block的class是什么呢?
在block runtime中,定义了6中类
_NSConctreteStackBlock 栈上创建block
_NSConctreteMallocBlock 堆上创建的block
_NSConctreteGlobalBlock 作为全局变量的block
_NSConctreteWeakBlockVariable
_NSConctreteAutoBlock
_NSConctreteFinalizingBlock
全局block
在编译完成后,block内部的代码将会提取出来,成为一个单独的C函数,创建block时,实际上就是在方法中声明一个struct,并且初始化该struct的成员,而执行block时,就是调用那个单独创建的c函数,并把struct指针传递过去,block的实际效果,相当于C语言中的匿名函数
于是就可以理解_NSConctreteGlobalBlock的使用了,因为全局block是当一个block内部没有捕获任何外部变量时,就会是一个全局block类,此时,这个block与一个函数无异,所以那么他就应该和函数一样的静态特性,而且,我们在调用block的时候,其实和普通的c函数的调用很相似,都是名称家括号:block()
那么有函数一样静态特性的block,显然不需要再去考虑其他的生命周期
栈block
这个类型的block,是在编译器发现block内部引用了外部变量后,会生成的block类型.
在block内部有引用外部变量时,当struct第一次被创建时.他是存在于该函数的栈帧上的,其Class是固定的_NSConcreteStackBlock,其捕获的变量是会赋值到结构体成员上,所以当block初始化完成后,捕获到的变量不能更改
当函数返回时,函数的栈帧被销毁,这个block的内存也会被清除,所以在函数结束后仍然需要这个block是,就必须用Block_copy()方法将它拷贝到堆上,这个方法的核心动作很简单:申请内存,将栈数据复制过去,将Class改一下,最后向捕获到的对象发送retain,增加block的引用计数,
之所以这样设计,实际上,可以认为,当block有了外部变量的捕获,那么他就需要持有这个外部变量,就是赋值到结构体成员上,这种捕获,造成了block对应struct结构体大小的动态变化,所以设计上适合放在栈上更合理
堆block
在栈block中,说到过,当函数栈帧销毁,那么栈block也会被随之清除,但是我们一般都需要在函数结束后仍然能使用block,所以需要把block拷贝到堆上,在copy时,就把栈block的类型转成了堆block
所以在mrc时代,block的属性关键字必须是copy,这样就可以保证在给block属性赋值的时候,能把栈上的block给copy都堆区
为什么要把block放到堆区才安全
block是个匿名函数,只不过我们给了一个变量来引用这个匿名函数,在需要的时候调用,但是栈block就会随着函数栈帧的销毁而销毁,这样一来,我们之前做引用的变量再去调用一块被销毁的内存,就会出现内存崩溃
所以只有把block放到由我们来控制生命周期的堆区中,才能安全的使用block
我们知道在oc中,对象都会在堆区存储,实际上,此时的堆block,他的确就是一个对象,而且你还需要对他手动release,当然在arc下,这就不同了
arc下的block
全局block
没有捕获任何外部变量,所以他是一个全局block,
堆block和栈block
只要一个block被赋值给一个strong变量,就会自动copy,那么就得到了堆block````
网友评论