[toc]
参考
https://www.jianshu.com/p/a11337dbc8fd // 存储域
存储域 - 3种类型
(类型和存储域是一一对应的)
block.class |
__block_impl.isa |
特征 | 存储域 | 生命周期 | copy效果 | 持有对象 |
---|---|---|---|---|---|---|
__NSGlobalBlock__ |
_NSConcreteGlobalBlock |
没有访问auto变量(局部变量) | 全局 .data | 从创建到应用程序结束 | 什么也不做 | 否 |
__NSStackBlock__ |
_NSConcreteStackBlock |
访问了auto变量 | 栈 | 出栈时(出函数作用域)会被销毁 | 从栈复制到堆 | 否 |
__NSMallocBlock__ |
_NSConcreteMallocBlock |
__NSStackBlock__ 调用了copy |
堆 | 当引用计数为0时会被销毁 | 引用计数增加 | 是 |
可以通过打印出 block 对象来确定它存储的位置, 这3种类型的 block 对象打印出来的类分别是:
__NSGlobalBlock__
、__NSStackBlock__
、__NSMallocBlock__
通过调用 class 和 superclass 方法, 可查看 block 具体类型:
// __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]); // __NSGlobalBlock__
NSLog(@"%@", [block class].superclass); // __NSGlobalBlock
NSLog(@"%@", [block class].superclass.superclass); // NSBlock
NSLog(@"%@", [block class].superclass.superclass.superclass); // NSObject
可见: block 最终都是继承自 NSBlock 类型, 而 NSBlock 继承于 NSObjcet。
那么 block 其中的 isa 指针其实是来自NSObject中的, 这也更加印证了block 的本质就是OC对象。
__NSGlobalBlock__
未访问任何变量的 全局block
// 定义在全局的block
void (^globalBlk)(void) = ^{
printf("全局的block");
};
// 在viewDidLoad 中打印
NSLog(@"全局block变量-本身 %@", globalBlk);
NSLog(@"全局block变量-拷贝 %@", [globalBlk copy]);
// ARC/MRC下验证上述代码, 均输出如下:
全局block变量-本身 <__NSGlobalBlock__: 0x100018090>
全局block变量-拷贝 <__NSGlobalBlock__: 0x100018090>
结论:
普通全局block, 不论是ARC/MRC, 不论是否拷贝, block对象都是
__NSGlobalBlock__
类, 存储区域都是.data区
当我们把Block作为全局变量使用时, 对应生成的Block将被设为 _NSConcreteGlobalBlock
, 如:
void (^block)(void) = ^{ NSLog(@"This is a Global Block"); };
int main(int argc, const char * argv[]) {
@autoreleasepool {
block();
}
return 0;
}
该代码转换后的代码中, Block结构体的成员变量isa的初始化为: impl.isa = &_NSConcreteGlobalBlock
;
访问全局变量的 全局block
(全局block只能访问全局变量)
// 全局变量
int globalNum = 1;
// 定义在全局的block
void (^globalBlk)(void) = ^{
globalNum = 2;
};
// 在viewDidLoad 中打印
NSLog(@"全局block变量-本身 %@", globalBlk);
NSLog(@"全局block变量-拷贝 %@", [globalBlk copy]); // 未拷贝出新对象
// ARC/MRC下验证上述代码, 均输出如下:
全局block变量-本身 <__NSGlobalBlock__: 0x100098088>
全局block变量-拷贝 <__NSGlobalBlock__: 0x100098088>
结论:
访问了全局变量的全局 block, 和普通全局 block 性质完全一样
全局 block, 不论是ARC/MRC, 不论是否拷贝, 不论是否访问全局变量, block 对象都是
__NSGlobalBlock__
类, 存储区域都是.data区
未访问任何变量的 局部block
- (void)viewDidLoad {
[super viewDidLoad];
void (^localBlk)(void) = ^{
printf("局部的");
};
NSLog(@"局部block变量-本身 %@", localBlk);
NSLog(@"局部block变量-拷贝 %@", [localBlk copy]); // 未拷贝出新对象
NSLog(@"局部block对象-本身 %@", ^{
printf("局部的");
});
NSLog(@"局部block对象-拷贝 %@", [^{
printf("局部的");
} copy]); // 拷贝出了新对象
}
// ARC/MRC下验证上述代码, 均输出如下:
局部block变量-本身 <__NSGlobalBlock__: 0x1000a4098>
局部block变量-拷贝 <__NSGlobalBlock__: 0x1000a4098>
局部block对象-本身 <__NSGlobalBlock__: 0x1000a40d8>
局部block对象-本身 <__NSGlobalBlock__: 0x1000a4118>
结论:
未访问变量的局部block, 不论是ARC/MRC, 不论是否拷贝, block对象都是
__NSGlobalBlock__
类, 存储区域都是.data区
访问(静态)全局变量的 局部block
// 全局变量 (考虑加static的情况)
int globalNum = 1;
- (void)viewDidLoad {
[super viewDidLoad];
void (^localBlk)(void) = ^{
globalNum = 2;
};
NSLog(@"局部block变量-本身 %@", localBlk);
NSLog(@"局部block变量-拷贝 %@", [localBlk copy]);
NSLog(@"局部block对象-本身 %@", ^{
globalNum = 3;
});
NSLog(@"局部block对象-拷贝 %@", [^{
globalNum = 4;
} copy]); // 拷贝出了新对象
}
// ARC/MRC 普通/静态全局变量, 验证上述代码, 均输出如下:
局部block变量-本身 <__NSGlobalBlock__: 0x100034090>
局部block变量-拷贝 <__NSGlobalBlock__: 0x100034090>
局部block对象-本身 <__NSGlobalBlock__: 0x1000340d0>
局部block对象-拷贝 <__NSGlobalBlock__: 0x100034110>
结论:
局部 block 访问全局变量, 不论是 ARC/MRC , 不论是否拷贝 , block 对象都是
__NSGlobalBlock__
类, 存储区域都是.data区
捕获静态局部变量的 局部block
- (void)viewDidLoad {
[super viewDidLoad];
static int localNum = 1;
void (^localBlk)(void) = ^{
localNum = 2;
};
NSLog(@"局部block变量-本身 %@", localBlk);
NSLog(@"局部block变量-拷贝 %@", [localBlk copy]);
NSLog(@"局部block对象-本身 %@", ^{
localNum = 3;
});
NSLog(@"局部block对象-拷贝 %@", [^{
localNum = 4;
} copy]); // 拷贝出了新对象
}
// ARC/MRC下 验证上述代码, 均输出如下:
局部block变量-本身 <__NSGlobalBlock__: 0x1000d4070>
局部block变量-拷贝 <__NSGlobalBlock__: 0x1000d4070>
局部block对象-本身 <__NSGlobalBlock__: 0x1000d40b0>
局部block对象-拷贝 <__NSGlobalBlock__: 0x1000d40f0>
结论:
局部block访问静态局部变量, 不论是ARC/MRC, 不论是否拷贝, block对象都是
__NSGlobalBlock__
类, 存储区域都是.data区
__NSStackBlock__
/ __NSMallocBlock__
捕获普通局部变量的 局部block (F) ★★
- (void)viewDidLoad {
[super viewDidLoad];
int localNum = 1;
void (^localBlk)(void) = ^{
printf("%d", localNum);
};
NSLog(@"局部block变量-本身 %@", localBlk); // ARC在堆上, MRC在栈上
NSLog(@"局部block变量-拷贝 %@", [localBlk copy]); // ARC/MRC 经过copy, 都在堆上
NSLog(@"局部block对象-本身 %@", ^{
printf("%d", localNum);
}); // ARC/MRC都在栈上 (该block未被强指针引用)
NSLog(@"局部block对象-拷贝 %@", [^{
printf("%d", localNum);
} copy]); // ARC/MRC都在堆上
}
// ARC下验证上述代码, 输出如下:
局部block变量-本身 <__NSMallocBlock__: 0x174240ae0>
局部block变量-拷贝 <__NSMallocBlock__: 0x174240ae0>
局部block对象-本身 <__NSStackBlock__: 0x16fd69e90>
局部block对象-拷贝 <__NSMallocBlock__: 0x174240990>
// MRC下验证上述代码, 输出如下:
局部block变量-本身 <__NSStackBlock__: 0x16fd05eb8>
局部block变量-拷贝 <__NSMallocBlock__: 0x170245520>
局部block对象-本身 <__NSStackBlock__: 0x16fd05e90>
局部block对象-拷贝 <__NSMallocBlock__: 0x170245580>
结论:
F0) 捕获局部变量的 Block, 不论是ARC/MRC, 不论是否被强指针引用, 只要主动调用copy方法, 都会被复制到堆上;
F1) MRC下:
截获局部变量的Block对象本身是存储在栈上的, 即使通过持有这个Block对象的变量来访问它, 仍然能顺利访问到存储在栈上的Block对象。捕获局部变量的 Block 不管是否被强指针引用, 都存储在栈上;
捕获局部变量的 Block 只有主动调用copy方法, 才能将Block对象从栈上复制到堆上;
F2) ARC下:
截获局部变量的Block对象本身也是存储在栈上的, 只是当使用持有这个Block对象的变量来访问它时, 这个Block对象就会被从栈上复制到堆上, 这样变量访问到的就是存储在堆上的Block对象了。捕获局部变量的 Block 未被强指针引用, 存储在栈上;
捕获局部变量的 Block 被强指针引用 (Block被赋值给__strong指针或者id类型), 会被从栈上复制到堆上;
F3) ARC下, 只有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的block。比较ARC/MRC下的区别, 可以发现: 使用变量访问Block对象时, 在不同编译模式下Block对象的存储区域不同, MRC下能访问到栈上的对象, ARC下访问到的是被复制到堆上的对象, 所以才会有 "<u>ARC下没有 _NSConcreteStackBlock</u>" 这种说法。
捕获__block局部变量的 局部block (G)
- (void)viewDidLoad {
[super viewDidLoad];
__block int localNum = 1;
void (^localBlk)(void) = ^{
localNum = 2;
};
NSLog(@"局部block变量-本身 %@", localBlk); // ARC在堆上, MRC在栈上
NSLog(@"局部block变量-拷贝 %@", [localBlk copy]); // ARC/MRC 经过copy, 都在堆上
NSLog(@"局部block对象-本身 %@", ^{
localNum = 3;
}); // ARC/MRC都在栈上
NSLog(@"局部block对象-拷贝 %@", [^{
localNum = 4;
} copy]); // ARC/MRC都在堆上
}
// ARC下验证上述代码, 输出如下:
局部block变量-本身 <__NSMallocBlock__: 0x17405dfa0>
局部block变量-拷贝 <__NSMallocBlock__: 0x17405dfa0>
局部block对象-本身 <__NSStackBlock__: 0x16fdf5e58>
局部block对象-拷贝 <__NSMallocBlock__: 0x170240540>
// MRC下验证上述代码, 输出如下:
局部block变量-本身 <__NSStackBlock__: 0x16fd91e90>
局部block变量-拷贝 <__NSMallocBlock__: 0x17004b370>
局部block对象-本身 <__NSStackBlock__: 0x16fd91e58>
局部block对象-拷贝 <__NSMallocBlock__: 0x17004b220>
结论:
对比F和G, 可以发现
__block
不会影响block的存储域。
Block声明在全局, 定义在局部, 访问局部变量
// 声明在全局
void (^block)(void);
- (void)viewDidLoad {
[super viewDidLoad];
int age = 10;
// 定义在局部
block = ^{
NSLog(@"age is %d", age);
};
NSLog(@"block: %@", block);
block();
}
// MRC 输出:
block: <__NSStackBlock__: 0x7ffeefbff3e8>
// ARC 输出:
block: <__NSMallocBlock__: 0x103a04ee0>
结论:
Block 定义在局部, 行为与局部block一致。
存储域 - 类型结论 ★★
① 没有访问局部变量的 block 就是 __NSGlobalBlock__
未访问任何外部变量、或仅访问了 (静态)全局变量
/ 静态局部变量
的block, 不论该 block 定义在 全局 / 局部
, 不论是 MRC / ARC
, 不论是否被拷贝
, 该block对象都是 __NSGlobalBlock__
类, <u>存储在 .data区
, 生命周期从创建到应用程序结束</u>。
② 也就是说, 只有访问了普通局部变量的block , 存储域才会改为 堆/栈
; 该block必然是局部block。
③ 被强指针引用的 block 访问了局部变量:
- 如果未手动copy, ARC 在堆上(自动copy了), MRC在栈上;
- 经过copy,
ARC/MRC
都在堆上。
④ 未被强指针引用的 block 访问了局部变量:
- 如果未手动copy,
ARC/MRC
都在栈上; - 经过copy, ARC/MRC 都在堆上。(案例F)
⑤ __block
不会影响 block 的存储域。
存储域 - 对持有对象的影响
只有堆上的block会持有对象 (产生强引用, 使retainCount +1)
__NSGlobalBlock__
不持有对象
// 在MRC下执行
static NSObject *obj;
- (void)viewDidLoad {
[super viewDidLoad];
obj = [[NSObject alloc] init];
NSLog(@"block定义前: obj引用 = %lu", (unsigned long)obj.retainCount);
void (^localBlk)(void) = ^{
NSLog(@"block内部一: obj引用 = %lu", (unsigned long)obj.retainCount);
};
NSLog(@"block定义后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
localBlk();
NSLog(@"block执行后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
}
输出:
block定义前: obj引用 = 1
block定义后: obj引用 = 1, 局部block变量-本身 <__NSGlobalBlock__: 0x10005c070>
block内部一: obj引用 = 1
block执行后: obj引用 = 1, 局部block变量-本身 <__NSGlobalBlock__: 0x10005c070>
__NSStackBlock__
不持有对象
// 在MRC下执行 (ARC下被强指针引用的block会直接copy到堆)
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [[NSObject alloc] init];
NSLog(@"block定义前: obj引用 = %lu", (unsigned long)obj.retainCount);
void (^localBlk)(void) = ^{
NSLog(@"block内部一: obj引用 = %lu", (unsigned long)obj.retainCount);
};
NSLog(@"block定义后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
localBlk();
NSLog(@"block执行后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
// [localBlk release];
}
// 输出:
block定义前: obj引用 = 1
block定义后: obj引用 = 1, 局部block变量-本身 <__NSStackBlock__: 0x16fdc1eb8>
block内部一: obj引用 = 1
block执行后: obj引用 = 1, 局部block变量-本身 <__NSStackBlock__: 0x16fdc1eb8>
__NSMallocBlock__
持有对象
// 在MRC下执行
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [[NSObject alloc] init];
NSLog(@"block定义前: obj引用 = %lu", (unsigned long)obj.retainCount);
void (^localBlk)(void) = [^{
NSLog(@"block内部一: obj引用 = %lu", (unsigned long)obj.retainCount); // 此时obj指针copy到堆, 但和包外的obj共同指向堆中的同一块内存地址, 导致其引用计数+1 <参考2.3C>
} copy];
NSLog(@"block定义后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
localBlk();
NSLog(@"block执行后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
[localBlk release]; // 如果不销毁, obj.retainCount 还是 2
NSLog(@"block销毁后: obj引用 = %lu", (unsigned long)obj.retainCount);
}
block定义前: obj引用 = 1
block定义后: obj引用 = 2, 局部block变量-本身 <__NSMallocBlock__: 0x17405a970>
block内部一: obj引用 = 2
block执行后: obj引用 = 2, 局部block变量-本身 <__NSMallocBlock__: 0x17405a970>
block销毁后: obj引用 = 1
自动copy到堆 ★
ARC下, 编译器会根据以下情况自动将栈上的block 复制到堆上:
Block 作为函数的返回值;
由于_NSConcreteStackBlock
所属的变量域一旦结束, 该Block就会被销毁。所以编译器会自动将返回的block进行copy操作; 这样即使 Block 的变量作用域结束, 堆上的 Block 还可以继续存在。Block 被强引用, Block被赋值给
__strong
指针或者id类型 (有强指针引用block) ;调用 Cocoa API 入参中含有 usingBlock 的方法;
block 作为 GCD API 的入参;