iOS Block

block本质
block是封装了函数调用以及函数调用环境的OC对象(它内部也有个isa指针)

函数调用环境:函数调用需要什么(比如参数、需要外部值)

比如main函数里有block

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        int age = 10;
        void (^myBlock)() = ^(){
            NSLog(@"This is Block! age = %d",age);
        };
        myBlock();
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

上面block那几句代码在转C++的cpp文件里大致是这样的

int age = 10;

//block定义
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));//这里的括号里面加括号可以简单理解为在强转,主要就是&__main_block_impl_0这个东西,相当于拿到__main_block_impl_0函数地址传给左边的myBlock,搜索__main_block_impl_0函数可以看到是__main_block_impl_0结构体里的构造函数。第一个参数是block里的方法地址;第二个参数是block的大小相关的描述;第三个参数是外界值。因为__main_block_impl_0函数是结构体里的初始化函数,所以就是把结构体的地址赋值给了左边的myBlock。

//block调用
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);//可以看到这个调用很奇怪,去掉强制转换后可以简写为myBlock->FuncPtr(myBlock),可能为很奇怪,明明FuncPtr是在__main_block_impl_0结构体里的,为啥能直接通过myBlock->FuncPt拿呢,首先可以看到myBlock->FuncPt前的强制转换,强制把myBlock转成了(__block_impl *)型,这时候会更好奇了,__block_impl和__main_block_impl_0是不一样的结构体,为啥能转,其实是因为__block_impl是__main_block_impl_0的第一个元素,__main_block_impl_0的地址值就是__block_impl的地址值,所以可以进行强转。强转之后调用FuncPt,其实就是调用__main_block_func_0函数,里面需要传一个block。

第一个参数长下面这样,可以看到就是myBlock里的NSLog函数。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_1a84fd_mi_0,age);
}

//展开__main_block_impl_0结构体可以看到定义如下
//__main是文件里的方法名带过来的,如果是别的方法,__main会被换成别的名字。_0的这个0代表main中的第几个block,如果再有一个block,会变成_1
struct __main_block_impl_0 {
    //__block_impl结构体没有用*修饰impl,相当于直接把__block_impl放到这里面(但是如果直接考进来,可能会有内存不对等)
    struct __block_impl impl;
    //__main是文件里......
    struct __main_block_desc_0* Desc;
    //外界定义的变量
    int age;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
      impl.isa = &_NSConcreteStackBlock;
      impl.Flags = flags;
      impl.FuncPtr = fp;
      Desc = desc;
    }
};

struct __block_impl {
    void *isa;     //ISA指针
    int Flags;
    int Reserved;
    void *FuncPtr;  //block内部一些函数方法地址的指针
};

static struct __main_block_desc_0 { //__main是文件里的方法名带过来的,如果是别的方法,__main会被换成别的名字
  size_t reserved;
  size_t Block_size;   //block占用大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //0赋值给reserved;sizeof(struct __main_block_impl_0)赋值给Block_size

为了便于学习,上面代码可以简洁如下

struct __main_block_impl_0 {
    struct __block_impl impl;   //__block_impl结构体没有用*修饰impl,相当于直接把__block_impl放到这里面(但是如果直接考进来,可能会有内存不对等)
    struct __main_block_desc_0* Desc; //__main是文件里......
    int age;     //外界定义的变量
};

struct __block_impl {
    void *isa;     //ISA指针
    int Flags;
    int Reserved;
    void *FuncPtr;  //block内部一些函数方法地址的指针
};

static struct __main_block_desc_0 { //__main是文件里的方法名带过来的,如果是别的方法,__main会被换成别的名字
  size_t reserved;
  size_t Block_size;   //block占用大小
}

如果block接收参数呢

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        int age = 10;
        void (^myBlock)(int,int) = ^(int a,int b){
            NSLog(@"This is Block! age = %d, a = %d, b = %d",age,a,b);
        };
        myBlock(11,12);
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

将上述代码转cpp文件后看到如下

int age = 10;
//block定义
void (*myBlock)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));//相较上面,也就是左边myBlock和传递方法地址时多了(int, int)。
//block调用
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 11, 12);//强转以及调用多了俩接收参数

//第一个参数长下面这样,可以看到就是myBlock里的NSLog函数。相比无传参的block,多了接收外界值。
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    int age = __cself->age; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_02897f_mi_0,age,a,b);
}

//__main_block_impl_0结构体并没有变化。
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//__block_impl结构体并没有变化。
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

//__main_block_desc_0结构体并没有变化。
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

