iOS底层原理探索 ----- Block

1,100 阅读25分钟

资源准备

Block 的类型介绍

Block分为三种类型,分别是:GlobalBlockMallocBlockStackBlock

  • GlobalBlock

    • 位于全局区

    • Block内部不能捕获外部变量,或只使用静态变量或全局变量

  • MallocBlock

    • 位于堆区

    • Block内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量

  • StackBlock

    • 位于栈区

    • MallocBlock一样,可以在内部使用局部变量或OC属性。但不能赋值给强引用或Copy修饰的变量

GlobalBlock

typedef void(^KCBlock)(id data);

全局Block,不能捕获外部变量,但可以使用静态变量或全局变量:

C8FC72A9-1E10-4F4D-A7DA-DCDB088343CE.png

  • 使用静态变量、全局变量、Block内部声明的变量,都没有问题。

MallocBlock

堆区Block,内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量:

684268D0-AB31-41EF-81F5-541DE9E8C4F9.png

  • 对外部的a变量进行捕获;

  • 赋值给强引用的block

  • 其中block持有的是堆区Block的内存地址。

StackBlock

栈区Block,和堆区Block的使用大致相同,区别在于不能赋值给强引用或Copy修饰的变量:

4CC6566C-A108-4AB3-A9B3-181331341B9A.png

  • 同样对外部的number变量进行捕获
  • 和堆区Block的区别:赋值给弱引用的block

Block 案例

Block的内存拷贝

  • NSObject+Block.h 7BD4AE2E-A343-4A4F-8ED6-F74D5F73FAE1.png 4196F930-9B0A-44AD-A069-6F78213BD2E0.png
  • NSObject+Block.m

236FC095-C511-404C-8BC0-1CAAD8D4986A.png

然后在控制器里面实现相关案例:

87DEAD4F-F330-4F9C-BDE8-9FE20FC121FC.png 代码解读

  • 第一步,weakBlock为栈区Block

  • 第二步,按Block的底层源码,自定义_LGBlock结构体。只要内存结构一致,即可将Block桥接为自定义对象;

  • 第三步,Block的本质是结构体,将结构体首地址赋值给__strong修饰的对象;

  • 第四步,将结构体的invoke置空,即:Block的函数指针;

  • 第五步,将strongBlock赋值给强引用的strongBlock1,然后对其进行调用。

闪退原因

  • 在第三步中,将结构体首地址赋值给对象,二者指向相同内存空间;

  • 在第四步中,将结构体的invoke置空,修改的是同一片内存空间;

  • 在第五步中,将invoke置空后的Block赋值给strongBlock1,调用时坏地址访问,程序闪退。

修改案例

先将栈区Block赋值给强引用的Block,然后将invoke置空:

1DC0772C-B7A2-4BD8-8AAE-CCE2664D4FA9.png

但依然闪退.

闪退原因Block的赋值只是进行了浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向对象的指针,但两个指针依然指向同一个对象

所以,解决办法,必须在invoke置空之前,将Block进行深拷贝:

4BE3B115-8539-4BD6-9B48-99D660D9A6E8.png

使用lldb,观察两个Blockinvoke置空前后的变化:

363E398C-0693-4933-8DF8-9D05AC15BE8B.png

  • 深拷贝相当于将对象进行复制,产生一个新的对象。并且会递归复制每个指针类型的实例变量,直到两个对象没有任何公共的部分。

所以,栈区Blockinvoke(指针)置空,并不影响堆区Block的调用。

对外部变量的引用计数处理

E97F27B1-66C6-4D46-A619-7EACBB1BC87D.png

代码解读

  • 第一步,objc初始化后的打印1,没有任何问题;

  • 第二步,在strongBlock中打印3,拆分成两步进行分析:

    1、外部objc变量,被栈区Block捕获,引用计数+1

    2、栈区Block赋值给强引用的strongBlock,将栈区Block拷贝到堆区,底层进行深拷贝,引用计数也会+1

  • 第三步,赋值给弱引用的weakBlock,属于栈区Block,仅对外部objc变量进行捕获,引用计数+1

  • 第四步,将栈区Block调用copy方法,赋值给mallocBlock,仅对栈区Block进行了深拷贝,引用计数+1

其实第三步和第四步,等同于第二步的分解,所以打印结果:1、3、4、5

堆栈Block的释放

94ADE33D-5FD5-4F7E-89F1-5BEDEC439FF1.png

代码解读:

  • 第一步,weakBlock使用__weak修饰,赋值为nil

  • 第二步,定义代码块,实现一个堆区Block

  • 第三步,将堆区Block赋值给代码块外面的weakBlock

  • 第四步,在代码块执行完毕后,调用weakBlock

闪退原因:

  • 在第三步中,堆区Block赋值__weak修饰的weakBlock,相当于映射关系;

  • 在第四步中,当代码块执行完毕,strongBlock由于是堆区Block,出了代码块就会被释放。作为映射的weakBlock,自然也会被置为nil。此时对其进行调用,出现坏地址访问,程序闪退。

修改案例

7B1140F5-BA72-49E9-B9B2-C1832F0E6818.png

  • strongBlock使用__weak修饰,即可正常打印。

strongBlock使用__weak修饰后,成为栈区Block。将其赋值给__weak修饰的weakBlock,此时依然是栈区Block。栈区Block的生命周期与代码块无关,依赖于函数栈帧,所以可以正常打印。

