iOS与OS X多线程和内存管理 - Blocks
前言
iOS与OS X多线程和内存管理 读书笔记(二) Blocks
笔记
Blocks概要
Blocks:带有自动变量(局部变量)的匿名函数
完整的Block语法与一般的C语言函数定义相比只有2点不同:
- 开头多了^符号
- 没有函数名
看看如何声明Block类型变量:
int (^blk)(int);
声明了一个参数类型为int,返回类型为int的Block变量blk。typedef int (^blk_t)(int);
那么blk_t代表一个Block变量Block会自动截获和保存变量值。但是不能更改保存的变量值,除非你使用__block。如下:
int val = 0; void (^blk)(void) = ^{val = 1}; // 更改自动保存的变量值 报错 blk(); /* 使用__block之后 */ __block int val = 0; void (^blk)(void) = ^{val = 1}; // 不报错 blk();
PS:如果是截获的OC对象,比如截获了NSMutableArray对象array,可以使用array:
[array addObject:obj];
;但是不能给array赋值:array = [[NSMutableArray alloc] init];
。若要给他赋值,还是得使用__block修饰符。PS:不会自动截获C语言数组:
char text[] = "hello";
;但是可以截获指针:char *text = "hallo";
。
Blocks的实现
Block其实就是Objective-C对象
来看看具体实现
int main() { void (^blk)(void) = ^{printf("Bolck.../n");}; blk(); return 0; } /* 经过编译器转化之后 */ /* 下面的结构体表示的是Block对象 */ struct __main_block_impl_0 { void *isa; // OC对象都有一个isa指向它的类,表明这个对象是一个什么类 /* 无关紧要的一些成员 */ int Flags; int Reserved; struct __main_block_desc_0 *Desc; // 一个结构体指针,后面会有定义,大概和系统有关 void *FuncPtr; // 函数指针,指向这个Block的匿名函数,也就是 {printf("Block.../n");} /* 结构体的初始化函数 */ __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) { // 传入的参数是匿名函数的函数指针,__main_block_desc_0结构体指针和flags isa = &_NSConcreteStackBlock; // 初始化isa Flags = flags; FuncPtr = fp; Desc = desc; } } /* 下面代码的结构体 就是上面Block结构体中的那个无关理解的结构体指针成员的定义 */ static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; /* 匿名函数的定义,传入的参数是Block结构体指针 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Block.../n"); } /* main函数的实现 */ int main { void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); // 初始化Block,其实也就是创建Block结构体 blk->FuncPtr(blk); // 执行Block,也就是执行Block结构体中的函数,传入结构体本身 return 0; }
上面这个编译的过程就是:
- 在main函数外面,先对Block做定义:Block是一个结构体,他有一个成员是匿名函数的函数指针;这个结构体的构造函数需要传入匿名函数的函数指针。
- 在main函数外面再定义匿名函数,匿名函数的参数是Block结构体,该参数在截获自动变量时会有用。
- 定义main函数,先新建一个Block结构体并初始化;在执行Block,也就是执行Block结构体里面的函数指针指向的函数;再return 0。
PS:结构体的初始化里面的
isa = &_NSConcreteStackBlock;
就是在将结构体当做OC对象在处理,他将OC对象的信息放在isa里面。看看如何截获自动变量
int main() { int dmy = 256; // 不会被截获的变量 int val = 10; // 被截获 const chat *fmt = "val = %d/n"; void (^blk)(void) = ^{printf(fmt, val);}; blk(); return 0; } /* 经过编译之后 */ /* Block结构体定义 */ struct __main_block_impl_0 { /* 如前所诉 */ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; /* 截获的自动变量,变成了结构体成员 */ const char *fmt; int val; /* 初始化函数,这里会使用截获的自动变量对成员进行赋值 */ __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) :fmt(_fmt), val(_val) { /* 如前所诉,就省略了 */ //... } } /* __main_block_desc_0结构体定义,同上,省略 */ /* 匿名函数定义 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char *fmt = __cself->fmt; int val = __cself->val; printf(fmt, val); } /* main函数实现 */ int main() { int dmy = 256; int val = 10; const char *fmt = "val = %d/n"; void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &main_block_desc_0_DATA, fmt, val); // 这里进行Block的初始化 blk->FuncPtr(blk); // 执行Block return 0; }
从上面可以看出,在初始化得时候,将自动变量传入了Block结构体的初始化函数,进行保存。所以之后对这些自动变量进行修改,Block结构体里面的这些保存在结构体成员里的变量是不受影响的。
__block的实现
__block变量也定义在main函数之外,不在只是一个简单的变量,而是变成了结构体:
struct __Block_byref_val_0 { void *isa; __Block_byref_val_0 *__forwarding; // 指向自己的指针 int __flags; int __size; int val; // 原来的自动变量 } /* Block里面含有__block变量的结构体指针 */ struct __main_block_impl_0 { /* 同上的成员省略 */ __Block_byref_val_0 *val; // 指向val结构体的指针 /* 结构体初始化函数,使用val结构体的__forwarding成员变量初始化Block结构体的val指针 */ __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags = 0) : val(_val->forwarding) { /* 同上 省略 */ } } /* 匿名函数 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; val->forwarding->val = 1; // 在这里 val和val->forwarding其实都指的是自己 } /* main函数里面 */ int main() { int val = 10; void (^blk)(void) = ^{val = 1;}; } /* 转化之后 val变成结构体 */ int main() { __Block_byref_val_0 val = { 0, &val, // 指向自身的指针 0, sizeof(__Block_byref_val_0), 10 // 初始化 }; blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val); // 初始化Block结构体时将val结构体地址传过去 }
将val变量变成存放在栈上的结构体,这也就是__block变量
Block存储域
- _NSConcreteStackBlock将Block存放在栈上
- _NSConcreteGlobalBlock将Block存放在数据区域
- _NSConcreteMallocBlock将Block存放在堆上
存放在数据区域的Block相当于全局变量,不能截获自动变量,全局只有一个实例。所有不需要截获自动变量的Block都是这种类型的。
其他的Block都现在栈上生成,但是当超过这个Block的作用域,但是又要使用该Block时,就会将Block从栈上复制到堆上。例如下面的例子:
typedef int (^blk_t)(int); blk_t func(int rate) { return ^(int count)(return rate *count); }
这里Block在函数内生成后,返回时就已经离开Block的作用域了。这时候就需要将他copy到堆上
blkt func(int rate) { blkt tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate); tmp = _Block_copy(tmp); // 将栈上的Block复制到堆上 return objc_autoreleaseReturnValue(tmp); // 将tmp注册到NSAutoreleasePool,说明Block也是OC对象 }
出了函数之后,栈上的Block就会被释放,这个时候只能使用在堆上的Block了。很多情况下编译器会帮助我们自动复制的,但是NSArray得
initWithObjects
方法就不会自动复制:- (id)getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects: ^{NSLog(@"haha");}, ^{NSLog(@"yaya");}, nil]; } // 这种情况下返回的NSArray,如果去获取NSArray中的Block对象,是会出错的,因为这些Block在出函数时被废弃了, 这时就需要我们手动复制了,也就是调用copy方法 - (id)getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects: [^{NSLog(@"haha");} copy], [^{NSLog(@"yaya");} copy], nil]; }
PS:当Block被复制到堆上时,它 使用 的__block变量也会从栈上被复制到堆上,这个时候该变量会被Block 持有。如果还有在栈上的其他Block使用block变量的话,block变量还会保留在栈上,但是他的 forwarding指针已经指向堆上的block变量了,所以改变block变量的话,始终改变的都是同一个结构体。
看看如何截获OC对象
{ id array = [[NSMutableArray alloc] init]; blk = [^(id obj) { [array addObject:obj]; } copy]; } /* 注意 可以理解成 Block里面的array结构体成员强引用NSMutableArray对象 */ struct __main_block_impl_0 { id __strong array; // 强引用 }
但实际上结构体对__strong是没有概念的,不过此时的结构体会手动加上ARC的概念上去。当Block从栈上复制到堆上时,OC对象被Block持有;当Block从堆上废弃时,OC对象被释放。所以,很重要的一点:只有Block被复制到堆上时,OC对象,才能被Block持有;也就是要调用Block的copy函数。
__block类型的OC对象
__block id obj = [[NSObject alloc] init]; /* 编译之后 */ struct __Block_byref_obj_0 { void *isa; __Block_byref_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); // 用来使Block持有对象 void (*__Block_byref_id_object_dispose)(void*); // 用来使Block释放对象 __strong id obj; }
所以__block类型的OC对象也是讲OC对象封装成一个结构体,和之前的block类型的自动变量一样,只不过添加了retain和release的概念而已。
PS:如果对象是weak类型的,和上一篇里面分析weak和strong的一样。Block也只是含有一个指向对象的指针而已,如果该指针为weak,那么该置成nil,还是要置成nil。
看看使用Block的一个常见问题:循环引用
最常见的就是在Block中使用self,而这个Block还是self对象的一个变量。那么对象持有Block,Block又持有对象。循环引用。所以在Block中使用self经常需要使用weakself。