iOS

OC_Block内部实现原理分析

2017-03-14  本文已影响363人  Init_ZSJ

序言:翻阅资料,学习,探究,总结,借鉴,谢谢探路者,我只是个搬运工。
参考、转发资料:
http://www.cnblogs.com/chenxianming/p/5554395.html
http://www.jianshu.com/p/ca6ac0ae93ad#
书籍:Objective-C高级编程

1. Block的本质

Block的本质其实是object对象

2. 内部分析

在百度到的知识基本上都是使用clang(LLVM编译器,和GCC类似)来解析编译的。总结一下自己的理解。
以下代码为例:

int main(int argc, char * argv[]) {
    void (^test)() = ^(){
    };
    test();
}

接下来我要用到一个命令clang src.m -rewrite-objc -o dest.cpp.这个意思是用clang编译器对源文件src.m中的objective-c代码转换成C代码放在dest.cpp文件。其实xcode编译时也会帮我们转换。我们这样就可以dest.cpp在看到我们定义和调用的block转换成C是怎么样的。执行命令后查看这个dest.cpp会发现有一大堆代码。下面我把对我们有用并能够说清楚原理的关键贴上来并加以注释:

//__block_imp:  这个是编译器给我们生成的结构体,每一个block都会用到这个结构体
struct __block_impl {
  void *isa;         //对于本文可以忽略
  int Flags;          //对于本文可以忽略
  int Reserved;       //对于本文可以忽略       
  void *FuncPtr;       //函数指针,这个会指向编译器给我们生成的下面的静态函数__main_block_func_0
};
/*__main_block_impl_0: 
是编译器给我们在main函数中定义的block
void (^test)() = ^(){
};
生成的对应的结构体
*/
struct __main_block_impl_0 {
struct __block_impl impl;          //__block_impl 变量impl
struct __main_block_desc_0* Desc;    //__main_block_desc_0 指针,指向编译器给我们生成的结构体变量__main_block_desc_0_DATA __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {  //结构体的构造函数
    impl.isa = &_NSConcreteStackBlock;  //说明block是栈blockimpl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;}
};
//__main_block_func_0: 编译器根据block代码生成的全局态函数,会被赋值给impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
//__main_block_desc_0: 编译器根据block代码生成的block描述,主要是记录下__main_block_impl_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_desc_0的变量__main_block_desc_0_DATA
//这里就是main函数了
int main(int argc, char * argv[]) {
// 以下代码转换之后,void (* test)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //下面单独讲
//  以下代码转换之后,test->FuncPtr(test);
    ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);                          //下面单独讲
}

3. 获取自动变量的瞬间值

int main(int argc, char * argv[]) {
    int value = 1;
    void (^test)() = ^(){
        int valueTest = value;
    };
    test();
}

如上的内部实现代码:

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;
// 这里是不同于上面的,多出来的,但是其中使用的全局变量(包括全局静态变量)不会生成对应的参数对象。只有自动变量才会。
  int value;               
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int value = __cself->value; // bound by copy
        int valueTest = value;              // 这里是不同于上面的,多出来的
    }
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, char * argv[]) {
    int value = 1;
    void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value));
    ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}

3. __block 的实现原理(下一模块就再对它的作用情况进行细致的讲解)

以下代码为例:

int main(int argc, char * argv[]) {
    __block int value = 1;
    void (^test)() = ^(){
        value = 2;
    };
    test();
    int value1 = value;
}

一如既往,我们看一下添加了_block内部发生了什么变化。

//根据带__block修饰符的变量value,编译器给我们生成了个结构体
struct __Block_byref_value_0 {
  void *__isa;
__Block_byref_value_0 *__forwarding;   //这个会指向被创建出来的__Block_byref_value_0实例
int __flags;
 int __size;
 int value;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_value_0 *value;  //保存__Block_byref_value_0变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__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_value_0 *value = __cself->value; // bound by ref
        // 注意这里
        (value->__forwarding->value) = 2;
    }

//这两个函数分别会在test block 被拷贝到堆和释构时调用的,作用是对__Block_byref_value_0实例的内存进行管理,至于怎么管理,这里就不讨论了,这里就会调用上面导出来的接口。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->value, 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*); //回调函数指针,会被赋值为__main_block_copy_0

 void (*dispose)(struct __main_block_impl_0*);            //回调函数指针,会被赋值为__main_block_dispose_0
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; /*{ 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0},这句就是创建一个例的意思,这是结构体的一种构造方式。*/


int main(int argc, char * argv[]) {
    /*我们定义的__block int value转换后并不是一个简单的栈变量,而会是新建的__Block_byref_value_0堆变量*/
     __attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 0, sizeof(__Block_byref_value_0), 1};

void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_value_0 *)&value, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
  //最后面使这句int value1 = value;使用value时,在我们表面看到是好像是使用main函数里的一个局部栈变量,其实不是,使用的是堆里面的容int value1 = (value.__forwarding->value);
}
1420513789717076.jpg

4. Block的存储区域

  1. Block和_block变量的实质
  1. Block的类:
void (^blk)(void) = ^(printf("HelloWorld")) ;
int main(){
}
/**
     * _Block_copy函数
     * 将栈上的Block复制到堆上
     * 复制后,将堆上的地址作为指针赋值给变量tmp
     */
    tmp = _Block_copy(tmp) ;

注意:Block从栈区copy到堆区是相当消耗CPU的。

不同类型Block执行_Block_copy的图标说明。

Block的类 副本源的配置存储域 赋值效果
__NSConcreteStackBlock 从栈区复制到堆区
__NSConcreteGlobalBlock 程序的数据区域(静态区) 什么都不做
__NSConcreteMallocBlock 堆区 引用计数增加

5. 具体例子的分析,并用以上讲解的知识解释

  1. 为什么在Block方法外修改自动变量不会影响Block内部的使用。
    以下代码为例:
int value = 0 ;
    void (^test)() = ^(){
        // 结果:value = 0
        NSLog(@"value = %d",value) ;
    };
    value = 2 ;
    test();

Block方法获取的是自动变量的瞬间值。

  1. 为什么Block不能直接修改自动变量。
    以下代码为例:
int value = 0 ;
    void (^test)() = ^(){
        // 结果:value = 0
        value = 4 ;
    };
    test();

这段代码在编写编译的时候就会报错,因为自动变量值截获只能保存执行Block语法瞬间的值,保存后就不能改写该值。其实这段代码在编写的规范上没有任何错误,但是因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出给被截获自动变量赋值的操作时,便产生编译错误。

  1. 虽然在Block中不能对自动变量直接修改,但是能对自动变量做其他操作。
    例如:
NSMutableArray *array_m = [NSMutableArray array] ;
    [array_m addObject:@1] ;
    
    void (^test)() = ^(){
        // 结果array_m: (1,2)
        [array_m addObject:@2] ;
        NSLog(@"array_m: %@",array_m) ;
    };
    test();

因为array_m是个指针对象,和Block中value都同时指向同一个类的实例化区域,所以可以在不改变value指针地址的基础上进行操作。但是你要是改变array_m的指针地址例如
array_m = nil 就会出现错误。

  1. 使用_blcok解决不能修改Block外自动变量的值。
    以下代码为例:
__block int value = 0 ;
    void (^test)() = ^(){
        value = 4 ;
        // 结果:value: 4
        NSLog(@"value: %d",value) ;
    };
    test();

我们可以参照上面简述的代码内容:

//根据带__block修饰符的变量value,编译器给我们生成了个结构体
struct __Block_byref_value_0 {
  void *__isa;
__Block_byref_value_0 *__forwarding;   //这个会指向被创建出来的__Block_byref_value_0实例
int __flags;
 int __size;
 int value;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_value_0 *value;  //保存__Block_byref_value_0变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__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_value_0 *value = __cself->value; // bound by ref
        // 注意这里
        (value->__forwarding->value) = 4;
    }

你会发现对了一个value在Block中变成了一个结构体,这就是奥秘,从_main_block_func_0方法来看,你在Block的操作结构体指针永远都不会变和自动变量一致,而是对_Block_byref_value_0的value进行操作,所以可以赋值。

// 方法
int main(int argc, char * argv[]) {
    int value = 1 ;
    void (^test)() = ^(){
      printf(value) ;
    };
      test();
}
// 转换之后__main_block_impl_0结构体中
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 注意这里 value变量不带*
  int value;               
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
- 静态局部变量
// 方法
int main(int argc, char * argv[]) {
    static int value = 1 ;
    void (^test)() = ^(){
      printf(value) ;
    };
      test();
}
// 转换之后__main_block_impl_0结构体中
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 注意这里 value变量带*
  int *value;               
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

二者的区别:value对象,一个带*是指针,一个不带指针是个基本变量类型。

6. Block的循环引用

  1. 说明:
    如果在Block中使用附有_strong修饰符的对象类型自动变量,那么当Block从栈复制到堆区时,该对象为Block所持有,这样容易引起循环引用。
typedef void (^Test)(void);
@interface ViewController ()
{
    Test test ;
}
@end
@implementation ViewController
  - (void)viewDidLoad {
    [super viewDidLoad];
    test = ^{
        NSLog(@"self: %@",self) ;
    } ;
}

执行的Block语法使用附有_strong修饰符的id类型变量self,因此通过Block语法生成在栈上的Block此时由栈区复制到堆区,并持有所使用的self。
如图:

使用Block成员变量循环引用

接下来解释一下上面一句话:

// 转换之前的代码
NSMutableArray *array_m = [[NSMutableArray alloc]init] ;
    void (^test)() = ^(){
        [array_m addObject:@(1)] ;
    };
    test();
// 转换之后的代码,省略部分代码
__main_block_impl_0{
  id _strong array_m ;
}

解释:Block中使用的赋值给附有_strong修饰符的自动变量的对象和复制到堆上的_block变量由于被堆上的Block持有,因此可超出其变量作用域而存在(有点复杂,不做解释)。
解决:用_weak修饰self,来解决循环引用。

  1. 解决方式
typedef void (^Test)(void);
@interface ViewController ()
{
  Test test ;
}
@end
@implementation ViewController
  - (void)viewDidLoad {
  [super viewDidLoad];
  __weak ViewController *weakSelf = self ;
  test = ^{
      NSLog(@"self: %@", weakSelf) ;
  } ;
}
  typedef void (^Test)(void);
  @interface ViewController ()
{
    Test test ;
}
@end
@implementation ViewController
    - (void)viewDidLoad {
    [super viewDidLoad];
    __block ViewController *blockSelf = self ;
    test = ^{
        NSLog(@"self: %@", blockSelf) ;
        // 注意
        blockSelf = nil ;
    } ;
    // 注意
    test() ;
}

注意:需要注意两点。
1、一定要在Block中把_block变量置nil 。
2、一定要执行这个Block方法 。

原理图:

__block避免循环引用.png
  1. __block的特点
-  通过_block变量可控制对象的持有时间。优点
- 为了避免循环引用必须执行Block。缺点
上一篇 下一篇

猜你喜欢

热点阅读