浅谈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的实现 
