block是什么?
    
    
    
     1. block本质是一个OC对象,内部也有isa指针
    
    
    
     2. block是封装了函数调用以及函数调用环境的OC对象 (block内部用到block外部对象也会封装到block内部)
    
   
    
     a).block本质探究
    
    
    在main.m中写一个block,并在arm64下编译成c++代码,方便查看实现如下
   
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        int num = 3;
        void (^block2)(int) = ^(int a){
            NSLog(@"用变量保存起来的 block, a = %d, num = %d", a, num);
        };
        block2(222);
    }
    return 0;
}
利用clang编译器把objc代码转成c++代码,终端命令如下:
//在main.n同目录下生成main.cpp的文件
/xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
查看编译后的main,cpp文件,可以看到main函数中block相关代码如下:
 //声明block,删除强制转化代码,即
 //声明
 void (*block2)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
 //调用
 void (*block2)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
 
//以上代码删除强制转换修饰部分,如下
//__main_block_impl_0函数返回的地址即是block,函数的参数分别为__main_block_func_0和__main_block_desc_0_DATA以及num
void (*block2)(int) = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA, num);
//调用block部分即 block2->FuncPtr(block, 222) ,即block2的FuncPtr函数指针调用
((void (*)(__block_impl *, int))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2, 222);
        
其中的block是名称为__main_block_impl_0的结构体,文件搜索可以看出结构体如下
//__block_impl结构体
struct __block_impl {
  void *isa; //isa指针
  int Flags; 
  int Reserved; //保留字段
  void *FuncPtr; //存储的函数指针,指向block内部的实现部分
};
//__main_block_desc_0结构体
static struct __main_block_desc_0 {
  size_t reserved; //保留字段
  size_t Block_size; //block大小
}
//__main_block_impl_0结构体
struct __main_block_impl_0 {
  struct __block_impl impl; //__block_impl结构体(见上面)
  struct __main_block_desc_0* Desc; //__main_block_desc_0结构体指针
  int num; //block对象内部存在外部的变量封装
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
忽略__main_block_impl_0结构体中c++部分构造函数__main_block_impl_0,实际上main()函数中的block结构如下
struct __main_block_impl_0 {
  struct __block_impl impl; //__block_impl结构体(见上面)
  struct __main_block_desc_0* Desc; //__main_block_desc_0结构体指针
  int num; //block对象内部存在外部的变量封装
};
    
     b).测试block如何捕获局部变量
    
   
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int num_auto = 10;
        //static 实际传递的是地址,若对象用static修饰,则是**
        static int num_static = 10;
        void (^block)(void) = ^(){
            //num的值捕获进来了
            NSLog(@"block内部打印,num_auto =%d, num_static=%d", num_auto, num_static);
        };
        num_auto = 20;
        num_static = 20;
        block();
    }
    return 0;
}
    上述block执行完毕后,输出结果为10, 20。对于上述代码编译成成cpp文件如下
    
    
    
    
     b).测试block如何捕获全局变量
    
   
int num1 = 10;
static int num2 = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^(){
            //num的值捕获进来了
            NSLog(@"block内部打印,num1 =%d, num2=%d", num1, num2);
        };
        num1 = 20, num2 = 20;
        block();
    }
    return 0;
}
    
    
    
     c).测试block如何捕获self
    
    
    相关测试代码如下,
   
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
- (void)test;
@end
@implementation Person
- (void)test{
    void(^block)(void) = ^(){
        NSLog(@"------%@", _name);
    };
    block();
}
@end
    编译成cpp代码分析
    
    
    
    block内部对于当前成员变量的访问,其实也是对于当前block实例捕获后,再通过当前对象访问对应的成员变量或者属性的。
   
    
     block捕获变量总结
    
   
| 变量类型 | 是否捕获到block内部 | 访问方式 | |
|---|---|---|---|
| 局部变量 | auto | 是 | 值传递 | 
| static | 是 | 地址传递 | |
| 全局变量 | 否 | 直接访问 | |
    
     d).block类型分析
    
    
    测试代码如下
   
void blockSuperclass() {
    void(^block)(void) = ^(){ };
    NSLog(@"%@", [block class]);
    NSLog(@"%@", [[block class] superclass]);
    NSLog(@"%@", [[[block class] superclass] superclass]);
    NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
};
//通过以上代码分析继承关系,log结果为:__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
 