看一个例子

int age = 10;

void (^block)(void) =  ^{
    NSLog(@"age is %d", age);
};

age = 20;

block();  //调用block结果是 age is 10

针对这种原因是因为block块代码在生成的时候,已经在内部生成了一个自己的int age ,然后把外界传进来的局部变量age=10赋值进去,所以在调用block()执行的时候是调用了block内部的age属性
看cpp文件就知道

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_1a84fd_mi_0,age);
}
可以看到是int age = __cself->age;,这个__cself就是block,给age赋值时取得是block内部的age

static静态变量

静态变量优点:
1、节省内存。静态变量只存储一处,但供所有对象使用。
2、它的值是可以更新的。
3、可提高时间效率。只要某个对象对静态变量更新一次,所有的对象都能访问更新后的值。

静态变量static的使用
1、修饰局部变量
让局部变量只初始化一次,局部变量在程序中只有一份内存,但是并不会改变局部变量的作用域,仅仅是改变了局部变量的生命周期(只到程序结束,这个局部变量才会销毁)。
2、修饰全局变量
全局变量的作用域仅限于当前文件

block的变量捕获(capture)

    变量类型             捕获到block内部   访问方式
    局部变量  auto变量          是         值传递
           static变量         是        指针传递
    全局变量                   否         直接访问

auto:自动变量,离开作用域自动销毁。所以一般定义的局部变量都是默认auto。 auto int age 等于 int age; 注意:auto只存在于局部变量里面

定义局部变量
auto int age = 10;
static int num = 20;

看另外一个例子

int age = 10; //此处局部变量
static int height = 10;  //此处局部变量

void (^block)(void) =  ^{
    NSLog(@"age is %d,height is %d", age,height);
};

age = 20;
height = 20;

block();  //调用block结果是 age is 10,height is 20
针对这种原因是因为block块代码在生成的时候,已经在内部生成了一个自己的int age和 int *height,然后把外界传进来的局部变量age=10以及height的指针赋值进去,所以在调用block()执行的时候是调用了block内部的age属性以及height的指针,从而找到height = 20

同样都是局部变量,为什么auto和static会有这么大的差异呢?
auto作用域太小,为了防止需要访问时内存已被销毁,所以需要在block内部自己定义一份。static能够常驻内存不用担心释放,所以直接指针传递就行了。

为了验证上述问题,看下上面代码转成的cpp文件

int age = 10;
static int height = 10;

void (* myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));//可以看到age传的是值,height传的是地址

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

其中__main_block_impl_0如下
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;       //可以看到age是值
  int *height;   //可以看到height是地址
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

block内部方法如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; //拿到值
    int *height = __cself->height; // 拿到指针

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_42835e_mi_0, age,(*height));//打印时height指针地址里存的值
}

再看全局变量问题

#import <Foundation/Foundation.h>

int age_ = 10;
static int height_ = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       void (^block)(void) = ^{
            NSLog(@"age_ is %d, height_ is %d", age_, height_);
        };
        age_  = 20;
        height_ = 20;
        block();  //打印都是20
    }
    return 0;
}

针对这种原因是因为block块代码在生成的时候,不会在block内部捕获,直接访问外界全局变量
为了验证上述问题,看下cpp文件

void (* myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//可以看到并无传值

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

其中__main_block_impl_0如下
struct __main_block_impl_0 {//__main_block_impl_0内部也没有外界值的定义
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

block内部方法如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_a9afb9_mi_0, age_,height_);//方法里是直接拿外界值。
}

为什么局部变量一定会被捕获到block内部,全局变量不用 被捕获?
因为作用域不同,针对局部变量,如果跨域去访问,必须捕获到block内部才能保证正常访问,全局变量则不用担心这个问题。
至于auto需要值传递是因为作用域太小,生命周期太短,不进行值传递跨域访问时会出问题;static局部变量虽然作用域也是很小,但是生命周期因为static的原因能常驻内存,所以没必要像auto一样累赘的值传递,直接地址传递就行了。

比如一个MJPerson.h类,有一个test方法

- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"-------%p", self);
    };
    block();
}

那么请问调用的这个self会捕获吗?
会被捕获。
简单看下转成cpp是什么样的
针对test方法在cpp里面是怎么定义的,如下

static void _I_MJPerson_test(MJPerson * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__MJPerson__test_block_impl_0((void *)__MJPerson__test_block_func_0, &__MJPerson__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

可以看到test默认接收了俩参数,一个自身 MJPerson * self,一个SEL _cmd,因为参数也被当做是局部变量,所以test里的self会被捕获。
可以看到block初始化时里有四个参数,第三个是self,第四个是SEL _cmd

看下__MJPerson__test_block_impl_0定义如下,捕获了self,至于SEL _cmd则没有要,因为没必要。

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *self;//MJPerson *原因为self在外部类型就是这样的,并不是指针传递
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

再看个例子一个MJPerson.h类,有一个test方法,有一个name属性

@property (copy, nonatomic) NSString *name;

那么如下调用,name会被捕获吗?

- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"-------%d", _name);
    };
    block();
}

不会单独针对name捕获,会将整个self捕获,因为_name访问方式等同于self->_name,self会被捕获,那么_name也就被包含了。 _name并不能等同于全局变量的。

那么这个例子呢?

- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"-------%@", [self name]);
    };
    block();
}

不会单独针对name捕获,会将整个self捕获,虽然通过[self name]调用,但是[self name]本质走的是消息转发流程mbjc_msgSend(self,sel_regsterName("name")),self为主体在且在block内部被用到,所以self作为参数被当做局部变量进行捕获了。

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

__NSGlobalBlock__ (等价于 _NSConcreteGlobalBlock )
__NSMallocBlock__ (等价于 _NSConcreteMallocBlock )
__NSStackBlock__ (等价于 _NSConcreteStackBlock )

既然说block是一个oc对象,那么可以调用class看下这个类的特征,以及父类什么的。

void (^block)(void) = ^{
    NSLog(@"Hello");
};
NSLog(@"%@", [block class]);  // __NSGlobalBlock__  这几个打印都是在ARC下打印的
NSLog(@"%@", [[block class] superclass]);//__NSGlobalBlock
NSLog(@"%@", [[[block class] superclass] superclass]);//NSBlock
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);//NSObject ,看到最后继承于NSObject,也可以很好地说明block是OC对象。至于block里的isa指针也是继承NSObjects时带过来的。

看一个例子

void (^block1)(void) = ^{
    NSLog(@"Hello");
};

int age = 10;
void (^block2)(void) = ^{
    NSLog(@"Hello - %d", age);
};

NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
    NSLog(@"%d", age);  //这几个打印都是在ARC下打印的
} class]);//打印结果__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

我们把上述代码转c++代码后看到isa指针指向的都是impl.isa = &_NSConcreteStackBlock;并没有区分三种类别。其实通过clang转的C++并不一定是真正编译运行的c++,只能作为一种参考,一切以运行时为准,运行的时候上述三种block是三种类别。

先讲一下内存分配

四块区域

   低地址   ___
    |     |___|  程序区域.text块   (代码段、函数方法)
    |     |___|  数据区域.data块   (全局变量)                     <-存在数据区域 __NSGlobalBlock__
   \|/    |___|    堆 (动态分配内存,比如alloc生成的,由开发者申请释放) <-存在堆 __NSMallocBlock__
  高地址   |___|    栈(比如局部变量,程序自动管理)                   <-存在栈 __NSStackBlock__

针对三种block在内存中怎么分配的呢?

__NSGlobalBlock__存在  数据区域.data块
__NSMallocBlock__存在  堆
__NSStackBlock__ 存在  栈

那block怎么区分类型呢?
访问了auto变量的block是 NSStackBlock (有些情况下需要关闭ARC才能看到正确打印,不关闭ARC有时候NSStackBlock调用copy后打印的是NSMallocBlock)
调用了copy的block是 NSMallocBlock (注意:NSGlobalBlock调用copy依旧是NSGlobalBlock,什么都不会变化;NSMallocBlock调用copy依旧是NSMallocBlock,只是引用计数器加1,之所以引用计数器加1是因为NSMallocBlock已经是在堆上了)
没有访问auto变量的block是 NSGlobalBlock (访问static局部变量依旧是GlobalBlock)

新建一个mac命令行程序(因为不用启动模拟器)
MRC环境下:

#import <Foundation/Foundation.h>

void (^block)(void);

void test() {
    int age = 10;
    block = ^{
        NSLog(@"age = %d",age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}

运行程序可以看到打印
2019-11-29 11:17:41.051430+0800 testss[2475:81438] age = -272632488

为什么会这样呢,因为block访问了auto变量,成了NSStackBlock被存放在了栈区,当调用完test();后,block随着test函数的结束,作用域也结束。所以这时候在访问block();就会看到内存访问混乱。
针对上面问题,访问了auto变量的block是 NSStackBlock 被放在了栈内存中,随时都会被销毁,所以跨域访问会出现各种问题,所以如果把NSStackBlock放在堆里,就不会出现跨域异常问题。即将NSStackBlock copy一下就变成了NSMallocBlock放在堆上了

block = [^{
       NSLog(@"age = %d",age);
        } copy];

MRC下:

void (^block)(void) = ^{
    NSLog(@"-------");
};

int age = 10;
void (^block1)(void) = ^{
       NSLog(@"-------,%d",age);
};

void (^block2)(void) = [^{
    NSLog(@"-------,%d",age);
} copy];

NSLog(@"%@ %@ %@", [block class], [block1 class], [block2 class] );
2019-11-29 10:50:36.169072+0800 Test[2052:64456] __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__

ARC下

void (^block)(void) = ^{
    NSLog(@"-------");
};

int age = 10;
__weak void (^block1)(void) = ^{  //如果不加__weak,这里会打印成__NSMallocBlock__
       NSLog(@"-------,%d",age);
   };

void (^block2)(void) = [^{
    NSLog(@"-------,%d",age);
} copy];

NSLog(@"%@ %@ %@", [block class], [block1 class], [block2 class] );
2019-11-29 10:48:22.019517+0800 Test[1997:62412] __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__

上面的情况,有一个注意点
__weak int age = 1; //这种写法会有警告,因为__weak只是用来修饰对象的。

 void (^block1)(void) = ^{
    NSLog(@"-------,%d",age);
};
依旧是__NSMallocBlock__。因为左边有强指针,所以会copy,copy后会根据age是弱指针,导致弱引用age,但是这里age是基础数据类型,所以desc里没有copy和dispose。

如果是下面这种是对象类型,就会copy到堆上后弱引用person

__weak MJPerson *person = [[MJPerson alloc]init];
void (^ block1)(void) = ^{
    NSLog(@"-------,%p", person);
};//__NSMallocBlock__

在ARC环境下,编译器会根据情况自动对cblock进行copy操作,比如以下情况
1.block作为函数返回值时

              #import <Foundation/Foundation.h>

              typedef void (^MJBlock)(void); //定义全局block

              MJBlock getBlock() {  //声明一个c语言函数,返回值是MJBlock类型
                  return ^{  //用一个block作为返回值
                      NSLog(@"----");
                  };
              }

              int main(int argc, const char * argv[]) {
                  @autoreleasepool {
                       MJBlock block = getBlock(); //调用这个c语言函数,返回值是MJBlock类型
                       block(); //然后调用这个block
                  }
                  return 0;
              }

              上述情况,我们在main函数调用block时
              // MJBlock block = getBlock();
              // block();
              系统会自动copy

2.将block赋值给__strong指针时

              #import <Foundation/Foundation.h>

              typedef void (^MJBlock)(void);

              int main(int argc, const char *argv[])
              {
                  @autoreleasepool {
                      int age = 10;
                      MJBlock block = ^{ //并不一定左边的block要是全局,局部变量的默认也是strong
                          NSLog(@"---------%d", age);
                      };
                      block();
                  }
                  return 0;
              }

3.block作为Cocoa API中方法名含有usingBlock的方法参数时

       例如: NSArray *arr = @[@1,@2,@3,@4];
            [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

            }];

4.block作为GCD API的方法参数时

MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);

ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

在使用clang转换OC为C++代码时,可能会遇到以下问题

cannot create __weak reference in file using manual reference

解决方案:支持ARC、指定运行时系统版本,比如

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

如上如果指定运行时版本的话,可以看到生成的cpp文件的变量内存属性

来看个例子

MJPerson里有个age
然后如下代码
#import <Foundation/Foundation.h>
#import "MJPerson.h"

typedef void (^MJBlock)(void);

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        MJBlock myBlock;
        {
            MJPerson *person = [[MJPerson alloc]init];
            person.age = 2;

            myBlock = ^{
                NSLog(@"age = %d", person.age);
            };
        }
        NSLog(@"--------");
    }
    return 0;
}

如上,ARC下person会在代码执行到NSLog断点处时销毁吗?
不会。
那么MRC下person会在代码执行到NSLog断点处时就销毁吗?
会。

首先看下指定运行时版本转的cpp文件

int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        MJBlock myBlock;
        {
            MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 2);
            
            myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
        }
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_2b9a90_mi_1);
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

如果在ARC下把代码改造成下面样式

#import <Foundation/Foundation.h>
#import "MJPerson.h"

typedef void (^MJBlock)(void);

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        MJBlock myBlock;
        {
            MJPerson *person = [[MJPerson alloc]init];
            person.age = 2;

            __weak MJPerson *weakPerson = person;
            myBlock = ^{
                NSLog(@"age = %d", weakPerson.age);
            };
        }
        NSLog(@"--------");
    }
    return 0;
}

如上,ARC下person会在代码执行到断点处时销毁吗?
会。

首先看下指定运行时版本转的cpp文件

int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        MJBlock myBlock;
        {
            MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 2);

            __attribute__((objc_ownership(weak))) MJPerson *weakPerson = person;
            myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakPerson, 570425344));
        }
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_573c8b_mi_1);
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

NSStackBlock存放在栈区,不会"强"持有block内部访问外界的auto对象(不会"强"持有意思就是会持有,但不是强引用);NSMallocBlock存放在堆区,会持有,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作。(注意点:只要是存放在栈区NSStackBlock,哪怕捕获到变量是__strong也没用,也不会强持有外界的auto对象)

当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用

下面看另一种现象 ARC下

#import <Foundation/Foundation.h>
typedef void (^MJBlock)(void);

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        int age = 2;
        MJBlock myBlock = ^{
            NSLog(@"age = %d", age);
        };
    }
    return 0;
}

转成的cpp文件

int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        int age = 2;
        MJBlock myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

那如果访问的不是基础类型而是对象呢,arc下

#import <Foundation/Foundation.h>
#import "MJPerson.h"

typedef void (^MJBlock)(void);

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc]init];
        person.age = 2;

        MJBlock myBlock = ^{
            NSLog(@"age = %d", person.age);
        };
    }
    return 0;
}

转成的cpp文件

int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 2);

        MJBlock myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __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((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

可以看到将block拷贝到堆上时,内部如果捕获的是对象时,__main_block_desc_0产生了变化。多了一个copy指针,一个dispose指针。copy指针指向__main_block_copy_0函数;dispose指针指向__main_block_dispose_0函数。copy内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符来自动做出相应的操作,形成强引用或者弱引用。当block被从堆中移除时,会调用dispose指针指向的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose进行释放。

在下面连接上可以看到开源的_Block_object_assign函数的定义

http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c
/*
 * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
 * to do the assignment.
 */           //_Block_object_assign三个参数:参数1:目标地址;;参数2:源对象地址;参数3:flags的不同确定后方走什么路
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}

其中

The flags parameter of _Block_object_assign and _Block_object_dispose is set to
    * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
    * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
    * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16).

如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
Block_object_dispose函数会自动释放引用的auto变量(相当于release)

针对__main_block_desc_0方法,如果block内部访问的是外界基础数据类型,内部会


struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;  //block占用大小
};

如果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*);
}

因为需要内存管理来控制对象

//下面代码不能再命令行里演示,因为命令行main函数直接return0了,根本不给你机会等几秒后
例1


//ARC
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    MJPerson *person = [[MJPerson alloc]init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"---%@",person);
    });
    
    NSLog(@"开始点击");
}
可以看到打印,person 3秒后 销毁
2019-11-29 17:31:11.593005+0800 Test[6177:250328] 开始点击
2019-11-29 17:31:14.593211+0800 Test[6177:250328] ---<MJPerson: 0x6000013f4510>
2019-11-29 17:31:14.593349+0800 Test[6177:250328] MJPerson----dealloc

例2


//ARC
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    __weak MJPerson *person = [[MJPerson alloc]init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"---%@",person);
    });
    
    NSLog(@"开始点击");
}
可以看到打印,点击前MJPerson已经销毁
2019-11-29 17:32:07.765767+0800 Test[6198:251170] MJPerson----dealloc
2019-11-29 17:32:07.765942+0800 Test[6198:251170] 开始点击
2019-11-29 17:32:10.765957+0800 Test[6198:251170] ---(null)

例3


//ARC
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    __weak MJPerson *person = [[MJPerson alloc]init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"---%@",person);
        });
        
    });
    NSLog(@"开始点击");
}
可以看到打印,点击前MJPerson已经销毁
2019-11-29 17:34:08.055245+0800 Test[6236:252828] MJPerson----dealloc
2019-11-29 17:34:08.055468+0800 Test[6236:252828] 开始点击
2019-11-29 17:34:11.055688+0800 Test[6236:252828] ---(null)

例4


//ARC
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    MJPerson *p = [[MJPerson alloc] init];

    __weak MJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", weakP);

       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"2-------%@", p);
              });
       });
} //在这个例子里,gcd被copy到堆。因为延迟3秒的p是强引用,所以MJPerson在三秒后才释放,weakP和p打印结果一样

//ARC
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     MJPerson *p = [[MJPerson alloc] init];

     __weak MJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         NSLog(@"1-------%@",  p);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"2-------%@", weakP);
                 });
    });
}//在这个例子里,gcd被copy到堆。因为延迟1秒的p是强引用,所以MJPerson在一秒后才释放,p有值,weakP为null

默认情况下,block不能修改auto局部变量(全局和static静态是可以在block内部直接修改的)
如果想要修改需要
1.将auto局部变量变成static的局部变量或者使用全局变量
2.使用__block来修饰auto局部变量

为什么__block可以做到?下面通过代码来解释。

__block

下面是"普通数据类型"的"auto变量"


int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        int age = 10;
        void(^myBlobk)(void) = ^{
            NSLog(@"age = %d",age);
        };
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

将上述代码转cpp文件后看到如下


int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
         int age = 10;
        void(*myBlobk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

其中__main_block_impl_0如下
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

其中__block_impl如下
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

其中__main_block_desc_0如下
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

其中__main_block_func_0如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   int age = __cself->age; // bound by copy
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_71806c_mi_0,age);
}

如果用"__block"来修饰"普通数据类型"的"auto变量"呢?如下


int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        __block int age = 10;
        void(^myBlobk)(void) = ^{
            age = 20; //多了一句修改值
            NSLog(@"age = %d",age);
        };
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

将上述代码转cpp文件后看到如下

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
        
        //int age = 10;加上__block后 __block int age = 10;在main函数里就变成了下面这句话,右边是在给__Block_byref_age_0结构体初始化。第二个参数是将自己的地址传给了 。
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        
        void(*myBlobk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); //在捕捉时,已经从捕捉age变成捕捉__Block_byref_age_0的指针了。

    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

其中__main_block_impl_0如下
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

其中__block_impl如下
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

其中__main_block_desc_0如下
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*); //相较访问普通类型auto,加了__block的auto普通类型在底层已经是个对象了,所以需要copy和dispose管理内存
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

其中__Block_byref_age_0如下
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding; //指向自身的指针。至于为什么,是因为存在__Block_byref_age_0结构体从栈上拷贝到堆上的问题。当在栈上时,__forwarding指向栈上的自身取值,没有问题,当__Block_byref_age_0要拷贝到堆上时,这时候修改栈上的__forwarding指向堆上的__Block_byref_age_0结构体,这时候访问,栈上的__forwarding和堆上的__forwarding都指向堆自身,这样不管__block怎么复制到堆上,还是在栈上,都可以通过(结构体指针->__forwarding->值)来访问到变量值。__forwarding指针的存在意义是确保能正确的访问__block变量。
 int __flags;
 int __size;
 int age;
};

其中__main_block_func_0如下

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_age_0 *age = __cself->age; // 先通过__cself结构体里的age拿到指针
    (age->__forwarding->age) = 20;//拿到指针后再拿到指向自身的__forwarding,然后再取出里面的age进行修改值
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_002bd3_mi_0,(age->__forwarding->age));
}

如果用"__block"来修饰"auto对象"呢?如下

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);

        __block MJPerson *person = [[MJPerson alloc]init];
        void(^myBlobk)(void) = ^{
            person = nil;
            NSLog(@"person = %@",person);
        };
        myBlobk();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

将上述代码转cpp文件后看到如下

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    //右边是在给__Block_byref_person_0结构体初始化,相较于用"__block"来修饰"普通数据类型"的"auto变量",这里的结构体里多了copy和dispose函数,而在自赋值初始化过程中,__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131函数内部调用的也是_Block_object_assign函数,这不过这个结构体里的copy和dispose用来管理__Block变量,而这整个结构体因为包装成对象所以当被拷贝到堆上时是强引用给block。
        __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"))};
        void(*myBlobk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)myBlobk)->FuncPtr)((__block_impl *)myBlobk);
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

其中__main_block_impl_0如下
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

其中__block_impl如下
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

其中__main_block_desc_0如下
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

其中__Block_byref_person_0如下
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);//唯一不同点就在这儿,desc里面的copy和dispose管理的是外界捕获的值的内存也就是__Block_byref_person_0结构体的内存。而__Block_byref_person_0结构体里的copy和dispose管理的是__Block_byref_person_0结构体里捕获的真正用到的对象的内存。
    //如果是普通类型auto,即便是加了__Block,__Block_byref_person_0结构体里也不会有copy和dispos的,只有desc里会有。
 void (*__Block_byref_id_object_dispose)(void*);//唯一不同点就在这儿,desc里面的copy和dispose管理的是外界捕获的值的内存也就是__Block_byref_person_0结构体的内存。而__Block_byref_person_0结构体里的copy和dispose管理的是__Block_byref_person_0结构体里的对象的内存。
 MJPerson *__strong person; //可以看到person是强引用
};

其中__main_block_func_0如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     __Block_byref_person_0 *person = __cself->person; // bound by ref

    (person->__forwarding->person) = __null;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_315fcb_mi_0,(person->__forwarding->person));
 }

//结构体里的copy和dispose函数调用方法
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

如果用"__block"来修饰"__weak auto对象"呢?如下

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);

        __block __weak MJPerson *person = [[MJPerson alloc]init];  //__weak用来修饰person指针是弱的
        void(^myBlobk)(void) = ^{
            person = nil;
            NSLog(@"person = %@",person);
        };
        myBlobk();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

将上述代码转cpp文件后看到如下

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));

        //包装成__Block_byref_person_0结构体时标识了一下结构体内部的person是weak
        __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"))};
        void(*myBlobk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)myBlobk)->FuncPtr)((__block_impl *)myBlobk);
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

其中__main_block_impl_0如下
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

其中__block_impl如下
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

其中__main_block_desc_0如下
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

其中__Block_byref_person_0如下
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 MJPerson *__weak person; //此处是weak。这也是相较于__strong的auto对象最大的不同之处。
};

其中__main_block_func_0如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_person_0 *person = __cself->person; // bound by ref
    (person->__forwarding->person) = __null;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_e40e2a_mi_0,(person->__forwarding->person));
}

//结构体里的copy和dispose函数调用方法
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

总结就是编译器会将__block变量包装成一个对象,对象里面有指向自己的指针和变量,修改的话通过指针找到自己再找到变量,进行修改
注意:__block不能修饰全局变量、静态变量(static),但可以修饰任何类型的自动变量。

至于上面那么多代码分析,所以__block的内存管理总结如下

__block的内存管理

1.当block在"栈"上时,并不会对包含__block变量的结构体产生"强"引用(不会"强"引用意思就是会引用,但不是强引用)
2.当block被copy到堆时 会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对包含__block变量的那个结构体形成强引用。然后根据__block变量的修饰符,在结构体里根据结构体里自身的copy和dispose函数对__block变量形成不同引用(至于强弱引用,看修饰符,strong强引用(此处是特殊情况,仅限于ARC下__block变量才是强引用。因为在MRC下,即使此处在堆上且strong修饰,__block变量也还是弱引用。需要注意),weak弱引用)

图解就是

堆上Block ———永远强引用———>结构体指针(包含__block变量的那个结构体)
                          |
                       |    |
                     |         |
                  |              |
                |                   |
            |                       |
         |                             |
强引用强指针修饰的__block变量  弱引用弱指针修饰的__block变量(注意:MRC下都是弱引用__block变量,即使__block变量修饰符是strong)

用代码来体验下强弱引用

//ARC下
int main(int argc, const char *argv[])
{
    @autoreleasepool {
        MJBlock block;
        {
            __block MJPerson *person = [[MJPerson alloc]init];
            block = ^{
                NSLog(@"person-->%@", person);
            };
        }
        block();
    }
    return 0;
}//执行到断点处person还存在
//ARC下
int main(int argc, const char *argv[])
{
    @autoreleasepool {
        MJBlock block;
        {
            __block __weak MJPerson *person = [[MJPerson alloc]init];
            block = ^{
                NSLog(@"person-->%@", person);
            };
        }
        block();
    }
    return 0;
}//执行到断点处[MJPerson dealloc]

循环引用

因为对象持有了block,而生成block时,如果用到对象,就会在block内部引用,导致双方都持有,从而循环引用了

比如有个person类
#import <Foundation/Foundation.h>

typedef void (^MJBlock) (void);

@interface MJPerson : NSObject
@property (copy, nonatomic) MJBlock myBlock;
@property (assign, nonatomic) int age;
@end
我们在main函数里调用
int main(int argc, const char *argv[])
{
    @autoreleasepool {
        {
        MJPerson *person = [[MJPerson alloc] init];
        person.age = 10;
        person.myBlock = ^{
                           NSLog(@"age is %d", person.age);
                          };
        }
        NSLog(@"----");
    }
    return 0;
}

结合以前学的知识,MJPerson *person = [[MJPerson alloc] init];会生成person指针指向[MJPerson alloc]内存。MJPerson类强引用自身的MJBlock myBlock;然后我们在main的block里调用了person.age,从而让block内部强引用了person。这样双方都持有,导致都没法释放。
上面几句代码执行后,引用结构为下

person————通过alloc、int指向————————>MJPerson(包含__block成员变量)对象
                                                |              /|\
                                                |               |
                                   通过属性赋值强引用              通过捕获强引用外界person对象
                                                |               |
                                                |               |
                                                |               |
                                                |               |
                                               \|/              |
                                             block(block内部捕获了外界person对象)
                                                           

如上如,MJPerson对象里的成员变量强引用block,block捕获的person强引用MJPerson对象。当代码走到断点处,person通过alloc、int指向MJPerson对象的指针消失,留下了

MJPerson(包含__block成员变量)对象
             |              /|\
             |               |
通过属性赋值强引用              通过捕获强引用外界person对象
             |               |
             |               |
             |               |
             |               |
            \|/              |
          block(block内部捕获了外界person对象)

互相强引用着,使得MJPerson对象不能释放。

ARC下如何解决上述问题

让block内部弱引用了person即可解决双向强持有问题

"方案一" 用__weak

__weak MJPerson *weakPerson = person;
person.block = ^{
       NSLog(@"age is %d", weakPerson.age);
};
这样让block内部弱引用person,从而避免了双方强持有。
__weak MJPerson *weakPerson = person;可以换成__weak typeof(person) weakPerson = person;

"方案二" 用__unsafe_unretained (含义:不安全,不强引用)

__unsafe_unretained MJPerson *weakPerson = person;
person.block = ^{
         NSLog(@"age is %d", weakPerson.age);
};

__unsafe_unretained被称为不安全的原因是__unsafe_unretained修饰的指针地址内存空间如果被释放,这个指针指向依旧有值,不会像__weak修饰的指针地址 被置nil,所以被称为不安全的
使用__unsafe_unretained修饰的对象,捕捉后定义的__main_block_impl_0如下
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__unsafe_unretained person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__unsafe_unretained _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

"方案三" 用__block

#import <Foundation/Foundation.h>
#import "MJPerson.h"

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        __block MJPerson *person = [[MJPerson alloc] init];
        person.age = 10;
        person.myBlock = ^{
            NSLog(@"age is %d", person.age);
            person = nil;  //后置空person ,这两步才能保证避免循环引用
        };
        person.myBlock(); //先调用block  如果使用__block来避免循环引用,却不调用block,依旧会循环引用并引起内存泄漏
    }
    return 0;
}

之所以能用__block配合解决循环引用,是因为在上述题目中,使用__block修饰后,引用结构为

   __block变量
    /      /|\
   /         \
 持有         持有
 \|/            \
MJPerson--持有-->myBlock

MJPerson对象引用myBlock,myBlock引用__block变量,__block变量引用MJPerson对象。执行myBlock后置person = nil;实际上就是置包含__block变量的结构体里的__block变量也就是指向MJPerson对象的指针为nil,所以引用变成了MJPerson对象引用myBlock,myBlock引用__block变量,从而解决了循环引用。

使用__block控制循环引用的优点
1.通过__block变量可控制对象的持有期间
2.在不能使用__weak的环境中,不适用__unsafe_unretained
使用__block控制循环引用的缺点
1.为避免循环引用必须执行block,让__block变量置nil

MRC下如何解决上述循环引用问题

// MRC不支持__weak的
所以只剩下__unsafe_unretained和__block解决了

MRC用__block解决循环引用没有那么负责,直接__block修饰即可,因为在MRC环境下,被__block修饰的对象即使此处在堆上且strong修饰,也还是弱引用。

__block MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.block = ^{
    NSLog(@"age is %d", person.age);
};

block内部调用weakself时,多次执行self方法有些不生效,需要在block内__strong修饰self指针。

有一个例子

NSMutableArray *arr = [NSMutableArray array];
MJBlock block1 = ^{
    [arr addObject:@"111"];
    [arr addObject:@"222"];
};

不会报错,而且能修改。是因为addObject这两句不是在修改arr这个指针,而是在使用arr这个指针。如果是arr = nil才是修改这个指针。这里是截获OC对象,调用变更该对象的方法。
但是如下如下操作,则报错

NSMutableArray *arr = [NSMutableArray array];
MJBlock block1 = ^{
    arr = [NSMutableArray array];
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容