Block 拷⻉到堆区

如果Block为全局Block,使用任何方式都不会拷贝到堆区,即使手动copy也没用,它依然是全局Block

E2A34EE9-E504-483B-906B-6B917E174569.png

除此之外,以下四种操作,系统会将Block复制到堆上:

  • 手动Copy

  • Block作为返回值;

  • 被强引用或Copy修饰;

  • 系统API包含usingBlock

Block作为返回值

由于栈区Block所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈区copy到堆区。例如:当Block作为函数返回值的时候,肯定会copy到堆区。

系统API包含usingBlock

Block为函数参数时,需要将其手动copy到堆区。但系统API我们不需要处理,比如GCD中携带的usingBlock方法。其他自定义方法传递Block为参数时,都需要进行手动copy

循环引用

对比以下两种Block代码:

59AEC1D4-CD92-4708-B00E-DFA8FDED8DDB.png

  • Block1会出现循环引用,因为blockself持有,而block中使用self,所以self又被block持有。这种相互持有的情况下,就会出现循环引用。

  • Block2不会出现循环引用,因为block的持有者是UIView,和self无关,不会出现相互持有的情况,所以不会循环引用。

对象正常释放的过程:

image-1.png

  • A持有BBretainCount进行+1
  • A触发dealloc时,会给B放信号,BretainCount进行-1。此时B的retainCount如果为0,就会调用dealloc,正常释放。

对象循环引用的过程:

image.png

  • AB相互持有时,Adealloc无法触发,因为A要等B放信号才能对retainCount进行-1

  • 但是Bdealloc也无法触发,因为B也在等待A的信号。此时AB都在等待对方的释放,最终出现循环引用。

避免循环引用的方式:

  • weak-strong-dance

  • Block中强行切断持有者;

  • 将持有者作为Block参数进行传递和使用。

weak-strong-dance

__weak typeof(self) weakSelf = self; 

self.block = ^{ 
    __strong typeof(self) strongSelf = weakSelf;     
    NSLog(@"%@",strongSelf.name); 
};
  • self赋值给__weak修饰的weakSelf,此时weakSelf属于self的映射,指向同一片内存空间,并且self的引用计数不会发生变化;

  • Block中将weakSelf赋值给__strong修饰的strongSelf,避免self提前释放导致访问为nil的情况;

  • 因为strongSelf为临时变量,在Block作用域结束后,即可自动释放,因此不会循环引用。

Block中强行切断持有者

__block ViewController *vc = self; 

self.block = ^{ 
    NSLog(@"%@",vc.name);
    vc = nil; 
};

self.block();
  • 使用__block修饰对象,否则vc无法改变,也就是说无法置为nil

  • Block中使用结束,手动将对象置为nil。相当于手动切断持有关系,可以避免循环引用;

  • 缺陷:这种方式Block必须调用,否则将无法手动切断持有关系,selfblock都无法释放,最终出现循环引用。

将持有者作为Block参数进行传递和使用

self.vcBlock = ^(ViewController *vc){ 
    NSLog(@"%@",vc.name);
};

self.vcBlock(self);
  • self作为参数,提供给Block内部使用。当Block执行结束,vc会自动释放,然后相互持有关系的切断,self也会释放;

  • 这种方式,self的是否依赖于Block的执行结束。如果Block中有延迟执行的代码,self的释放也会延迟。

面试题

以下几个案例,是否会出现循环引用?

案例1

static ViewController *staticSelf_; 

- (void)blockWeak_static { 
    __weak typeof(self) weakSelf = self; 
    staticSelf_ = weakSelf; 
}

[self blockWeak_static];
  • 会出现循环引用;

  • self赋值__weak修饰的对象,它们属于映射关系,指向同一片内存空间。当weakSelf赋值全局静态变量,staticSelf_在程序运行过程中不会主动释放,它会持续持有self,所以self也无法释放。

案例2

- (void)block_weak_strong { 
    __weak typeof(self) weakSelf = self; 
    
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf; 
        weakSelf.doStudent = ^{
            NSLog(@"%@", strongSelf); 
        }; 
        
        weakSelf.doStudent();
    }; 
    
    self.doWork(); 
} 

[self block_weak_strong];
  • 会出现循环引用;

  • doWork内部,strongSelf持有的是self。虽然strongSelf是临时变量,但在doStudent中又被持有,导致引用计数+1。在doWork执行完毕后引用计数-1,但doStudent中的持有还存在,所以会出现循环引用。

底层 cpp 分析

Block本质

创建block.c文件,写入以下代码:

#include "stdio.h" 

int main() {
    void(^block)(void) = ^{ 
        printf("LG"); 
    }; 
    
    block(); 
    return 0; 
}

使用xcrun命令,生成block.cpp文件,底层 C++ 文件。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp

打开block.cpp文件,找到main函数:

BAF60810-574D-4050-95F5-39C13A1C0852.png

为了方便阅读,剔除强转代码

int main() {
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); 
    block->FuncPtr(block); 
    
    return 0; 
}
  • 只生成两行代码:

    • 调用__main_block_impl_0函数,传入两个参数,取地址并赋值block

    • 调用blockFuncPtr函数,传入block

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

参数 1

1static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("LG");
}

