block的几种类型:
https://www.jianshu.com/p/6568f245deb2
- NSGlobalBlock
block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。 - NSStackBlock
block 内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。MRC 环境下,[[mutableAarry addObject: blockA],(在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在其所在作用域结束也就是函数出栈后,从mutableAarry中取到的blockA已经被回收,变成了野指针。正确的做法是先将blockA copy到堆上,然后加入数组。支持copy,copy之后生成新的NSMallocBlock类型对象。 - NSMallocBlock
如上例中的_block
,[blockA copy]
操作后变量类型变为 NSMallocBlock,支持retain、release,虽然 retainCount 始终是 1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放(可多次retain、release 操作验证)。copy之后不会生成新的对象,只是增加了一次引用,类似retain,尽量不要对Block使用retain操作。
block的实现原理:
OC代码:
int a=10;
void (^blk)(void) = ^(){
printf("This is a block.");
};
上面的OC代码被转化成了C语言的代码后,可以看出:block被转化成了指向__main_block_impl_0结构体的指针。这个结构体包含两个__block_impl和__main_block_desc_0两个结构体,和一个方法。
struct__main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
//下面这个是结构体构造函数 ,用来初始化变量__block_impl impl
和__main_block_desc_0
__main_block_impl_0(void *fp,struct __main_block_desc_0 *desc,int _a, int flags=0):a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
OC被转化成了以下C语言代码:
int a=10;
void (*blk)(void) =(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA,a);
简化的C语言代码:
void (*blk)(void) = &__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
);
//__main_block_impl_0 函数 参数:参数1 __main_block_func_0指针参数2 __main_block_desc_0_DATA :描述结构体大小 参数3 就是block之外的变量 a
//__main_block_func_0函数,即block对应的函数体。该函数接受一个__cself参数,即对应的block自身。
static void__main_block_func_0(struct __main_block_impl_0 *__cself) {
int a=__cself->a;
printf("This is a block.");
}
struct __block_impl {
void *isa;//isa指针,如果我们对runtime了解的话,就明白isa指向Class的指针 (_NSConcreteStackBlock,_NSConcreteMallocBlock和_NSConcreteGlobalBlock)
int Flags;//Flags,当block被copy时,应该执行的操作
int Reserved;//Reserved为保留字段
void *FuncPtr;//FuncPtr指针,指向block内的函数实现
};
staticstruct __main_block_desc_0 {
size_t reserved;//reserved为保留字段默认为0
size_t Block_size;//Block_size为sizeof(struct __main_block_impl_0)
,用来表示block所占内存大小。因为没有持有变量,block大小为impl的大小加上Desc指针大小
} __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0)};
//__main_block_desc_0_DATA
为__main_block_desc_0的一个结构体实例
这个结构体,用来描述block的大小等信息。如果持有可修改的捕获变量时(即加__block
),会增加两个函数(copy和dispose)
OC代码:
blk();
转成C语言:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
简化的C语言代码:
blk->FuncPtr(blk);block的创建和调用,可以看出执行block就是调用一个以block自身作为参数的函数,这个函数对应着block的执行体。
//如果一个block外部的变量加了 __block修饰系统就会创建一个结构体内部封装了变量的地址
__blockint a = 10;
//结构体__Block_byref_a_0的变量
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;//保存自己的地址,____forwarding,指向自己的指针,当从栈copy到堆时,指向堆上的block
int __flags;// 0
int __size; //当前结构体的大小
int a;
};
__attribute__((__blocks__(byref)))__Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0, sizeof(__Block_byref_a_0),10};
block的类型用_NSConcreteStackBlock来表示,表明这个block位于栈中。同样地,还有_NSConcreteMallocBlock和_NSConcreteGlobalBlock。
由于block也是NSObject,我们可以对其进行retain操作。不过在将block作为回调函数传递给底层框架时,底层框架需要对其copy一份。比方说,如果将回调block作为属性,不能用retain,而要用copy。我们通常会将block写在栈中,而需要回调时,往往回调block已经不在栈中了,使用copy属性可以将block放到堆中。或者使用Block_copy()和Block_release()。
原理解析:
参考原理:https://www.cnblogs.com/dahe007/p/6067591.html主
https://blog.youkuaiyun.com/wangyanchang21/article/details/79785274
block的本质就是指向结构体的指针。其中有变量isa指针(每个对象中都有一个isa指针);funcptr指针(指向block申明的函数),flag说(block copy的时候会用到)。block创建的时候,入口就是一个结构体,结构体中有一个block类型的变量,还有一个desc描述结构体,还有一个对block结构体中变量赋值的函数。当调用block的时候,实际上就是运行时动态的给对象发送消息执行相应的方法。根据block中的isa找到对象和方法地址,在方法区执行对应的block的实现函数。
当访问局部变量的时候,block入口mainblockimpl结构体中会多一个访问的变量。会把mianlockiml作为参数在block的实现函数中传入,并把外部的变量赋值给block实现函数内部。block会在以下几个时机进行copy的操作。作为参数,属性强引用 返回值 gcd
__block的作用:将值传递变成了指针引用传递。给外部变量加了__block后,会吧变量包装成一个blockbrfnum的结构体(这个结构体中有isa指针,外部变量,标志位flags,结构体大小size,还有一个指向拷贝到堆上的指针blockbrefnum *,也就是通过这个指针变量传递给block)。block内部修改了值后,外部也改变。block中多了一个block_bref-的指针(在block中就是一个结构体blockbrefnum),这个指针的作用是指向copy到堆上的指针。入口中的局部变量变成了指针,在block的实现函数中通过指针传给了block,(block的执行函数mainblockfun中 通过block_breyf的forwarding来修改外部变量。)
block会保存代码,在适当的时机调用。
block的三种类型:全局的block:不吊用外部变量或调用全局变量属性。
栈block,函数作用域结束或函数返回,销毁,通过copy到堆上保存。
堆block由开发人员自己控制,引用计数为0时销毁。
mainblockfun是block要执行的函数.
mainblockdesc是block描述的结构体.
blockiml是block的结构体.
mainblockimpl是入口函数.
mainblockcopy从栈上拷贝到堆上调用;
mainblockdispose从堆内释放的时候调用: