如下环境都在ARC环境下,常规设置,使用XCode10测试。html
Block做为属性声明时为何都声明为Copy?git
Block为何能保存外部变量?程序员
Block中__block
关键字为什么能同步Block外部和内部的值?github
Block有几种类型?objective-c
何时栈上的Block会复制到堆?编程
Block的循环引用应该如何处理?api
Block外部__weak typeof(self) weakSelf = self;
Block 内部 typeof(weakSelf) strongSelf = weakSelf;
,为何须要这样操做?数组
如下Block在ARC环境下能正常运行吗?若能分别打印什么值?闭包
void exampleA_addBlockToArray(NSMutableArray*array) {
char b = 'A';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleA() {
NSLog(@"---------- exampleA ---------- \n");
NSMutableArray *array = [NSMutableArray array];
exampleA_addBlockToArray(array);
void(^block)(void) = [array objectAtIndex:0];
block();
}
复制代码
void exampleB_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("B\n");
}];
}
void exampleB() {
NSLog(@"---------- exampleB ---------- \n");
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void(^block)(void) = [array objectAtIndex:0];
block();
}
复制代码
typedef void(^cBlock)(void);
cBlock exampleC_getBlock() {
char d = 'C';
return^{
printf("%c\n", d);
};
}
void exampleC() {
NSLog(@"---------- exampleC ---------- \n");
cBlock blk_c = exampleC_getBlock();
blk_c();
}
复制代码
NSArray* exampleD_getBlockArray() {
int val = 10;
return [[NSArray alloc] initWithObjects:^{NSLog(@"blk1:%d",val);}, ^{NSLog(@"blk0:%d",val);}, ^{NSLog(@"blk0:%d",val);}, nil];
}
void exampleD() {
NSLog(@"---------- exampleD ---------- \n");
typedef void (^blk_t)(void);
NSArray *array = exampleD_getBlockArray();
NSLog(@"array count = %ld", [array count]);
blk_t blk = (blk_t)[array objectAtIndex:1];
blk();
}
复制代码
NSArray* exampleE_getBlockArray() {
int val = 10;
NSMutableArray *mutableArray = [NSMutableArray new];
[mutableArray addObject:^{NSLog(@"blk0:%d",val);}];
[mutableArray addObject:^{NSLog(@"blk1:%d",val);}];
[mutableArray addObject:^{NSLog(@"blk2:%d",val);}];
return mutableArray;
}
void exampleE() {
NSLog(@"---------- exampleE ---------- \n");
typedef void (^blk_t)(void);
NSArray *array = exampleE_getBlockArray();
NSLog(@"array count = %ld", [array count]);
blk_t blk = (blk_t)[array objectAtIndex:1];
blk();
}
复制代码
void exampleF() {
NSLog(@"---------- exampleF ---------- \n");
typedef void (^blk_f)(id obj);
__unsafe_unretained blk_f blk;
{
id array = [[NSMutableArray alloc] init];
blk = ^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
};
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
复制代码
void exampleG() {
NSLog(@"---------- exampleG ---------- \n");
typedef void (^blk_f)(id obj);
blk_f blk;
{
id array = [[NSMutableArray alloc] init];
blk = ^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
};
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
复制代码
void exampleH() {
NSLog(@"---------- exampleH ---------- \n");
typedef void (^blk_f)(id obj);
blk_f blk;
{
id array = [[NSMutableArray alloc] init];
id __weak weakArray = array;
blk = ^(id obj) {
[weakArray addObject:obj];
NSLog(@"array count = %ld", [weakArray count]);
};
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
复制代码
void exampleI() {
NSLog(@"---------- exampleI ---------- \n");
typedef void (^blk_g)(id obj);
blk_g blk;
{
id array = [[NSMutableArray alloc] init];
__block id __weak blockWeakArray = array;
blk = [^(id obj) {
[blockWeakArray addObject:obj];
NSLog(@"array count = %ld", [blockWeakArray count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
复制代码
Objective-C中的Block中文名闭包,是C语言的扩充功能,是一个匿名函数而且能够截获(保存)局部变量。经过三个小节来解释这个概念。app
程序语言 | Block的名称 |
---|---|
Swift | Closures |
Smalltalk | Block |
Ruby | Block |
LISP | Lambda |
Python | Lambda |
Javascript | Anonymous function |
由于Block是在模仿C语言函数指针的写法:
int func(int count) {
return count + 1;
}
// int (^tmpBlock)(int i) = ...
int (*funcptr)(int) = &func;
复制代码
可是Block的写法依旧很是难记,国外的朋友更是专门写了一个叫fuckingblock网页提供Block的各类写法。
// 演示截取局部变量
int tmpVal = 10;
void (^blk)(void) = ^{
printf("val = %d", tmpVal); // val = 10
};
tmpVal = 2;
blk();
复制代码
这里依旧显示val = 10
,Block会截取当前状态下val
的值。至于为何能截获局部变量的值,咱们下一节中讨论。
经过clang -rewrite-objc main.m
将上面的示例代码翻译成C,关键代码以下:
// Block基础结构
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
复制代码
// 根据示例中blk的实现,生成不一样的 __main_block_impl_0 结构体。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int tmpVal;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tmpVal, int flags=0) : tmpVal(_tmpVal) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
根据上面的代码能解决咱们3个疑惑:
__block_impl
中有isa
指针,那么Block
也是一个对象。__main_block_impl_0
,这里结构里面包含int tmpVal
就是咱们局部变量,而__main_block_impl_0
的构造函数中是值传递。因此block内部截获的变量不受外部影响。__main_block_impl_0
构造函数中有个void *fp
函数指针指向的就是block实现。咱们向上面示例代码再添加多一些变量类型:
static int outTmpVal = 30; // 静态全局变量
int main(int argc, char * argv[]) {
int tmpVal = 10; // 局部变量
static int localTmpVal = 20; // 局部静态变量
NSMutableArray *localMutArray = [NSMutableArray new]; // 局部OC对象
void (^blk)(void) = ^{
printf("val = %d\n", tmpVal); // val = 10
printf("localTmpVal = %d\n", localTmpVal); // localTmpVal = 21
printf("outTmpVal = %d\n", outTmpVal); // outTmpVal = 31
[localMutArray addObject:@"newObj"];
printf("localMutArray.count = %d\n", (int)localMutArray.count); // localMutArray.count = 2
};
tmpVal = 2;
localTmpVal = 21;
outTmpVal = 31;
[localMutArray addObject:@"startObj"];
blk();
}
复制代码
对应输出结果为:
val = 10
localTmpVal = 21
outTmpVal = 31
localMutArray.count = 2
clang -rewrite-objc main.m
后关键代码以下:
static int outTmpVal = 30;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int tmpVal;
int *localTmpVal;
NSMutableArray *localMutArray;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tmpVal, int *_localTmpVal, NSMutableArray *_localMutArray, int flags=0) : tmpVal(_tmpVal), localTmpVal(_localTmpVal), localMutArray(_localMutArray) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
由于涉及到OC对象,这里还会有2个新的方法,这2个方法会放到后面讲:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->localMutArray, (void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src){
_Block_object_dispose((void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
复制代码
static int outTmpVal = 30;
储存在内存中的.data
段,static
限制了做用域,该文件做用域内可修改。static int localTmpVal = 20;
在int main(int argc, char * argv[]) { }
做用域可修改,注意__main_block_impl_0
构造函数中是传递的*_localTmpVal
指针,因此外部修改Block内部一样有效,由于是static
因此,Block内部也能够修改localTmpVal
的值。NSMutableArray *localMutArray
向__main_block_impl_0
传递的是指向的地址,因此localMutArray
内部操做对于block内一样有效。
- 静态变量的这种方式一样也能够做用到局部变量上,传递一个指针到block内,经过指针来读取指向的值,通知也能够修改。可是这种方式在block离开局部变量所在做用域后再调用就会出现问题,由于局部变量已经被释放。
static int localTmpVal = 20;
能经过指针的方式修改值,NSMutableArray *localMutArray
修改指向的值为何不能够? 这是clang对于Block内修改指针的一个保护措施。
总结下:
静态变量
、 静态全局变量
、全局变量
均可以访问,修改,保持同一份值。一样的方式,咱们先看__block
用C是怎么实现的,下面是一段使用__block
的代码:
int main(int argc, char * argv[]) {
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
printf("val = %d", val);
};
blk();
}
复制代码
翻译成C,只保留关键代码:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
复制代码
这就是__block
对应C中的新结构体:
*__forwarding
是一个与本身同类型的指针。int val;
这个变量就是为了保存本来__block int val = 10;
的值。__block int val = 10;
对应的结构体__Block_byref_val_0
也是和以前同样建立在栈上的。接下来继续看,blk
的结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0.... // 和以前的__block_impl构造方式一致
};
复制代码
blk
结构内部新增了__Block_byref_val_0 *val
做为成员变量,和以前原理一致。
blk
的实现val = 1;
:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
printf("val = %d", (val->__forwarding->val));
}
复制代码
(val->__forwarding->val) = 1;
这句很是重要,不是直接经过val->val
进行赋值操做,而是通过__forwarding
指针进行赋值,这带来很是大的灵活性,如今是blk
和__block int val
都是在栈上,__forwarding
也都指向了栈上的__Block_byref_val_0
。以上代码解决了在Block内修改外部局部变量的值。
__block
新增了2个方法:__main_block_copy_0
和__main_block_dispose_0
:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign(&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
复制代码
经过方法命名和参数,能够大体猜出是对Block
的拷贝和释放。
经过以上clange
的编译,Block和__block都是有isa指针的,二者都应该是Objective-C的对象。isa指向的就是它的类对象。在ARC下大体有如下几种,根据名字能够知道对应储存空间:
clang转出的结果和运行代码时 Block 实际显示的isa类型是不同的,在实际的编译过程当中已经不会通过clang翻译成C再编译。
_NSConcreteGloalBlock
有两种状况下能够生成:
_NSConcreteStackBlock
由于在栈上,在函数做用域内声明的Block。
_NSConcreteMallocBlock
正由于_NSConcreteStackBlock
的做用域在栈上,超出做用域后想要继续使用Block,这就得复制到堆上。那些状况会触发这种复制:
block
赋值给Strong
修饰的属性时。Block
做为一个返回值时(超出做用域还能使用,autorelease处理对象生命周期)。copy
。usingBlock
等时,不用外部copy
。内部已经进行copy。GCD
的Api,也不用外部copy
。这里有个比较经典的例子(摘自《Objective-C高级编程》):
- (id)getBlockArray {
int val = 10;
return [[NSArray alloc] initWithObjects:^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);}, nil];
}
{
id obj = [self getBlockArray];
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
}
// crash
复制代码
在ARC状况下,NSArray 数组类会有2个元素,第一个在堆上,第二个栈上。在超出getBlockArray做用域后,第二栈上的block会变成野指针。在全部做用域结束时,Array会释放数组内全部元素。野指针对象执行销毁时会触发崩溃。 正常状况下
NSArray
应该持有数组内全部元素。但使用initWithObjects:
方法时,发现只有第一个元素进行了持有操做,第二个Block
依旧在栈上。当我使用NSMutableArray
的addObject:
方法时,每一个Block都会进行持有赋值到堆上。我怀疑应该是initWithObjects:
方法中多参形式比较特殊。
反复提到Block就是OC的对象,对于对象Copy会带来哪些变化:
Block类 | 原来储存域 | 复制产生的影响 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | .data | 无变化 |
_NSConcreteMallocBlock | 堆 | 引用计数增长 |
Block是一个OC对象,因此涉及到从栈到堆,引用计数的变动等,常见OC对象内存管理的问题。同时Block在堆上时又会对__block
进行持有,那么对于 __block
一样也是OC对象,内存管理有什么区别呢?
Block从栈复制到堆时对__block变量产生的影响:
__block 存储域 | Block 从栈复制到堆时对__block的影响 |
---|---|
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
__block
从栈上复制到堆上后,本来栈上的__block
依旧会存在,被复制到堆上的__block
会被Block持有__block
的引用计数会增长,栈上的__block
会由于做用域结束而释放,堆上的__block
会在引用计数归零后释放。__block
的内存管理就是OC对象的引用计数管理方式,没有被其余Block持有时引用计数归0后释放。上面提到当__block
从栈上复制到堆上,会有两个__block
产生,一个栈上的一个堆上的。这两个不一样储存区域的__block
是如何实现数据同步的?
这就利用__block关键字如何实现?中提到的指向本身的*__forwarding
,当持有__block
的Block没有从栈上拷贝到堆上时:*__forwarding
指向栈上的__block
, 当持有__block
的Block拷贝到堆上时后,栈上的__block
->__forwarding
->堆上的__block
,堆上的__block
->__forwarding
->堆上的__block
。读起来有点绕,借用《Objective-C高级编程》中的插图:
上面讲了Block
和__block
在从栈上复制到堆上时的一些变化。为了解决__block
和OC对象
在Block结构体
内的生命周期问题,新增了一下几个方法:
__main_block_desc_0
中新加2个成员方法:copy
和dispose
,这是两个函数指针,指向的分别就是__main_block_copy_0
和__main_block_dispose_0
。Block
中使用OC对象
和__block
关键字时新增的2个方法:__main_block_copy_0
和 __main_block_dispose_0
,这两个方法用于在Block
被 copy
到堆上时,管理__block
和OC对象
的生命周期。Block:
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*);
}
复制代码
OC对象:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->localMutArray, (void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
复制代码
__block:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign(&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
复制代码
捕获OC对象
和使用__block
变量时在参数上会不一样:
OC对象 | BLOCK_FIELD_IS_OBJECT |
---|---|
__block | BLOCK_FIELD_IS_BYREF |
_Block_object_assign
就至关于retain
方法,_Block_object_dispose
就至关于release
方法,可是咱们在clang翻译的C语言中并无发现 __main_block_copy_0
和 __main_block_dispose_0
的调用。只有在如下时机copy
和dispose
方法才会调用:
copy函数 | 栈上的Block复制到堆时 |
---|---|
dispose函数 | 堆上的Block被废弃时(引用计数为0) |
何时栈上的Block会复制到堆?
Block
的copy
实例方法。Block
做为函数返回值返回时。(autorelease
对象延长生命周期)Block
赋值给附有__strong
修饰符的id类型的类或Block
类型成员变量(赋值给Strong
修饰的Block
类型属性时,编译器会帮忙复制到堆)。usingBlock
的Cocoa框架方法
或GCD
的api中传递Block
时。使用self.blockxxx()
时,使用clang
转换成C时,能够看到Bblock的调用实际是调用
Block`内的函数指针与OC对象调用发消息的形式不同。
其余业务场景,好比使用self
的成员变量作NSAarry
或 NSDictionary
作增长操做时。
不要无脑使用,更加清晰的理解Weak-Strong-Dance
,Block
内部strong
self
后Block
会继续持有self
,有些场景并不须要。
Strong
与Copy
效果都同样。在ARC环境下编译会自动将做为属性的Block
从栈Copy
到堆,这里Apple建议继续使用Copy
防止程序员忘记编译器有Copy
动做。Block
结构体中会有建立一个成员变量与截获的变量类型一直,这个值与截获时的值一致,这是一个值传递,保存的是一个瞬时值。__block
关键字的实现是一个结构体,结构体中有个本身同类型的*_farwarding
指针,当Block在栈上,__block
也是在栈上时:*_farwarding
指向栈上的本身。当Block拷贝到堆,堆中建立的__block
的*_farwarding
指向本身,同时将栈上的*_farwarding
指向堆中__block
。copy
。2 做为返回值返回。3 将Block
赋值给__strong
修饰的id类型
或Block
类型成员变量。4 方面名中含有usingBlock
的cocoa框架方法
或GCD
。__weak
弱引用,或者手动断开强引用。Block
内的weakSelf
可能会出现nil
的状况,nil
可能会形成奔溃或是其余意外结果。因此在Block
内做用域内声明一个Strong
类型的局部变量,在做用域结束后会自动释放不会形成循环引用。编程题目答案,请参考Github上的repo:TestBlock。