参数 2

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)};
  • 不难看出,Block的本质是结构体,main函数中调用的是结构体的构造函数。

  • 结构体中包含两个成员变量:

    • __block_impl结构体类型的impl

    • __main_block_desc_0结构体指针类型的Desc

  • 构造函数中生成了flags等于0的默认值,赋值implFlags

  • 参数fpBlock代码块的函数指针,赋值implFuncPtr

  • 参数desc赋值给成员变量Desc

  • 所以main函数中,代码block->FuncPtr(block)就是在对Block进行调用。

  • 由此可见,当Block仅定义不调用执行,不会触发Block中的代码块。

捕获外界变量

代码如下:

9B2A8F12-B1A0-4080-A1FB-1C3059B62347.png

生成block.cpp文件,找到main函数:

int main(){ 

    int a = 8; 
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
    
    block->FuncPtr(block); 
    
    return 0; 
}
  • 代码发生了改变,之前__main_block_impl_0函数的入参变成三个;

  • 增加的第三个参数为外界变量a

找到__main_block_impl_0的定义:

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

参数 1

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a;  // bound by copy
    printf("LG - %d",a);
}

参数 2

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)};
  • 结构体中的成员变量也发生了变化,当捕获外界变量时,在结构体内部,会生成相应的成员变量用来存储;

  • 成员变量a通过结构体的构造函数赋值:a(_a)

  • main函数中调用Bolck,由于捕获外界变量,此时传入的FuncPtr中的block发挥作用:

    • block为自身结构体的指针,将block中的成员变量a赋值给临时变量a,然后对其打印;

    • 临时变量a__cself->a的值相同,但地址不同;

    • 由于a是值拷贝,Bolck的代码块中不能对a的值进行改变,会造成编译器的代码歧义。所以,此时的a是只读的。

  • 捕获外界变量并赋值强引用变量,本该是堆区Block,但结构体中implisa赋值为&_NSConcreteStackBlock,标记为栈区Block。因为在编译时,无法开辟内存空间,所以暂且标记为StackBlock。在运行时,会根据情况将Block拷贝到堆区,然后生成MallocBlock

__block的作用

代码如下:

#include "stdio.h" 
int main() { 
    __block int a = 18;
    void(^block)(void) = ^{ 
        a++;
        printf("LG - %d",a); 
    }; 
    
    block(); 
    return 0;
}

生成block.cpp文件,找到main函数:

int main() { 
    __Block_byref_a_0 a = { 
        0,
        (__Block_byref_a_0 *)&a, 
        0, 
        sizeof(__Block_byref_a_0), 
        18 
    };
    
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344); 
    
    block->FuncPtr(block);
    
    return 0; 
}
  • int类型a,对应生成__Block_byref_a_0结构体。成员变量2,对结构体a取地址,转为结构体指针。

找到__Block_byref_a_0定义:

struct __Block_byref_a_0 { 
    void *__isa; 
    __Block_byref_a_0 *__forwarding; 
    int __flags;
    int __size; 
    int a; 
};
  • 其中__forwarding存储的就是a结构体的地址;

  • 最后的成员变量a存储18的值;

  • 结构体中存储了自身的地址和值。

找到__main_block_impl_0的定义:

struct __main_block_impl_0 { 
    struct __block_impl impl; 
    struct __main_block_desc_0* Desc;
    __Block_byref_a_0 *a; // by ref 
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { 
        impl.isa = &_NSConcreteStackBlock; 
        impl.Flags = flags; 
        impl.FuncPtr = fp; 
        Desc = desc; 
    } 
};

参数 1

static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 
    __Block_byref_a_0 *a = __cself->a; // bound by ref 
    
    (a->__forwarding->a)++; 
    printf("LG - %d",(a->__forwarding->a));
}

参数 2

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


//参数2使用的copy和dispose函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
} 

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
  • 局部变量a__cself->a指针地址相同,它的值一旦改变,相当于对外界变量的值进行修改;

  • 没有__block修饰,属于值拷贝,也就是深拷贝。拷贝的值不可更改,它们指向不同的内存空间;

  • 使用__block修饰,属于地址拷贝,也就是浅拷贝。生成的对象指向同一片内存空间,内部修改等同于对外界变量的修改。

汇编分析

流程分析

搭建App项目,写入以下代码:

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    
    __block NSObject *objc = [NSObject alloc];
    void (^block)(void) = ^{ 
        NSLog(@"LG_Block %@ ",objc); 
    };
    
    block(); 
}

针对block的定义设置断点,运行项目,查看汇编代码:

AB820AF2-7231-47FE-99B4-7B06E297E4DE.png

在项目中,设置objc_retainBlock符号断点:

709691C6-0647-435D-AEAD-24616090FFE7.png

  • 来自libobjc.A.dylib框架。

打开objc4-818.2源码,找到objc_retainBlock函数:

id objc_retainBlock(id x) { 
    return (id)_Block_copy(x); 
}
  • 调用_Block_copy函数,但是objc源码中找不到它的实现

在项目中,设置_Block_copy符号断点:

21770810-2D5B-4CD8-AF46-08A8D05283F7.png

来自libsystem_blocks.dylib框架,但该框架暂未开源,可以在libclosure替代工程中查看。

Block结构

通过cpp分析,Block的本质是结构体。在libclosure-79源码中,来到_Block_copy函数,可以找到Block的真实类型:Block_layout

找到Block_layout的定义:

C2D31780-873A-4495-959E-D320D3233CCC.png

  • isa:标示Block类型的类;

  • flags:标识符,按bit位表示Block的附加信息,类似于isa中的位域;

  • reserved:预留位置;

  • invoke:函数指针,指向Block实现的调用地址;

  • descriptor:附加信息,例如:存储保留变量数、Block的大小、进行copydispose的函数指针。

找到flags的标示:

enum {
    BLOCK_DEALLOCATING = (0x0001), // runtime
    BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 
    BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler 
    
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED 
    BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler 
#endif 

    BLOCK_IS_NOESCAPE = (1 << 23), // compiler 
    BLOCK_NEEDS_FREE = (1 << 24), // runtime  
    BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler  
    BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code 
    BLOCK_IS_GC = (1 << 27), // runtime 
    BLOCK_IS_GLOBAL = (1 << 28), // compiler 
    BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE 
    BLOCK_HAS_SIGNATURE = (1 << 30), // compiler 
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler 
};
  • BLOCK_DEALLOCATING:释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该Block可释放;

  • BLOCK_REFCOUNT_MASK:存储引用引用计数的值,是一个可选用参数;

  • BLOCK_NEEDS_FREE低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值;

  • BLOCK_HAS_COPY_DISPOSE:是否拥有拷贝辅助函数,用于拷贝到堆区,决定block_description_2

  • BLOCK_HAS_CTOR:是否拥有BlockC++析构函数;

  • BLOCK_IS_GC:标志是否有垃圾回收,OSX

  • BLOCK_IS_GLOBAL:标志是否是全局Block

  • BLOCK_USE_STRET:与BLOCK_HAS_SIGNATURE相对,判断是否当前Block拥有一个签名,用于runtime时动态调用;

  • BLOCK_HAS_SIGNATURE:是否有签名;

  • BLOCK_HAS_EXTENDED_LAYOUT:是否有拓展,决定block_description_3

运行时Copy

进入_Block_copy函数的汇编代码,读取 x0 寄存器的值,并对其进行打印:

image-1.png

  • 进入_Block_copy函数,当前Block标记为StackBlock

直接运行到函数结尾,读取返回值x0寄存器,并对其进行打印:

image.png

  • 程序运行时, 当前Block符合MallocBlock的条件,经过_Block_copy函数,会将Block复制到堆区。

Block调用

_Block_copy函数执行完毕,回到viewDidLoad方法,继续进行Block的调用:

image.png

  • ldr x8, [x0, #0x10]x0为当前Block,读取x0 + 16字节的值,赋值给x8

    • Block_layout结构体中,首地址偏移16字节,相当于跳过isaflagsreserved,读取到invoke函数地址,赋值给x8
  • blr x8:跳转到invoke函数地址,相当于Block的调用。

descriptor

_Block_copy函数执行完毕,打印当前BlockMallocBlock

image.png

  • 同时还打印出signaturecopydispose等数据。

descriptor类型

signaturecopydispose等数据,存储在Block_layout结构体的descriptor

descriptor分为三种类型:

  • Block_descriptor_1

  • Block_descriptor_2

  • Block_descriptor_3

其中Block_descriptor_1一定存在,Block_descriptor_2Block_descriptor_3为可选

找到descriptor的定义:

  • Block_descriptor_1:存储预留字段和Block大小
#define BLOCK_DESCRIPTOR_1 1 
struct Block_descriptor_1 { 
    uintptr_t reserved;
    uintptr_t size; 
};
  • Block_descriptor_2:存储copydispose的函数指针
#define BLOCK_DESCRIPTOR_2 1 
struct Block_descriptor_2 { 
    // requires BLOCK_HAS_COPY_DISPOSE 
    BlockCopyFunction copy;
    BlockDisposeFunction dispose; 
};
  • Block_descriptor_3:存储signature签名和layout
#define BLOCK_DESCRIPTOR_3 1 
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE 
    const char *signature; 
    const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

Block_descriptor_1的读取:

static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock) {
    return aBlock->descriptor; 
}
  • 由于Block_descriptor_1一定存在,直接通过Blockdescriptor读取。

Block_descriptor_2的读取:

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock) { 
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL; 
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1); 
    return (struct Block_descriptor_2 *)desc;
}
  • 通过位与运算,判断Block_descriptor_2不存在,返回NULL

  • 否则,读取Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址;

  • 强转为Block_descriptor_2的结构体指针返回。

Block_descriptor_3的读取:

static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock) { 
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL; 
    uint8_t *desc = (uint8_t *)aBlock->descriptor; 
    desc += sizeof(struct Block_descriptor_1); 
    
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) { 
        desc += sizeof(struct Block_descriptor_2); 
    } 
    
    return (struct Block_descriptor_3 *)desc;
}
  • 通过位与运算,判断Block_descriptor_3不存在,返回NULL

  • 否则,读取Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址;

  • 通过位与运算,判断Block_descriptor_2是否存在:

    • 存在,再偏移Block_descriptor_2大小,即:Block_descriptor_3的首地址;

    • 不存在,强转为Block_descriptor_3的结构体指针返回。

因为Block_descriptor_2Block_descriptor_3的内存结构相同,所以偏移Block_descriptor_1之后的地址,即可强转为2,可强转为3

lldb验证descriptor

通过flags进行位与运算,可以得知Block_descriptor_2Block_descriptor_3是否存在。

使用x/8g命令,输出Block的内存结构:

(lldb) x/8g 0x283aaa430 
0x283aaa430: 0x00000001de1c0880 0x00000000c3000002
0x283aaa440: 0x00000001021422ac 0x0000000102144018 
0x283aaa450: 0x0000000283aaa400 0x0000000000000000 
0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
  • 0xc3000002Blockflags标识符。

验证Block_descriptor_2

//BLOCK_HAS_COPY_DISPOSE = (1 << 25) 

(lldb) p/x 1 << 25 
(int) $7 = 0x02000000 

(lldb) p/x (0xc3000002 & 0x02000000) 
(unsigned int) $8 = 0x02000000
  • 运算结果不为0,证明Block_descriptor_2存在。

验证Block_descriptor_3

//BLOCK_HAS_SIGNATURE = (1 << 30)

(lldb) p/x 1 << 30 
(int) $10 = 0x40000000 

(lldb) p/x (0xc3000002 & 0x40000000) 
(unsigned int) $16 = 0x40000000
  • 运算结果不为0,证明Block_descriptor_3存在。

Block签名

lldb验证签名

使用x/8g命令,输出Block的内存结构:

(lldb) x/8g 0x283aaa430
0x283aaa430: 0x00000001de1c0880 0x00000000c3000002 
0x283aaa440: 0x00000001021422ac 0x0000000102144018 
0x283aaa450: 0x0000000283aaa400 0x0000000000000000 
0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
  • 首地址平移24字节0x0000000102144018就是descriptor的结构体指针。

使用x/8g命令,输出descriptor的内存结构:

(lldb) x/8g 0x0000000102144018 
0x102144018: 0x0000000000000000 0x0000000000000028
0x102144028: 0x00000001021422e0 0x00000001021422f0 
0x102144038: 0x00000001021433e3 0x0000000000000010 
0x102144048: 0x00000001de78a280 0x00000000000007c8
  • 0x00000001021422e0copy函数指针;

  • 0x00000001021422f0dispose函数指针;

  • 0x00000001021433e3signature签名。

0x00000001021433e3进行强转输出:

(lldb) po (char *)0x00000001021433e3
"v8@?0"

签名含义

对于签名v8@?0的解释:

  • v:返回值类型viod

  • 8:方法所占用的内存8字节

  • @?参数0类型

  • 0参数0的起始位置,从0字节开始

签名的详细信息,可以使用NSMethodSignaturesignatureWithObjCTypes方法输出:

(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] <NSMethodSignature: 0xbb2001894a750adf>
    number of arguments = 1
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) 'v'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
     argument 0: -------- -------- -------- --------
        type encoding (@) '@?'
        flags {isObject, isBlock}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} 
        memory {offset = 0, size = 8}
  • number of arguments = 1:表示传入一个参数;

  • is special struct return? NO:没有返回值;

  • return value:返回值:

    • type encoding (v) 'v'void类型的缩写;

    • memory {offset = 0, size = 0}:无返回值,故此size0

  • argument 0参数0

  • type encoding (@) '@?':其中@id类型,?未知类型;

  • flags {isObject, isBlock}:即是Object类型,也是Block类型;

  • memory {offset = 0, size = 8}参数00字节开始,占8字节

源码分析

使用替代工程libclosure进行源码分析:

Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝。

_Block_copy

进入_Block_copy函数:

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock; 
    
    // 如果 arg 为 NULL,直接返回 NULL
    if (!arg) return NULL; 
    
    // The following would be better done as a switch statement 
    // 强转为 Block_layout 类型 
    aBlock = (struct Block_layout *)arg;
    
    // 如果现在已经在堆上 
    if (aBlock->flags & BLOCK_NEEDS_FREE) { 
        // latches on high 
        // 就只将引用计数加 1 
        latching_incr_int(&aBlock->flags); 
        return aBlock; 
    } 
    // 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身 
    else if (aBlock->flags & BLOCK_IS_GLOBAL) { 
        return aBlock; 
    }
    else {// 栈 - 堆 (编译期)
        // Its a stack block. Make a copy. 
        // block 现在在栈上,现在需要将其拷贝到堆上 
        // 在堆上重新开辟一块和 aBlock 相同大小的内存 
        size_t size = Block_size(aBlock);
        struct Block_layout *result = (struct Block_layout *)malloc(size); 
        // 开辟失败,返回 NULL 
        if (!result) return NULL;
        
        // 将 aBlock 内存上的数据全部复制新开辟的 result 上 
        memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls) 
        // Resign the invoke pointer as it uses address authentication. 
        result->invoke = aBlock->invoke;
        
#if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) { 
            uintptr_t oldDesc = ptrauth_blend_discriminator( &aBlock->descriptor, _Block_descriptor_ptrauth_discriminator); 
            uintptr_t newDesc = ptrauth_blend_discriminator( &result->descriptor, _Block_descriptor_ptrauth_discriminator); 
            
            result->descriptor = ptrauth_auth_and_resign(aBlock->descriptor, ptrauth_key_asda, oldDesc, ptrauth_key_asda, newDesc); 
        }
#endif
#endif
      // reset refcount 
      // 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0 
      result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed 
      // 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1 
      result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1 
      // copy 方法中会调用做拷贝成员变量的工作 
      _Block_call_copy_helper(result, aBlock); 
      
      // Set isa last so memory analysis tools see a fully-initialized object. 
      // isa 指向 _NSConcreteMallocBlock 
      result->isa = _NSConcreteMallocBlock; 
      return result;
    } 
}
  • _Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区;

  • 参数arg就是Block_layout对象;

  • 如果原来就在堆上,就将引用计数+1;

  • 如果Block在全局区,不用加引用计数,也不用拷贝,直接返回Block本身;

  • 如果原来在栈上,会拷贝到堆上,引用计数初始化为1,并且会调用_Block_call_copy_helper方法(如果存在的话);

  • 返回值是拷贝后Block的地址。

_Block_object_assign

cpp分析

打开cpp文件,找到声明Block时的第二个参数,__main_block_desc_0_DATA结构体的定义:

//void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344); 

__main_block_desc_0_DATA = { 
    0, 
    sizeof(struct __main_block_impl_0), 
    __main_block_copy_0, 
    __main_block_dispose_0 
};
  • __main_block_copy_0Block_descriptor_2中的copy

  • __main_block_dispose_0Block_descriptor_2中的dispose

找到__main_block_copy_0的定义:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { 

    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); 
}
  • 调用_Block_object_assign函数。

lldb分析

使用x/8g命令,输出Block的内存结构:

(lldb) x/8g 0x000000028167eca0 
0x28167eca0: 0x00000001de1c0880 0x00000000c3000002 
0x28167ecb0: 0x0000000104f1a2ac 0x0000000104f1c018 
0x28167ecc0: 0x000000028167ec70 0x0000000000000000
0x28167ecd0: 0x000021a1de1c6221 0x0000000000000000

使用x/8g命令,输出descriptor的内存结构:

(lldb) x/8g 0x0000000104f1c018
0x104f1c018: 0x0000000000000000 0x0000000000000028 
0x104f1c028: 0x0000000104f1a2e0 0x0000000104f1a2f0 
0x104f1c038: 0x0000000104f1b3e3 0x0000000000000010 
0x104f1c048: 0x00000001de78a280 0x00000000000007c8
  • 0x0000000104f1a2e0copy函数指针

使用dis -s读取汇编代码:

(lldb) dis -s 0x0000000104f1a2e0
004-Block结构与签名`__copy_helper_block_e8_32r: 
    0x104f1a2e0 <+0>: add x0, x0, #0x20 ; =0x20 
    0x104f1a2e4 <+4>: ldr x1, [x1, #0x20]
    0x104f1a2e8 <+8>: mov w2, #0x8 
    0x104f1a2ec <+12>: b 0x104f1a484 ; symbol stub for: _Block_object_assign
  • 调用_Block_object_assign函数。

调用时机

_Block_copy函数中,调用_Block_call_copy_helper函数

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock) { 
    if (auto *pFn = _Block_get_copy_function(aBlock)) pFn(result, aBlock); 
}

进入_Block_get_copy_function函数

static inline __typeof__(void (*)(void *, const void *)) _Block_get_copy_function(struct Block_layout *aBlock) { 
    if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) 
        return NULL;
    
    void *desc = _Block_get_descriptor(aBlock);
    
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED 
    if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
        struct Block_descriptor_small *bds = (struct Block_descriptor_small *)desc; 
        
        return _Block_get_relative_function_pointer( bds->copy, void (*)(void *, const void *)); 
    } 
#endif

    struct Block_descriptor_2 *bd2 = (struct Block_descriptor_2 *)((unsigned char *)desc + sizeof(struct Block_descriptor_1)); 
    
    return _Block_get_copy_fn(bd2);
}  
  • 如果存在copydispose,通过内存获取Block_descriptor_2的结构体指针;

  • 调用_Block_get_copy_fn函数,传入bd2.

进入_Block_get_copy_fn函数:

static inline __typeof__(void (*)(void *, const void *)) _Block_get_copy_fn(struct Block_descriptor_2 *desc) {
    return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy); 
}
  • 经过处理返回Block_descriptor_2下的copy函数。

最终,回到_Block_call_copy_helper函数,将copy函数地址赋值pFn,然后通过pFn(result, aBlock)对其进行调用。

源码分析

在源码中,找到Block捕获外界变量的种类

enum { 
    // see function implementation for a more complete description of these fields and combinations 
    BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ... 
    BLOCK_FIELD_IS_BLOCK = 7, // a block variable 
    BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers 
    BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines. 
};
  • BLOCK_FIELD_IS_OBJECT:普通对象类型;

  • BLOCK_FIELD_IS_BLOCKBlock类型作为变量;

  • BLOCK_FIELD_IS_BYREF:使用__block修饰的变量;

  • BLOCK_FIELD_IS_WEAKweak弱引用变量;

  • BLOCK_BYREF_CALLER:返回的调用对象,处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用。

进入_Block_object_assign函数:

// 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment 
// 参数 destAddr 其实是一个二级指针,指向真正的目标指针

void _Block_object_assign(void *destArg, const void *object, const int flags) { 
    const void **dest = (const void **)destArg; 
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /******* 
        id object = ...; 
        [^{ object; } copy]; 
        ********/
        
        // _Block_retain_object_default = fn (arc) 
        // 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数 
        // 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的 
        // 可以理解为交给系统 ARC 处理
        _Block_retain_object(object); 
        // 使 dest 指向的目标指针指向 
        object *dest = object; 
        break;
        
      case BLOCK_FIELD_IS_BLOCK: 
        /******* 
        void (^object)(void) = ...; 
        [^{ object; } copy];
        ********/ 
      
        // 使 dest 指向拷贝到堆上object 
        *dest = _Block_copy(object); 
        break;
      
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /******* 
          // copy the onstack __block container to the heap 
          // Note this __weak is old GC-weak/MRC-unretained.
          // ARC-style __weak is handled by the copy helper directly. 
          __block ... x; 
          __weak __block ... x; 
          [^{ x; } copy];
         ********/
        
        // 使 dest 指向拷贝到堆上的byref 
        *dest = _Block_byref_copy(object); 
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: 
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /******* 
        // copy the actual field held in the __block container 
        // Note this is MRC unretained __block only. 
        // ARC retained __block is handled by the copy helper directly. 
        __block id object; 
        __block void (^object)(void); 
        [^{ object; } copy]; 
        ********/
        
        // 使 dest 指向的目标指针指向 object 
        *dest = object; 
        break;
     
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
        /******* 
        // copy the actual field held in the __block container
        // Note this __weak is old GC-weak/MRC-unretained. 
        // ARC-style __weak is handled by the copy helper directly. 
        __weak __block id object;
        __weak __block void (^object)(void); 
        [^{ object; } copy];
        ********/
        
        // 使 dest 指向的目标指针指向 object 
        *dest = object;
        break;
        
      default: 
          break; 
      }
  }
  • 普通对象类型,交给系统ARC处理,使dest指向的目标指针指向object

  • Block类型作为变量,调用_Block_copy函数,使dest指向拷贝到堆上object

  • 使用__block修饰的变量,调用_Block_byref_copy函数,使dest指向拷贝到堆上的byref

_Block_byref_copy

进入_Block_byref_copy函数:

// 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3 
// 被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable
// 原来 byref 的 forwarding 也会指向堆上的 byref; 
// 2. 如果 byref 已经在堆上,就只增加一个引用计数。
static struct Block_byref *_Block_byref_copy(const void *arg) {
    // arg 强转为 Block_byref * 类型 
    struct Block_byref *src = (struct Block_byref *)arg;
    
    // 引用计数等于 0 
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack 
        // 为新的 byref 在堆中分配内存 
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size); 
        copy->isa = NULL; 
        
        // byref value 4 is logical refcount of 2: one for caller, one for stack 
        // 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。 
        // 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack 
        // one for caller 很好理解,那 one for stack 是为什么呢? 
        // 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 
        copy copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4; 
        
        // 堆上 byref 的 forwarding 指向自己
        copy->forwarding = copy; // patch heap copy to point to itself 
        // 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref 
        src->forwarding = copy; // patch stack to point to heap copy 
        // 拷贝 size 
        copy->size = src->size;
        
        // 如果 src 有 copy/dispose helper 
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest 
            // If more than one field shows up in a byref block this is wrong XXX 
            // 取得 src 和 copy 的 Block_byref_2 
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1); 
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1); 
            
            // copy 的 copy/dispose helper 也与 src 保持一致 
            // 因为是函数指针,估计也不是在栈上,所以不用担心被销毁 
            copy2->byref_keep = src2->byref_keep; 
            copy2->byref_destroy = src2->byref_destroy;
            
            // 如果 src 有扩展布局,也拷贝扩展布局
            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1); 
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1); 
                
                // 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上 
                copy3->layout = src3->layout;
            }
            
            // 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数 
            // 发起第三层拷贝 
            (*src2->byref_keep)(copy, src);
        } else { 
            // Bitwise copy. 
            // This copy includes Block_byref_3, if any. 
            // 如果 src 没有 copy/dispose helper 
            // 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3 
            memmove(copy+1, src+1, src->size - sizeof(*src)); 
        }
    }
    
    // already copied to heap 
    // src 已经在堆上,就只将引用计数加 1 
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) { 
        latching_incr_int(&src->forwarding->flags); 
    }
    
    return src->forwarding;
}      
  • Block_byref进行拷贝,属于三层拷贝中的第二层;

  • 如果引用计数为0,为新的byref在堆中分配内存:

    • 将堆上byrefforwarding指向自己;

    • 将原来栈上的byrefforwarding也指向堆上的byref

    • byref_keep发起Block的第三层拷贝。

  • 如果已经在堆上,就只将引用计数+1

byref_keep分析

找到byref_keep的定义:

struct Block_byref {
    void *isa; 
    struct Block_byref *forwarding; 
    volatile int32_t flags; // contains ref count
    uint32_t size; 
};

struct Block_byref_2 { 
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE 
    
    BlockByrefKeepFunction byref_keep; 
    BlockByrefDestroyFunction byref_destroy; 
};

struct Block_byref_3 { 
    // requires BLOCK_BYREF_LAYOUT_EXTENDED 
    const char *layout;
};

对应cpp文件查看:

struct __Block_byref_objc_0 { 
    void *__isa; 
    __Block_byref_objc_0 *__forwarding; 
    int __flags; 
    int __size; 
    void (*__Block_byref_id_object_copy)(void*, void*); 
    void (*__Block_byref_id_object_dispose)(void*); 
    NSObject *objc; 
};
  • 源码中的byref_keep,对应cpp中的__Block_byref_id_object_copy

找到__Block_byref_id_object_copy的赋值:

__Block_byref_objc_0 objc = {
    (void*)0, 
    (__Block_byref_objc_0 *)&objc, 
    33554432, 
    sizeof(__Block_byref_objc_0), 
    __Block_byref_id_object_copy_131, 
    __Block_byref_id_object_dispose_131,
    ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"))
};
  • 传入__Block_byref_id_object_copy_131

找到__Block_byref_id_object_copy_131的定义:

static void __Block_byref_id_object_copy_131(void *dst, void *src) { 
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); 
}

对应源码查看传入的参数:

(*src2->byref_keep)(copy, src);
  • 传入堆上的Block_byref,对应cpp中的__Block_byref_objc_0结构体。

byref_keep的结论

调用byref_keep,底层又调用一次_Block_object_assign函数

(char*)dst + 40参数,结构体内存平移,传入的就是objc实例对象

进入_Block_object_assign函数,命中普通对象类型的copy逻辑

将对象拷贝到堆区,完成Block的第三层拷贝

Block三层拷贝的结论

Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝

  • 【第一层】_Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区

    • 通过_Block_copy_Block_call_copy_helper,调用_Block_object_assign函数

    • 传入Block_byref结构体指针,类型为BLOCK_FIELD_IS_BYREF,调用_Block_byref_copy函数

  • 【第二层】_Block_byref_copy函数,将Block_byref拷贝到堆区

    • 通过byref_keep函数,调用_Block_object_assign函数,传入objc实例对象
  • 【第三层】_Block_object_assign函数,将捕获的外界变量拷贝到堆区

Block释放

如果Block在堆上,需要进行release。在全局区和栈区的Block,都不需要release

 _Block_release

进入_Block_release函数:

// block 在堆上,才需要 release,在全局区和栈区都不需要 release. 
// 先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁 
void _Block_release(const void *arg) { 
    struct Block_layout *aBlock = (struct Block_layout *)arg; 
    
    // 如果 block == nil 
    if (!aBlock) return; 
    
    // 如果 block 在全局区 
    if (aBlock->flags & BLOCK_IS_GLOBAL) return; 
    
    // block 不在堆上 
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return; 
    
    // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 block 需要被销毁 
    if (latching_decr_int_should_deallocate(&aBlock->flags)) { 
        // 调用 block 的 dispose helper,dispose helper 方法中会做诸如销毁 byref 等操作 
        _Block_call_dispose_helper(aBlock); 
        
        // _Block_destructInstance 啥也不干,函数体是空的 
        _Block_destructInstance(aBlock); free(aBlock); 
    } 
}
  • _Block_copy相似,通过_Block_call_dispose_helper函数,调用_Block_object_dispose函数。

_Block_object_dispose

进入_Block_object_dispose函数:

// 当 block 和 byref 要 dispose 对象时,它们的 dispose helper 会调用这个函数 
void _Block_object_dispose(const void *object, const int flags) { 
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
        // 如果是 byref 
        case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: 
        case BLOCK_FIELD_IS_BYREF: 
        
          // get rid of the __block data structure held in a Block 
          // 对 byref 对象做 release 操作 
          _Block_byref_release(object); 
          break; 
          
        case BLOCK_FIELD_IS_BLOCK: 
          // 对 block 做 release 操作 
          _Block_release(object); 
          break;
        
        case BLOCK_FIELD_IS_OBJECT: 
          // 默认啥也不干,但在 _Block_use_RR() 中可能会被 Objc runtime 或者 CoreFoundation 设置一个 release 函数,里面可能会涉及到 runtime 的引用计数 
          _Block_release_object(object); 
          break; 
        
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK: 
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
          break; 
          
        default:
          break; 
      } 
  }
  • 普通对象类型,交给系统ARC处理;

  • Block类型作为变量,调用_Block_release函数;

  • 使用__block修饰的变量,调用_Block_byref_release函数,对byref对象做release操作。

_Block_byref_release

进入_Block_byref_release函数:

// 对 byref 对象做 release 操作, 
// 堆上的 byref 需要 release,栈上的不需要 release,
// release 就是引用计数减 1,如果引用计数减到了 0,就将 byref 对象销毁 
static void _Block_byref_release(const void *arg) { 
    struct Block_byref *byref = (struct Block_byref *)arg;
    
    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?) 
    // 取得真正指向的 byref,如果 byref 已经被堆拷贝,则取得是堆上的 byref,否则是栈上的,栈上的不需要 release,也没有引用计数
    byref = byref->forwarding; 
    
    // byref 被拷贝到堆上,需要 release 
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) { 
        // 取得引用计数
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK; 
        os_assert(refcount);
        
        // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 byref 需要被销毁 
        if (latching_decr_int_should_deallocate(&byref->flags)) { 
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { 
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                
                // dispose helper 藏在 Block_byref_2 里 
                (*byref2->byref_destroy)(byref); 
            } 
            
            free(byref);
        }
    } 
}
  • _Block_byref_copy相似,由byref_destroy发起对象的release

对应cpp代码:

static void __Block_byref_id_object_dispose_131(void *src) { 
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
  • 调用_Block_object_dispose函数,传入objc实例对象。

  • 进入_Block_object_dispose函数,命中普通对象类型的release逻辑。