iOS的block

 

Posted by     DH on August 21, 2017

block概述

《Objective-c高级编程》中描述block是带有自动变量(局部变量)的匿名函数。

我的理解就是可以直接在一段代码中插入一个函数,而不用显示的去掉用这个函数。

block基本语法

^返回值 参数列表{};

// 返回值是整数,两个参数
^int(int i,int j){
            return  i+j;
        };

// 无返回值,参数两个
^(int i,int j){
           
        };
        
// 无参数,无返回值
^{};

将block作为变量(block变量)

blick可以作为变量,与一般OC(或者说C)的变量完全一样,可以作为:自动变量(局部变量)、静态变量、全局变量、函数参数、静态全局变量。

语法:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

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


block截取自动变量

《Objective-c高级编程》中描述block是带有自动变量(局部变量)的匿名函数。这个自动变量就表现在block对在他之前声明的自动变量的值的获取,这里的获取,需要 特别的注意,获取的是自动变量的瞬间值,block保存了自动变量的值,在block执行之后,即使改变了block中使用的自动变量的值,也不会影响block执行时自动变量的值。

举一个例子:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int i = 1;
        int j = 1;
        
        void (^block)(void) = ^{NSLog(@"%d",i+j);};
        
        block();
        
        i = 10;
        
        block();

        
    }
    return 0;
}

输出结果是:

2017-08-21 17:21:45.170390+0800 DemoBlock[2638:1592472] 2
2017-08-21 17:21:45.171136+0800 DemoBlock[2638:1592472] 2
Program ended with exit code: 0

我们可以看到,在第二次输出的依然是2,而不是11,也就说明了block捕获的是瞬间值。

__block修饰符

从上面一个例子知道,我们可以在block中截取自动变量的值,但是这个是只读的,我们不能修改block以外的自动变量的值,假如我们想要在block中修改上个例子中的i的值, 我们就要在i前面加一个__block修饰符。

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int i = 1;
        
        
        void (^block)(void) = ^{i=2;};
        
        block();
        
        NSLog(@"在block中修改自动变量的值---%d",i);

        
    }
    return 0;
}

输出结果:

2017-08-21 19:36:34.321111+0800 DemoBlock[3090:1927645] 在block中修改自动变量的值---2
Program ended with exit code: 0

block的实现

我们通过clang -rewrite-objc将源代码转化成C++语言,看看block的底层是怎么实现的。

原OC代码是:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void (^blk) (void) = ^{};
        blk();

        
        
    }
    return 0;
}
		

转换成C++代码(部分)如下:

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

struct __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}

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 main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);



    }
    return 0;
}


我们来分析一下这部分转化的代码,如下图:

经过编译,我们知道block其实就是一个结构体,从上图可以看出:

main函数的参数__cself 指向 __main_block_impl_0:

struct __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;
  }
};		

impl_0表示是main函数中的第1个block。这个结构体包含两个部分:

(1)结构题:__block_impl;

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

isa指针指向该对象对应的类,也就是block,我们可以看出其实block是一个OC对象(在runtime中对象和类都是用结构体表示)。

flags标志位

reserved 保留变量

FuncPtr block执行时调用的函数指针。

(2)Desc指针。

Desc指向的结构体__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)};		

包含两个部分;

(1)reseverd 版本升级需要的区域

(2)block的大小

同时,还创建了__main_block_desc_0_DATA,在__main_block_impl_0初始化时使用。

接下来我们重点来看看__main_block_impl_0的构造函数:

__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;
 }
 

首先,我们来看看main函数中是怎么对这个构造函数进行调用的:

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

	
        void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    }
    return 0;
}
 

((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))这一句就是对__main_block_impl_0构造函数 的调用。

第一个参数__main_block_func_0是转换成C语言的函数指针,第二个参数是初始化__main_block_impl_0结构体实例的结构体,和__main_block_impl_0的大小有关。

我们再回到__main_block_impl_0的狗在函数,这个构造函数初始化了四个变量:

isa指针指向一个_NSConcreteStackBlock;

FuncPtr指向__main_block_func_0;

desc指向__main_block_desc_0创建时的__main_block_desc_0_DATA,主要是用于初始化__main_block_impl_0的大小。

总的来说,我自己理解是两句话;

(1)block的实质是一个结构体,这个结构体包含了定义block时代码块的函数指针(FuncPtr或者说_cself)和block外部上下文变量等信息

(2)block是一个OC对象,这点从

block截获自动变量的底层实现

先来看一段代码:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int i = 0;
        int j = 1;
        
        ^{i;};
        

        
    }
    return 0;
}
		

经过编辑成C++之后的代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // 重点来了,block结构体多了一个成员变量
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy
i;}

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 main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int i = 0;
        int j = 1;

        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));



    }
    return 0;
}
		

我们可以发现在stack对应的结构体__main_stack_impl_0中多了一个成员变量i,这个i正是在main函数中,block截取的自动变量。 那么,还有一个变量j却没有被截取,因为在block中并没有使用这个变量。

