0%

老生常谈,oc block的实现

Block是什么?

一般来说OC上的语法糖,都可以使用 clang --rewrite-objc code.m 让clang 编译为 c 或 c++ ,由此可以窥探这些语法糖的具体实现。例如,oc的block,@autoreleasepool,@synchronized等等

说明

  1. Block的本质是个C的Struct,所以可以像C的Struct一样使用。
    1
    2
    3
    4
    typedef int (^blk_t) (int)
    blk_t blk = ^(int count){return count;};
    blk_t *blkptr = &blk;
    (*blkptr)(10);
  2. Block 没有实现截获C语言数组,只能通过指针截获。
    1
    2
    3
    4
    5
    //const char text[] = “hello”;编译失败
    const char *text = “hello”;
    void (^blk)(void) = ^{
    text[0];
    };

实验&实现

本文用一些实验,来通过实验现象来了解Block的具体实现

没有任何截获

源码:

clang 重写之后:

可以看到Block的在C语言的实现中是一个struct。struct中记录了Block具体方法实现的imp(即void *FuncPtr)。在调用Block的时候,实际上是调用了这个struct(__block_impl)内存储的函数指针。

截获基本数据类型的自动变量

源码:

clang 重写之后:

如果在Block内截获了外部的基本数据类型,那么这个基本数据类型就会在Block初始化的时候,以当时的“快照”被存储到Block结构体的成员变量内。在调用void *FuncPtr的时候,方法内部使用的数据就是这些被截获的数据。

为什么不支持C语言数组??

1
2
3
4
5
//如果支持C语言数组,那么必然会出现如下代码
//但是在C语言规范上,是不允许把一个数组类型的变量赋给另一个数组变量
void funcA(char a[20]){
char b[20] = a;
}

截获全局变量 && 静态全局变量 && 静态变量

源码:

clang 重写之后:

全局变量和静态全局变量没有什么区别,重写之后访问这些变量的方式依旧不变。

不同的是静态变量,静态变量在Block内会被截获这个静态变量的地址。如果是普通变量,在作用域结束的时候就会随着函数的结束,栈的弹出而销毁。但是静态变量是在程序执行前就分配好的数据区,并不会随着函数作用域而释放,所以Block可以通过截获这个变量的地址来访问这个变量。

截获对象(指针)类型的自动变量

源码:

clang 重写之后:

在Block被copy的时候调用desc0中的__main_block_copy_0持有OC对象

在Block被release的时候调用desc0中的__main_block_dispose_0释放持有的OC对象

截获__block修饰的基本数据类型

源码:

clang 重写之后:

变量 i 变成了一个结构体对象__Block_byref_val_0,并且block_imp中持有的也是这个结构体指针

截获__block修饰的指针类型的自动变量

源码:

clang 重写之后:

__Block_byref 对象新增Copy函数指针和dispose函数指针

__Block_byref 也是一个对象,所以在BlockCopy的时候会调用__Block_byref的copy,同样释放的时候也会调用dispose

注:在ARC无效的时候,byref不会调用对象的retain。(可以被用来在MRC下避免循环引用)

QA.

__block变量超出变量作用域存在的理由?

根据上面带__block变量基本类型变量和指针类型变量的描述,可以解释这个问题

forwarding存在的理由?

这张图来自于(Objective-C高级编程 iOS与OS X多线程和内存管理)

栈上的__block变量的结构体实例,在__block变量从栈上复制到堆上的时候(__Block_byref_id_object_copy_131参数中一个是堆上的,一个是栈上的),会将成员变量__forwarding的值替换为堆上的值。

当Block被Copy的时候就会Copy __Block_byref,并且改变原有 __forwarding 指针

Block的存储域?

  1. 在 ARC 环境下,只要给一个自动变量赋值一个堆栈__NSStackBlock__上block,那么就会调用block的copy
  2. 如果Block不截获任何变量那么生成__NSGlobalBlock__ 存储在数据区
  3. GCD的参数block和Cocoa框架中的UsingBlock传入的参数会被copy
  4. __NSStackBlock__会被copy到__NSMallocBlock__
  5. __NSMallocBlock__copy会增加引用计数
  6. __NSGlobalBlock__copy没有任何影响

为什么说Block是Objc对象?

  1. block 的结构体可以直接转换为oc对象 objc_object的结构体。objc_object中的isa指向的是class,同样的Block也是指向 __NSMallocBlock____NSMallocBlock____NSMallocBlock__class,这是三个class在runtime初始化的时候会被注册到class表中。
  2. block 和 object在同样的内存管理机制下进行创建和释放。