//block可能类型测试代码,打印输出
void blockType() {
    void(^block1)(void) = ^(){};
    int num = 10;
    void(^block2)(void) = ^(){
        NSLog(@"num=%d", num);
    };
    NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
        NSLog(@"%d", num);
    } class]);
}
//分析以上代码方法调用后的log输出结果为__NSGlobalBlock__  __NSMallocBlock__  __NSStackBlock__
//如果用clang编译成cpp文件,则对应类型如下 //对象clang中的_NSConcreteGlobalBlock  _NSConcreteMallocBlock  _NSConcreteStackBlock
//故可以看出block共有3中类型存在
//*切换MRC环境*,具体分析代码如下
int num = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int num1 = 10;
        int num2 = 10;
        void (^block1)(void) = ^{
            NSLog(@"block1 没有访问auto变量--------");
        };
        void(^block2)(void) = ^{
            NSLog(@"block3 访问了全局变量-------%d", num);
        };
        void (^block3)(void) = ^{
            NSLog(@"block3 访问了static变量--------%d", num1);
        };
        void(^block4)(void) = ^{
            NSLog(@"block4 访问了auto变量-------%d", num2);
        };
        
        
        NSLog(@"%@ %@ %@ %@", [block1 class], [block2 class], [block3 class], [block4 class]);
        //block没有访问外部变量,一般用的较少
        //MRC下输出结果:__NSGlobalBlock__ __NSGlobalBlock__ __NSGlobalBlock__ __NSStackBlock__
        //ARC下输出结果:__NSGlobalBlock__ __NSGlobalBlock__ __NSGlobalBlock__ __NSMallocBlock__
    }
    return 0;
}
//捕获了auto变量的在栈上内存的__NSStackBlock__,若再次访问捕获的变量,此时变量可能被释放了,所以可能引起异常
//注意static类型的block调用了copy后,结果仍然是global类型
总结如下:
| 应用程序的内存分配 | block存储位置示例(从上到下,内存地址递增) | 存储内容 | 
|---|---|---|
| 程序区域.text区 | 程序代码段 | |
| 数据区域.data区 | NSGlobalBlock (_NSConcreteGlobalBlock) | 全局变量存储区域 | 
| 堆 | NSMallocBlock (_NSConcreteMallocBlock) | 程序员手动开辟与释放 | 
| 栈 | NSStackBlock (_NSConcreteStackBlock) | 垃圾回收机制自动管理 | 
| 类型 | 原存储位置 | 新存储位置 | 
|---|---|---|
| NSGlobalBlock _ | 程序的数据区域 | 什么也不做 | 
| NSStackBlock _ | 栈 | 从栈复制到堆 | 
| NSMallocBlock _ | 堆 | 仅仅引用计数增1 | 
关于block进行copy操作的验证代码
typedef void(^Block)(void);
Block getBlock1() {
    //MRC下 返回的block是局部变量,栈上的block再次调用会很危险,此时需要手动进行copy操作
    return ^(){
        NSLog(@"无参数 无返回值的block");
    };
}
Block getBlock2() {
    int num = 2;
    return ^(){
        NSLog(@"无参数 无返回值的block, %d", num);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int num = 10;
        
        //ARC下此时拿到的block已经在堆区间了, 此时执行完毕后不用关心释放,系统也会帮你自动释放
        Block block = getBlock1();
        
        //没有引用auto修饰的变量,则默认为__NSGlobalBlock__
        NSLog(@"%@", [block class]);
        
        //有强指针指向,此时block由__NSStackBlock__,变为__NSMallocBlock__
        Block block2 = getBlock2();
        NSLog(@"getBlock2----%@", [block2 class]);
        
        //有访问auto变量,默认为__NSStackBlock__,又没有强指针__strong指向,所以不会copy到堆上,还是__NSStackBlock__
        NSLog(@"%@", [^{
            NSLog(@"----------%d", num);
        } class]);
    }
    return 0;
}
    总结就是,在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
    
    
     1.block作为函数返回值时
     
     2.将block赋值给__strong指针时
     
     3.block作为Cocoa APi中方法名含有usingBlock的方法参数时
     
     4.block作为GCD APi的方法参数时
    
   
    
     e).对象类型auto变量与block的关系
    
    
    默认情况下,方法调用完毕后,方法内部的对象释放,测试代码
   
typedef void(^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        {
            Person *per = [[Person alloc] init];
            per.age = 10;
        }
        Block block;
        {
            Person *per = [[Person alloc] init];
            per.age = 20;
            block = ^{
                NSLog(@"---此时没有立即释放,因为ARC下此时block在堆空间了,block又引用了per.所以per没有释放---%d", per.age);
            };
        }
        NSLog(@"对象类型的auto变量测试 log打印顺序");
    }
}
若对于上述的Person对象per进行弱引用修饰 的话,
__weak Person *weakPerson = per;
            block = ^{
                NSLog(@"---此时没有立即释放,因为ARC下此时block在堆空间了,block又引用了per.所以per没有释放---%d", weakPerson.age);
            };
//此时per对象会立即进行释放
上述Person对象会立即进行释放,执行如下命令,编译成cpp文件查看
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
    
    
    上述的block内部成员Person对象变成了弱引用,所以Person可以正常释放. 查看编译成的cpp代码
    
    
    
    相关总结如下:
    
    当block内部访问了对象类型的auto变量时:
    
    如果block是在栈上,那么将不会对auto变量产生强引用,
    
    若在堆上,则根据对象的修饰符(__strong、__weak、__unsafe_unretained)判断释放为强引用还是弱引用
   
 