因此,我们可以下一个结论:block对于自动变量的截取,是在block对应的结构体中增加一个成员变量,并且只有在block中只用的自动变量才会被截取。 同时,结构体中的成员变量i只是进行了值传递,也就是只可以读,不可以写,否则会报错。

block截取全局静态变量

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

static int i = 0;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    
        ^{i =10;};
        

        
    }
    return 0;
}
		

转换后:

static int i = 0;


struct __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
i =10;}

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 main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));



    }
    return 0;
}
		

我们可以看到,对于全局静态变量的截取,block对应的机构体并没有增加关于i的成员变量。

因为全局静态变量存储在静态数据区,这个区域的声明周期是和程序一样长的,只要程序还在运行,就能保证block访问到这一内存区域,所以不用在结构体中为这个变量保留位置。

block对于全局变量的截取

对于全局变量,我们可以根据全局静态变量进行猜想:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

int i = 0;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    
        ^{i =10;};
        

        
    }
    return 0;
}
		

转换后:

int i = 0;


struct __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
i =10;}

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 main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));



    }
    return 0;
}
		

这和我们的猜想一致,在结构体中并没有增加对于全局变量i的成员变量,这也是因为全局变量存储在全局静态数据区,这个区域保证我们在程序生命周期内,block都可以进行变量的访问。

block截取局部静态变量

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int i = 0;
        ^{i =10;};
        

        
    }
    return 0;
}
		

转换后


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //多了一个指向静态变量内存的指针
  int *i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *i = __cself->i; // bound by copy
  // 通过指针进行修改
(*i) =10;}

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 main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        static int i = 0;
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i));



    }
    return 0;
}


局部静态变量的作用域只在定义它的函数内部,而它存储在静态数据区,和程序拥有一样的生命周期,在整个程序运行期间,都可以访问到这个内存区域,所以在block内部,就只能通过他的地址来进行访问。

__block修饰符的底层实现

通过前面的例子我们知道,对于全局变量、静态全局变量、静态局部变量(通过地址访问),我们可以直接进行访问,但是对于自动变量,直接访问是会出错的,这是因为局部变量在block对应的结构体中只是进行了值传递。

那么这个时候,我们就要使用__block修饰符修饰自动变量了。 接下来看例子:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int i = 0;
        
        ^{i =10;};
        

        
    }
    return 0;
}


转换后的代码:

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) =10;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));



    }
    return 0;
}



由以上代码我们发现,自动变量居然被包装成了一个结构体,并且还有isa指针,也就是一个对象。block对应的结构体通过指向自动变量对应的结构体的指针访问结构体,同时自动变量对应的结构体通过内部的一个指向自己的指针(__forwarding)访问自动变量。当block被拷贝到堆上时,block的考培辅助函数会将自动变量对应的结构体也拷贝一份到堆区。因此即使栈上的局部变量对应的结构体被销毁,block任然可以正常访问堆区的。前面所说的__forwarding指针就是指向堆区的指针。

三种block(block在内存中的存储区域)

我们前面所说的都是_NSConcreteStackBlock,他在栈上分配内存,另外还有两类;

_NSConcreteGlobalBlock:在全局静态区,分配内存

_NSConcreteMallocBlock:在堆上分配内存

首先来说最简单的_NSConCreteGlobalStack,全局block。

先看下面的代码:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>


void (^globalBlock)() = ^{
    
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        

        
    }
    return 0;
}


转换成C++之后:

struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __globalBlock_block_func_0(struct __globalBlock_block_impl_0 *__cself) {


}

static struct __globalBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __globalBlock_block_desc_0_DATA = { 0, sizeof(struct __globalBlock_block_impl_0)};
static __globalBlock_block_impl_0 __global_globalBlock_block_impl_0((void *)__globalBlock_block_func_0, &__globalBlock_block_desc_0_DATA);
void (*globalBlock)() = ((void (*)())&__global_globalBlock_block_impl_0);

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




    }
    return 0;
}


我们可以看到这个block的类型就是_NSConcreteGlobalBlock。

除了我声明一个全局block,还有另一种情况局部block会自动转化成全局block,存储地址在静态数据区。这种情况就是函数内的block不截获自动变量,就会将block用结构体实例设置在程序的数据区。虽然这个时候我们用clang转化成C++,依然显示的是_NSConcreteStackBlock。

那么总结如下:

(1)在全局区声明block

(2)在函数内部,使用block,不截获局部变量

这两种情况下会在全局数据区创建全局block对应的结构体实例。

除了以上两种情况block语法生成的block为_NSConcreteStackBlock类对象。

接下来,看看_NSConcreteStackBlock:

对于_NSConcreteStackBlock的创建没什么好说的,只要在函数内部,使用了block语法,就会在栈去创建这个block的结构体实例。

但是我们要注意_NSConcreteStackBlock作为返回值时,在ARC有效的情况下,编译器会自动将栈上的block复制到堆上。

在绝大多数情况下,编译器都会自动的将栈上的block复制到堆上。

在向方法或者函数的参数中传递block时,需要手动调用copy函数。

然而下面两种情况下,不用手动的调用copy方法:

(1)cocoa框架中的方法,并且方法名中含有usingBlock;

(2)GCD的API

最后来看看_NSConcreteGlobalBlock,_NSConcreteGlobalBlock是不能直接创建的,必须由_NSConcreteStackBlock从栈上拷贝到堆上,这个过程会调用_block_copy_internal函数。

需要拷贝的情况,我们在上面已经说了,再次总结:

(1)block作为函数的返回值时;(自动复制)

(2)block作为函数的参数传递;(手动调用copy方法)(其中两种特殊情况不需要复制到堆上1.GCD的API;2.cocoa的方法并且方法名中包含usingBLock)

那么,如果我们对全局、栈、堆上的block发送copy方法会怎样呢?

__block修饰的变量的存储区域

上面我们说了三种block的存储区域,以及他们之间的转化,那么被__block修饰的变量的存储区域怎么变化呢?

就一句话,在Block中使用了__block修饰的变量,当Block从栈上拷贝到堆上时,会一并从栈拷贝到堆上。并且,这个Block持有__block修饰的变量.

在多个Block中使用__block修饰的变量时,当第一个Block被复制到堆上时,会一并复制__block修饰的变量,当其他BLock复制到堆上时,被复制的Block持有__block变量,并且__block引用计数增加。

当被__block修饰的变量复制到堆后,__forwarding指针如下图所示:

正是因为,栈上和堆上的结构体的_forwarding指针都指向堆上的结构体,所以,无论是在Block语法中、语法外只用__block修饰变量,也无论这个变量是在栈上还是堆上,都可以顺利的访问__blcok变量。

Block截取对象

前面我们说了Block截获的都是普通变量,还未截取对象,block截取对象和普通变量有所区别。

对于ID类型的对象的截取,在Block对应的结构体中,会增加一个该对象的成员变量,并用__strong修饰,这样一来,就可以在超出ID类型对象的声明周期外,成功的调用的这个对象。看下面的代码:

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>




int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        id array = [[NSMutableArray alloc]init];
        
        [^{
            [array add:nil];
        }copy];
        
    }
    return 0;
}


转换成C++代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  id array = __cself->array; // bound by copy

            ((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)array, sel_registerName("add:"), __null);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));

        ((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));

    }
    return 0;
}


我们可以看到在Block对应的结构体struct __main_block_impl_0中,增加了一个成员变量id array;默认修饰符是__strong。

OC的运行时库可以准确的掌握把Block从栈上赋值到堆上的时机,因此即使结构体中含有强引用或者弱引用的成员变量,都可以正确的初始化和废弃。

因此,我们看到上面的代码增加了两个函数:一个__main_block_copy_0和一个__main_block_dispose_0函数。

main_block_main_0函数调用_Blcok_object_assign函数,将ID类型的变量赋值给结构体中的成员变量,并持有该对象。

实际上assign函数相当于调用ID对象的retain函数,增加引用数量。

同时,还有一个__main_block_dispose_0函数,这个函数调用__Block_object_dispose函数,相当于对ID对象进行release操作。

copy函数会在栈上的Block被复制到堆上的时候进行调用;

dispose函数会在堆上的Block销毁时调用。

__blcok 修饰对象

有__block 修饰的对象,对象会转化成struct __Block_byref_array_0,并且相较于普通变量,对象的结构体增加了两个函数void(__Block_byref_id_object_copy)(void, void) 和 void (__Block_byref_id_object_dispose)(void*);这两个函数就是用于对象的内存管理。对对象进行retain或者release

//
//  main.m
//  DemoBlock
//
//  Created by DH on 2017/8/21.
//  Copyright © 2017年 GreedyCat. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        id __block array = [[NSMutableArray alloc]init];
        
        [^{
            [array add:nil];
        }copy];
        
    }
    return 0;
}

转化后的代码:

struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id array;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_array_0 *array; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_array_0 *array = __cself->array; // bound by ref

            ((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)(array->__forwarding->array), sel_registerName("add:"), __null);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"))};

        ((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344)), sel_registerName("copy"));

    }
    return 0;
}


Block循环引用

在ARC模式下,在栈间传递Block,不需要手动调用copy函数,因为ARC会自动的将栈中的Block进行copy,将_NSConcreteStackBlock类型的block转换成了_NSConcreteMallocBlock的block。

在Block循环引用中,要特别注意self隐式引用,例如:

@implementation Person
{
    int _a;
    void (^_block)();
}
- (void)test
{
  void (^_block)() = ^{
        _a = 10;
    };
}

@end

这一段代码转换成C++:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

        (*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
    }
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
  void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};

static void _I_Person_test(Person * self, SEL _cmd) {
  void (*_block)() = (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344);
}


注意观察struct __Person__test_block_impl_0结构体内,强引用了Person *self;

这就会造成循环引用:Person对象持有Block,而Block转化的结构体中右持有self,也就是Person的对象。要解决这个问题,只需要:

弱引用self,也就是

@implementation Person
{
    int _a;
    void (^_block)();
}

- (void)test
{
  void (^_block)() = ^{
  	
	id__weak weakSelf = self;
        weakSelf._a = 10;
    };
}