浅谈Objective-C中的block那些事
block是什么
block对象可以理解为一个标准的C语言匿名函数,同时又具备运行时的特性。通过看它的源码可以理解为什么官方文档称它为对象。
/* Revised new layout. */
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
其中isa有六种类型:
/* the raw data space for runtime classes for blocks */
/* class+meta used for stack, malloc, and collectable based blocks */
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
一般情况下,我们只需要考虑其中三种即可:
_NSConcreteStackBlock(ARC环境下会替换成_NSConcreteMallocBlock)
_NSConcreteMallocBlock
_NSConcreteGlobalBlock
block是怎么捕获变量的
这里关心的主要是静态变量和局部变量。(静态)全局变量因为它本身的作用域,所以一般不用特殊考虑。
block只会捕获它需要的变量,不需要的它不会捕获。
静态变量捕获的是变量的指针,所以可以修改原值。
局部变量捕获到数据结构,所以不可以修改原值。
block捕获Objective-C对象时,可以修改该对象内部值(dto这时候理解是一个指针)。
block捕获Ojbective-C对象时,不可以修改该对象本身(dto这时候理解为指针本身的值,也就是value)。具体可以参考下面测试代码。
通过添加
__block
,可以改变3和5的结果。
测试代码:
static int variable_static = 0;
int variable = 0;
__block int varibale_blcok = 0;
KFABlockDto *dto1 = [[KFABlockDto alloc] initWithName:@"我是1"];
KFABlockDto *dto2 = [[KFABlockDto alloc] initWithName:@"我是2"];
KFABlockDto *dto = dto1;
KFABlockDto *dto_c = dto1;
__block KFABlockDto *dto_block = dto1;
__block KFABlockDto *dto_block_c = dto1;
void (^TestBlock)(void) = ^(){
variable_static++;
// variable++; // Variable is not assignable (missing __block type specifier)
varibale_blcok++;
dto.name = @"我是dto";
// dto_c = dto2; // Variable is not assignable (missing __block type specifier)
dto_block.name = @"我是dto_block";
dto_block_c = dto2;
};
NSLog(@"variable_static: %d\nvaribale_blcok: %d\ndto: %@\ndto_block: %@\ndto_block_c: %@",variable_static,variable_static,dto,dto_block,dto_block_c);
TestBlock();
NSLog(@"variable_static: %d\nvaribale_blcok: %d\ndto: %@\ndto_block: %@\ndto_block_c: %@",variable_static,variable_static,dto,dto_block,dto_block_c);
console:
variable_static: 0
varibale_blcok: 0
dto: 名字:我是1,地址:0x7ffee8bb51e8
dto_block: 名字:我是1,地址:0x7ffee8bb51e8
dto_block_c: 名字:我是1,地址:0x7ffee8bb51e8
variable_static: 1
varibale_blcok: 1
dto: 名字:我是dto_block,地址:0x7ffee8bb51e8
dto_block: 名字:我是dto_block,地址:0x7ffee8bb51e8
dto_block_c: 名字:我是2,地址:0x7ffee8bb51e8
__block究竟干了啥
如果要修改外部变量的值,我们需要用__block
来修饰,那__block
这个为啥可以实现这个效果呢。
用__block
修饰之后,会先生成一个__Block_byref_i_0
的结构体,block捕获的是这个结构体的指针。这样就达到了改变外部变量的效果了。
block为什么用copy修饰
先看下面的测试代码及打印结果。
@property (nonatomic, copy) KFABasicParamBlock block_copy;
@property (nonatomic, strong) KFABasicParamBlock block_strong;
//@property (nonatomic, retain) KFABasicBlock block_retain; // Retain'ed block property does not copy the block - use copy attribute instead
@property (nonatomic, assign) KFABasicParamBlock block_assign;
@property (nonatomic, weak) KFABasicParamBlock block_weak;
______
// ①
// void(^TestBlock)(NSString *) = ^(NSString *type){NSLog(@"我只是个%@类型的block",type);};
// ②
int a = 2;
void(^TestBlock)(NSString *) = ^(NSString *type) {
NSLog(@"%d我只是个%@类型的block",a,type);
};
self.block_copy = TestBlock;
self.block_strong = TestBlock;
self.block_assign = TestBlock;
self.block_weak = TestBlock;
NSLog(@"%@\n%@\n%@\n%@\n%@",TestBlock,self.block_copy,self.block_strong,self.block_assign,self.block_weak);
[self performSelector:@selector(testMemory) withObject:nil afterDelay:2.0];
______
- (void)testMemory {
self.block_copy(@"copy");
self.block_strong(@"strong");
// self.block_assign(@"assign"); // Thread 1: EXC_BAD_ACCESS (code=2, address=0x600000ea5d70)
// self.block_weak(@"weak"); // Thread 1: EXC_BAD_ACCESS (code=1, address=0x460)
}
console:
MRC && ①:
TestBlock:<__NSGlobalBlock__: 0x10e7d6c98>
block_copy:<__NSGlobalBlock__: 0x10e7d6c98>
block_strong:<__NSGlobalBlock__: 0x10e7d6c98>
block_assign:<__NSGlobalBlock__: 0x10e7d6c98>
block_weak:<__NSGlobalBlock__: 0x10e7d6c98>
MRC && ②:
TestBlock:<__NSStackBlock__: 0x7ffeed3e2f08>
block_copy:<__NSMallocBlock__: 0x6000016ead90>
block_strong:<__NSMallocBlock__: 0x6000016eb540>
block_assign:<__NSStackBlock__: 0x7ffeed3e2f08>
block_weak:<__NSStackBlock__: 0x7ffeed3e2f08>
ARC && ①:
TestBlock:<__NSGlobalBlock__: 0x10af01c90>
block_copy:<__NSGlobalBlock__: 0x10af01c90>
block_strong:<__NSGlobalBlock__: 0x10af01c90>
block_assign:<__NSGlobalBlock__: 0x10af01c90>
block_weak:<__NSGlobalBlock__: 0x10af01c90>
ARC && ②:
TestBlock:<__NSMallocBlock__: 0x6000020f5f80>
block_copy:<__NSMallocBlock__: 0x6000020f5f80>
block_strong:<__NSMallocBlock__: 0x6000020f5f80>
block_assign:<__NSMallocBlock__: 0x6000020f5f80>
block_weak:<__NSMallocBlock__: 0x6000020f5f80>
__NSGlobalBlock__
不管是ARC还是MRC,不受修饰词的影响,本身就是全局的。在MRC下,block在创建的时候在栈上,用copy修饰是为了把它copy到堆上。也就是
__NSStackBlock__
->__NSMallocBlock__
。用retain修饰的话,Xcode会报警告:Retain’ed block property does not copy the block - use copy attribute instead
在ARC下,block创建之后打印发现,它已经变成了
__NSMallocBlock__
。所以用strong还是copy效果是一样的,但是为了保留传统以及避免出现一些意外的问题,还是习惯继续使用copy。Blocks “just work” when you pass blocks up the stack in ARC mode, such as in a return. You don’t have to call Block Copy any more.
参考
Blocks Programming Topics
BlocksRuntime
Transitioning to ARC Release Notes
深入研究Block捕获外部变量和__block实现原理
深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用
A look inside blocks: Episode 1
A look inside blocks: Episode 2
A look inside blocks: Episode 3 (Block_copy)
谈Objective-C block的实现