block概述
《Objective-c高级编程》中描述block是带有自动变量(局部变量)的匿名函数。
我的理解就是可以直接在一段代码中插入一个函数,而不用显示的去掉用这个函数。
block基本语法
^返回值 参数列表{};
// 返回值是整数,两个参数
^int(int i,int j){
return i+j;
};
// 无返回值,参数两个
^(int i,int j){
};
// 无参数,无返回值
^{};
将block作为变量(block变量)
blick可以作为变量,与一般OC(或者说C)的变量完全一样,可以作为:自动变量(局部变量)、静态变量、全局变量、函数参数、静态全局变量。
语法:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int (^block)(int) = ^(int i){return i;};
block(1);
}
return 0;
}
block截取自动变量
《Objective-c高级编程》中描述block是带有自动变量(局部变量)的匿名函数。这个自动变量就表现在block对在他之前声明的自动变量的值的获取,这里的获取,需要 特别的注意,获取的是自动变量的瞬间值,block保存了自动变量的值,在block执行之后,即使改变了block中使用的自动变量的值,也不会影响block执行时自动变量的值。
举一个例子:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int i = 1;
int j = 1;
void (^block)(void) = ^{NSLog(@"%d",i+j);};
block();
i = 10;
block();
}
return 0;
}
输出结果是:
2017-08-21 17:21:45.170390+0800 DemoBlock[2638:1592472] 2
2017-08-21 17:21:45.171136+0800 DemoBlock[2638:1592472] 2
Program ended with exit code: 0
我们可以看到,在第二次输出的依然是2,而不是11,也就说明了block捕获的是瞬间值。
__block修饰符
从上面一个例子知道,我们可以在block中截取自动变量的值,但是这个是只读的,我们不能修改block以外的自动变量的值,假如我们想要在block中修改上个例子中的i的值, 我们就要在i前面加一个__block修饰符。
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int i = 1;
void (^block)(void) = ^{i=2;};
block();
NSLog(@"在block中修改自动变量的值---%d",i);
}
return 0;
}
输出结果:
2017-08-21 19:36:34.321111+0800 DemoBlock[3090:1927645] 在block中修改自动变量的值---2
Program ended with exit code: 0
block的实现
我们通过clang -rewrite-objc将源代码转化成C++语言,看看block的底层是怎么实现的。
原OC代码是:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^blk) (void) = ^{};
blk();
}
return 0;
}
转换成C++代码(部分)如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
我们来分析一下这部分转化的代码,如下图:
经过编译,我们知道block其实就是一个结构体,从上图可以看出:
main函数的参数__cself 指向 __main_block_impl_0:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
impl_0表示是main函数中的第1个block。这个结构体包含两个部分:
(1)结构题:__block_impl;
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
isa指针指向该对象对应的类,也就是block,我们可以看出其实block是一个OC对象(在runtime中对象和类都是用结构体表示)。
flags标志位
reserved 保留变量
FuncPtr block执行时调用的函数指针。
(2)Desc指针。
Desc指向的结构体__main_block_desc_0
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
包含两个部分;
(1)reseverd 版本升级需要的区域
(2)block的大小
同时,还创建了__main_block_desc_0_DATA,在__main_block_impl_0初始化时使用。
接下来我们重点来看看__main_block_impl_0的构造函数:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
首先,我们来看看main函数中是怎么对这个构造函数进行调用的:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))这一句就是对__main_block_impl_0构造函数 的调用。
第一个参数__main_block_func_0是转换成C语言的函数指针,第二个参数是初始化__main_block_impl_0结构体实例的结构体,和__main_block_impl_0的大小有关。
我们再回到__main_block_impl_0的狗在函数,这个构造函数初始化了四个变量:
isa指针指向一个_NSConcreteStackBlock;
FuncPtr指向__main_block_func_0;
desc指向__main_block_desc_0创建时的__main_block_desc_0_DATA,主要是用于初始化__main_block_impl_0的大小。
总的来说,我自己理解是两句话;
(1)block的实质是一个结构体,这个结构体包含了定义block时代码块的函数指针(FuncPtr或者说_cself)和block外部上下文变量等信息
(2)block是一个OC对象,这点从
block截获自动变量的底层实现
先来看一段代码:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int i = 0;
int j = 1;
^{i;};
}
return 0;
}
经过编辑成C++之后的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 重点来了,block结构体多了一个成员变量
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
i;}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int i = 0;
int j = 1;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
}
return 0;
}
我们可以发现在stack对应的结构体__main_stack_impl_0中多了一个成员变量i,这个i正是在main函数中,block截取的自动变量。 那么,还有一个变量j却没有被截取,因为在block中并没有使用这个变量。
因此,我们可以下一个结论:block对于自动变量的截取,是在block对应的结构体中增加一个成员变量,并且只有在block中只用的自动变量才会被截取。 同时,结构体中的成员变量i只是进行了值传递,也就是只可以读,不可以写,否则会报错。
block截取全局静态变量
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
static int i = 0;
int main(int argc, const char * argv[]) {
@autoreleasepool {
^{i =10;};
}
return 0;
}
转换后:
static int i = 0;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
i =10;}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}
return 0;
}
我们可以看到,对于全局静态变量的截取,block对应的机构体并没有增加关于i的成员变量。
因为全局静态变量存储在静态数据区,这个区域的声明周期是和程序一样长的,只要程序还在运行,就能保证block访问到这一内存区域,所以不用在结构体中为这个变量保留位置。
block对于全局变量的截取
对于全局变量,我们可以根据全局静态变量进行猜想:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int i = 0;
int main(int argc, const char * argv[]) {
@autoreleasepool {
^{i =10;};
}
return 0;
}
转换后:
int i = 0;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
i =10;}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}
return 0;
}
这和我们的猜想一致,在结构体中并没有增加对于全局变量i的成员变量,这也是因为全局变量存储在全局静态数据区,这个区域保证我们在程序生命周期内,block都可以进行变量的访问。
block截取局部静态变量
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int i = 0;
^{i =10;};
}
return 0;
}
转换后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//多了一个指向静态变量内存的指针
int *i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *i = __cself->i; // bound by copy
// 通过指针进行修改
(*i) =10;}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int i = 0;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i));
}
return 0;
}
局部静态变量的作用域只在定义它的函数内部,而它存储在静态数据区,和程序拥有一样的生命周期,在整个程序运行期间,都可以访问到这个内存区域,所以在block内部,就只能通过他的地址来进行访问。
__block修饰符的底层实现
通过前面的例子我们知道,对于全局变量、静态全局变量、静态局部变量(通过地址访问),我们可以直接进行访问,但是对于自动变量,直接访问是会出错的,这是因为局部变量在block对应的结构体中只是进行了值传递。
那么这个时候,我们就要使用__block修饰符修饰自动变量了。 接下来看例子:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int i = 0;
^{i =10;};
}
return 0;
}
转换后的代码:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) =10;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
}
return 0;
}
由以上代码我们发现,自动变量居然被包装成了一个结构体,并且还有isa指针,也就是一个对象。block对应的结构体通过指向自动变量对应的结构体的指针访问结构体,同时自动变量对应的结构体通过内部的一个指向自己的指针(__forwarding)访问自动变量。当block被拷贝到堆上时,block的考培辅助函数会将自动变量对应的结构体也拷贝一份到堆区。因此即使栈上的局部变量对应的结构体被销毁,block任然可以正常访问堆区的。前面所说的__forwarding指针就是指向堆区的指针。
三种block(block在内存中的存储区域)
我们前面所说的都是_NSConcreteStackBlock,他在栈上分配内存,另外还有两类;
_NSConcreteGlobalBlock:在全局静态区,分配内存
_NSConcreteMallocBlock:在堆上分配内存
首先来说最简单的_NSConCreteGlobalStack,全局block。
先看下面的代码:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
void (^globalBlock)() = ^{
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
转换成C++之后:
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __globalBlock_block_func_0(struct __globalBlock_block_impl_0 *__cself) {
}
static struct __globalBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
} __globalBlock_block_desc_0_DATA = { 0, sizeof(struct __globalBlock_block_impl_0)};
static __globalBlock_block_impl_0 __global_globalBlock_block_impl_0((void *)__globalBlock_block_func_0, &__globalBlock_block_desc_0_DATA);
void (*globalBlock)() = ((void (*)())&__global_globalBlock_block_impl_0);
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
我们可以看到这个block的类型就是_NSConcreteGlobalBlock。
除了我声明一个全局block,还有另一种情况局部block会自动转化成全局block,存储地址在静态数据区。这种情况就是函数内的block不截获自动变量,就会将block用结构体实例设置在程序的数据区。虽然这个时候我们用clang转化成C++,依然显示的是_NSConcreteStackBlock。
那么总结如下:
(1)在全局区声明block
(2)在函数内部,使用block,不截获局部变量
这两种情况下会在全局数据区创建全局block对应的结构体实例。
除了以上两种情况block语法生成的block为_NSConcreteStackBlock类对象。
接下来,看看_NSConcreteStackBlock:
对于_NSConcreteStackBlock的创建没什么好说的,只要在函数内部,使用了block语法,就会在栈去创建这个block的结构体实例。
但是我们要注意_NSConcreteStackBlock作为返回值时,在ARC有效的情况下,编译器会自动将栈上的block复制到堆上。
在绝大多数情况下,编译器都会自动的将栈上的block复制到堆上。
在向方法或者函数的参数中传递block时,需要手动调用copy函数。
然而下面两种情况下,不用手动的调用copy方法:
(1)cocoa框架中的方法,并且方法名中含有usingBlock;
(2)GCD的API
最后来看看_NSConcreteGlobalBlock,_NSConcreteGlobalBlock是不能直接创建的,必须由_NSConcreteStackBlock从栈上拷贝到堆上,这个过程会调用_block_copy_internal函数。
需要拷贝的情况,我们在上面已经说了,再次总结:
(1)block作为函数的返回值时;(自动复制)
(2)block作为函数的参数传递;(手动调用copy方法)(其中两种特殊情况不需要复制到堆上1.GCD的API;2.cocoa的方法并且方法名中包含usingBLock)
那么,如果我们对全局、栈、堆上的block发送copy方法会怎样呢?
__block修饰的变量的存储区域
上面我们说了三种block的存储区域,以及他们之间的转化,那么被__block修饰的变量的存储区域怎么变化呢?
就一句话,在Block中使用了__block修饰的变量,当Block从栈上拷贝到堆上时,会一并从栈拷贝到堆上。并且,这个Block持有__block修饰的变量.
在多个Block中使用__block修饰的变量时,当第一个Block被复制到堆上时,会一并复制__block修饰的变量,当其他BLock复制到堆上时,被复制的Block持有__block变量,并且__block引用计数增加。
当被__block修饰的变量复制到堆后,__forwarding指针如下图所示:
正是因为,栈上和堆上的结构体的_forwarding指针都指向堆上的结构体,所以,无论是在Block语法中、语法外只用__block修饰变量,也无论这个变量是在栈上还是堆上,都可以顺利的访问__blcok变量。
Block截取对象
前面我们说了Block截获的都是普通变量,还未截取对象,block截取对象和普通变量有所区别。
对于ID类型的对象的截取,在Block对应的结构体中,会增加一个该对象的成员变量,并用__strong修饰,这样一来,就可以在超出ID类型对象的声明周期外,成功的调用的这个对象。看下面的代码:
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
id array = [[NSMutableArray alloc]init];
[^{
[array add:nil];
}copy];
}
return 0;
}
转换成C++代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
id array = __cself->array; // bound by copy
((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)array, sel_registerName("add:"), __null);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}
return 0;
}
我们可以看到在Block对应的结构体struct __main_block_impl_0中,增加了一个成员变量id array;默认修饰符是__strong。
OC的运行时库可以准确的掌握把Block从栈上赋值到堆上的时机,因此即使结构体中含有强引用或者弱引用的成员变量,都可以正确的初始化和废弃。
因此,我们看到上面的代码增加了两个函数:一个__main_block_copy_0和一个__main_block_dispose_0函数。
main_block_main_0函数调用_Blcok_object_assign函数,将ID类型的变量赋值给结构体中的成员变量,并持有该对象。
实际上assign函数相当于调用ID对象的retain函数,增加引用数量。
同时,还有一个__main_block_dispose_0函数,这个函数调用__Block_object_dispose函数,相当于对ID对象进行release操作。
copy函数会在栈上的Block被复制到堆上的时候进行调用;
dispose函数会在堆上的Block销毁时调用。
__blcok 修饰对象
有__block 修饰的对象,对象会转化成struct __Block_byref_array_0,并且相较于普通变量,对象的结构体增加了两个函数void(__Block_byref_id_object_copy)(void, void) 和 void (__Block_byref_id_object_dispose)(void*);这两个函数就是用于对象的内存管理。对对象进行retain或者release
//
// main.m
// DemoBlock
//
// Created by DH on 2017/8/21.
// Copyright © 2017年 GreedyCat. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
id __block array = [[NSMutableArray alloc]init];
[^{
[array add:nil];
}copy];
}
return 0;
}
转化后的代码:
struct __Block_byref_array_0 {
void *__isa;
__Block_byref_array_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id array;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_array_0 *array; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_array_0 *array = __cself->array; // bound by ref
((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)(array->__forwarding->array), sel_registerName("add:"), __null);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"))};
((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344)), sel_registerName("copy"));
}
return 0;
}
Block循环引用
在ARC模式下,在栈间传递Block,不需要手动调用copy函数,因为ARC会自动的将栈中的Block进行copy,将_NSConcreteStackBlock类型的block转换成了_NSConcreteMallocBlock的block。
在Block循环引用中,要特别注意self隐式引用,例如:
@implementation Person
{
int _a;
void (^_block)();
}
- (void)test
{
void (^_block)() = ^{
_a = 10;
};
}
@end
这一段代码转换成C++:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
(*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
}
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
static void _I_Person_test(Person * self, SEL _cmd) {
void (*_block)() = (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344);
}
注意观察struct __Person__test_block_impl_0结构体内,强引用了Person *self;
这就会造成循环引用:Person对象持有Block,而Block转化的结构体中右持有self,也就是Person的对象。要解决这个问题,只需要:
弱引用self,也就是
@implementation Person
{
int _a;
void (^_block)();
}
- (void)test
{
void (^_block)() = ^{
id__weak weakSelf = self;
weakSelf._a = 10;
};
}