Swift开发

block 底层原理

2021-09-08  本文已影响0人  HotPotCat

一、block 简介

1.1 block 分类

验证:

void (^globalBlock)(void) = ^{
    NSLog(@"globalBlock");
};

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

void (^__weak stackBlock)(void) = ^{
    NSLog(@"stackBlock - %d",a);
};

NSLog(@"globalBlock: %@\nmallocBlock: %@\nstackBlock: %@",globalBlock, mallocBlock, stackBlock);

输出:

globalBlock: <__NSGlobalBlock__: 0x10982a050>
mallocBlock: <__NSMallocBlock__: 0x6000011c1950>
stackBlock: <__NSStackBlock__: 0x7ffee63d60c0>

ARC下需要使用__weak修饰才是stackBlockblock持有的是^{}的指针,指向它的内存空间。

1.2 block 拷⻉到堆 block

⚠️ 前提是捕获了局部变量或者OC属性。

NSObject *objc = [NSObject alloc];
void(^ __weak weakBlock)(void) = ^{
    NSLog(@"%@",objc);
};
//copy
NSLog(@"weakBlock: %@,weakBlock copy: %@",weakBlock,[weakBlock copy]);
//weakBlock: <__NSStackBlock__: 0x7ffee3a8e5c8>,weakBlock copy: <__NSMallocBlock__: 0x60000316d860>
- (void(^)(void))returnBlock {
     int a = 0;
    return ^{
        NSLog(@"%d",a);
    };
}
//调用
NSLog(@"return block: %@",[self returnBlock]);

//输出
return block: <__NSMallocBlock__: 0x600002ad0f60>

目前在ARC下在运行时默认会自动copy成堆block,在MRC下返回栈block会直接报错,需要copy到堆区再返回:

image.png
void(^strongBlock)(void) = ^{
    NSLog(@"%@",objc);
};
- (void)block_copy_usingBlock {
    NSArray *arrar = @[@1,@2,@3];
    void (^usingBlock)(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) = ^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
    };
    [arrar enumerateObjectsUsingBlock:usingBlock];
    NSLog(@"%@",usingBlock);
}

这个时候定义的usingBlock是全局block,输出也是。不过打断点后底层调用了_Block_copy(最终不会真的copy,全局block就直接返回了):

image.png

当自己定义的函数有block参数时:

int a = 10;
[self block:^{
    NSLog(@"%d",a);
}];
NSLog(@"end");

- (void)block:(void (^)(void))block {
    block();
    NSLog(@"%@",block);
}

block本身是栈block,不会调用_Block_copy。当内部使用NSLog打印block时打印的是堆block,这个时候调用了_Block_copy

image.png

NSLog打印时block转成OC对象进行了Copy

1.3 block 案例分析

1.3.1 block 捕获变量的引用计数

NSObject *objc = [NSObject new];
//1
NSLog(@"retain count: %ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));

//1 + 2 (栈1 + 堆 1)
void(^strongBlock)(void) = ^{
    NSLog(@"retain count: %ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
//栈1
void(^__weak weakBlock)(void) = ^{
    NSLog(@"retain count: %ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
//堆1
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();

输出:

retain count: 1
retain count: 3
retain count: 4
retain count: 5

当将objc使用__weak修饰时:

NSObject *objc = [NSObject new];
//1
NSLog(@"retain count: %ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));

__weak NSObject *weakObjc = objc;
//放入弱引用表中,+1 = 2
NSLog(@"retain count: %ld",CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));

void(^strongBlock)(void) = ^{
    //弱引用表中已经存在,仍然是2
    NSLog(@"retain count: %ld",CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));
};
strongBlock();

void(^__weak weakBlock)(void) = ^{
    //弱引用表中已经存在,仍然是2
    NSLog(@"retain count: %ld",CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));
};
weakBlock();

void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();

输出:

retain count: 1
retain count: 2
retain count: 2
retain count: 2
retain count: 2

weakObjc是一个__weak修饰的变量被放入了弱引用表,本身不会对引用计数产生变化,在调用CFGetRetainCount的时候内部会调用objc_loadWeakRetained对原始对象引用计数+1,然后调用retainCount,最后调用objc_release对引用计数-1,所以输出的引用计数会多1

1.3.2 block 的内存拷贝

int a = 0;
void(^ __weak weakBlock)(void) = ^{
    NSLog(@"%d", a);
};
struct _HPBlock *blc = (__bridge struct _HPBlock *)weakBlock;
//stack  block,只是赋值
id __strong strongBlock = weakBlock;
blc->invoke = nil;
//类型强转
void(^strongBlock1)(void) = strongBlock;
strongBlock1();

_HPBlock是模仿block的底层结构写了一个结构体,block底层就是结构体。主要是为了修改invokeinvoke在底层是一个函数指针指向代码块地址。这样其实能达到hook block的目的。

上面的代码运行会报错:

image.png
因为将blc指向了weakBlock设置invokenil从而导致callout的时候找不到invoke
修改strongBlock如下:
//id __strong strongBlock = weakBlock;
//栈 -> 堆
id __strong strongBlock = [weakBlock copy];

这个时候运行就没有问题了,strongBlock是对weakBlock的一份copy。在拷贝之后对blcinvoke置为nil相当于是对栈中blockinvoke进行的修改,对堆中的strongBlock没有影响。
如果block本身已经在堆中了再对其copy只是引用计数+1,这个时候修改invoke也会影响到堆中copyblock

1.3.3 block 堆栈释放差异

- (void)testBlock3 {
    int a = 10;
    void(^__weak weakBlock)(void) = nil;
    {
        //代码块中 strongBlock 捕获 a
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"a:%d", a);
        };
        //栈block给到weakBlock,weakBlock是栈block
        weakBlock = strongBlock;
    }
    weakBlock();
}

上面的代码运行输出:

a:10

strongBlock是一个栈block捕获了a,然后将strongBlock赋值给weakBlock此时weakBlock也是栈block。出了代码块作用域后调用栈block仍然有用。因为栈block的作用域是函数的栈中由系统自动回收,与代码块无关。
在代码块中代码执行完毕后此时栈中数据为:self - testBlock3 - a - weakBlock。可以通过读取栈中数据进行验证,修改代码如下:

- (void)testBlock3 {
    int a = 10;
    void(^__weak weakBlock)(void) = nil;
    {
        //代码块中 strongBlock 捕获 a
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"a:%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"weakBlock:%@ strongBlock:%@",weakBlock,strongBlock);
    }

    //验证栈中数据
    void *sp  = (void *)&self;
    void *end = (void *)&weakBlock;
    long count = (sp - end) / 0x8;

    for (long i = 0; i <= count; i++) {
        void *address = sp - 0x8 * i;
        if (i == 1) {//SEL
            NSLog(@"%p : %s",address, *(void **)address);
        } else if (i == 2) {//int
            //由于int存储在高32位中,所以右移还原
            NSLog(@"%p : %d",address, *(uint64_t*)(address) >> 32);
        } else {//object
            NSLog(@"%p : %@",address, *(void **)address);
        }
    }
    //end

    weakBlock();
}

int数据只占用4字节,所以右移32位还原。输出:

0x7ffee8852118 : <ViewController: 0x7fad1dc05750>
0x7ffee8852110 : testBlock3
0x7ffee8852108 : 10
0x7ffee8852100 : <__NSStackBlock__: 0x7ffee88520d0>

由于栈中内存分配从高到低,所以这里输出顺序与上面分析完全相反。当然也可以直接lldb调试查看:

image.png

strongBlock__weak修饰去掉:

- (void)testBlock3 {
    int a = 10;
    void(^__weak weakBlock)(void) = nil;
    {
        //代码块中 strongBlock 捕获 a
        void(^strongBlock)(void) = ^{
            NSLog(@"a:%d", a);
        };
        //堆block给到 weakBlock 两个都变为堆block
        weakBlock = strongBlock;
        NSLog(@"weakBlock:%@ strongBlock:%@",weakBlock,strongBlock);
    }
    weakBlock();
}

输出:

weakBlock:<__NSMallocBlock__: 0x600000309050> strongBlock:<__NSMallocBlock__: 0x600000309050>

这个时候运行输出block类型后就崩溃了,因为strongBlock是堆block作用域是代码块中(出了作用域会调用_Block_release)。而weakBlock是弱引用(也是堆block,与strongBlock指向同一块内存空间)指向strongBlock没有强持有strongBlock出了作用域后strongBlock就释放了,weakBlock就为nil了。

image.png

此时修改aNSObject类型的变量objc

- (void)testBlock3 {
    NSObject *objc = [NSObject alloc];
    void(^__weak weakBlock)(void) = nil;
    {
        //代码块中 strongBlock 捕获 objc
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"objc:%@", objc);
        };
        weakBlock = strongBlock;
        NSLog(@"weakBlock:%@ strongBlock:%@",weakBlock,strongBlock);
    }

    //验证栈中数据
    void *sp  = (void *)&self;
    void *end = (void *)&weakBlock;
    long count = (sp - end) / 0x8;

    for (long i = 0; i <= count; i++) {
        void *address = sp - 0x8 * i;
        if (i == 1) {//SEL
            NSLog(@"%p : %s",address, *(void **)address);
        }
        else {//object
            NSLog(@"%p : %@",address, *(void **)address);
        }
    }
    //end
    weakBlock();
}

输出:

weakBlock:<__NSStackBlock__: 0x7ffee5b1d0d0> strongBlock:<__NSStackBlock__: 0x7ffee5b1d0d0>
0x7ffee5b1d118 : <ViewController: 0x7f9128b06980>
0x7ffee5b1d110 : testBlock3
0x7ffee5b1d108 : <NSObject: 0x600003b64350>
0x7ffee5b1d100 : <__NSStackBlock__: 0x7ffee5b1d0d0>
objc:(null)

可以看到此时blockobjc输出null,而objc在栈中是存在的。a是值拷贝,objc是引用计数增加。
如果weakBlockstrongBlock都是强引用那么此时blockobjc是可以输出的。那么说明栈block在捕获的对象在出了作用域后释放了。也就是说如果block是一个assign或者weak修饰的属性,那么捕获的对象也是被释放的。

1.3.4 栈block

- (void)blockHeap {
    int a = 0;
    void(^ __weak block)(void) = ^{
        NSLog(@"---%d", a);
    };
    dispatch_block_t dispatch_block = ^{
        block();
    };
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_block);
}

以上代码在执行后会crash,由于延迟调用的原因出了函数后栈block被释放了。
这个时候可以加个阻塞就不会崩溃了:

- (void)blockHeap {
    int a = 0;
    void(^ __weak block)(void) = ^{
        NSLog(@"---%d", a);
    };
    dispatch_block_t dispatch_block = ^{
        block();
    };
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_block);
//    sleep(3);
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}

那么如果block是一个堆block呢?

- (void)blockHeap {    
    int a = 0;
    void(^block)(void) = ^{
        NSLog(@"---%d", a);
    };
    dispatch_block_t dispatch_block = ^{
        block();
    };
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_block);
}

运行不会有问题。两个的区别是__weak__strongdispatch_block对于这两个的捕获是与修饰符对应的。这也就是block遇弱捕弱,遇强补强

二、block 的循环引用

正常对象的释放流程如下:


image.png

block和对象互相持有时:

image.png

2.1 案例1

typedef void(^HPBlock)(void);

@property (nonatomic, copy) HPBlock block;
@property (nonatomic, copy) NSString *name;

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

以上案例会造成循环引用self -> block -> self。这个时候使用__weak修饰self就可以解决循环引用了:

self.name = @"HotpotCat";
__weak typeof(self) weakSelf = self;
self.block = ^(){
    NSLog(@"%@",weakSelf.name);
};
self.block();

这个时候持有链变成了self -> block -> weakSelf(nil) -> block所以不会造成循环引用。在block内部执行一个延时操作:

self.name = @"HotpotCat";
__weak typeof(self) weakSelf = self;
self.block = ^(){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",weakSelf.name);
    });
};
self.block();

在快速退出页面后,打印的时候self已经释放了。这个时候需要在block内部对weakSelf进行强引用:

self.name = @"HotpotCat";
__weak typeof(self) weakSelf = self;
self.block = ^(){
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.name);
    });
};
self.block();

这个时候持有链变成了self -> block -> strongSelf(临时变量) —> weakSelf(nil) > block。这个时候strongSelf只是一个临时变量,出了作用域空间strongSelf就释放了。当延迟函数没有执行完毕退出页面的时候并不马上释放,等到执行完毕后才进行释放。weakSelf会自动置空,在block内部strongSelfweakSelf进行了短暂的持有。这种解决循环引用的方式是 weak-strong-dance(强弱共舞)

那么修改为网络请求呢?

__weak typeof(self) weakSelf = self;
[[[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.raywenderlich.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf);
    });
}] resume];

在进入页面后立马退出,输出:

(null)

与上面的区别是在执行完NSURLSession的时候weakSelf已经是nil了,内部strongSelf指向的就是nil。(这里没有互相持有)。

__weak 是将修饰的变量放入弱引用表,用的时候从弱引用表取出。取的时候如果为nil了就赋值nil,否则就赋值它所指向的对象给临时变量,临时变量出了作用域后自动释放。这也就是weak能解决循环引用的根本原因。

2.2 五种解决循环引用的方式

除了上面说大家比较熟悉的 weak-strong-dance(强弱共舞) 解决循环引用的方式,还有4种方式。

2.2.1 临时变量置nil

__block ViewController *vc = self;
self.block = ^(){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
        vc = nil;
    });
};
self.block();

使用本身的类型接收self然后用__block修饰,这个时候的持有关系是vc -> self -> block -> vc,但是在使用完毕后vc = nil切断了循环引用的链条也就断开了循环引用。

⚠️ 当然也可以在调用后或者在block内部调用完成时直接设置self.block = nil。一般这么做没什么意义,block设置为属性的目的就是为了多次使用,如果只使用一次就释放直接局部block就可以了。

2.2.2 self 作为 block 参数

typedef void(^HPBlock)(ViewController *);

self.block = ^(ViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
    });
};
self.block(self);

self作为了参数传递给blockblock内部是对参数的引用,不会造成循环引用。

⚠️另外两种待补充

2.3 案例2

static ViewController *staticSelf;

- (void)weak_static {
    __weak typeof(self) weakSelf = self;
    staticSelf = weakSelf;
}

以上案例在页面退出的时候dealloc不会执行造成了内存泄露。此时持有关系是staticSelf -> weakSelf -> self
那么weakSelf为什么没有释放呢?
本质上staticSelf 、weakSelf 、self都指向同一块内存空间:

image.png
staticSelf是全局变量并不会释放,所以即使退出页面页面也不会释放。

2.4 案例3

typedef void(^HPBlock)(void);

@property (nonatomic, copy) HPBlock block1;
@property (nonatomic, copy) HPBlock block2;

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

以上代码会造成循环引用。虽然strongSelf是一个临时变量,但是strongSelfblock2持有了(出了block1代码块仍然不会释放),block2又被self持有就有了self -> block2 -> strongSelf(与self指向同一块内存)。这个时候由于strongSelf不会自动置为nil所以会造成循环引用。

即使不调用block2也会造成循环引用,因为赋值的时候已经持有了。

那么如果修改如下呢?

- (void)block_weak_strong {
    self.block = ^{
        void (^block1)() = ^ {
            NSLog(@"%@",self);
        };
        block1();
    };
   self.block();
}

这个时候仍然会造成循环引用,由于 block的捕获是逐层捕获的

image.png

2.4.1 强弱共舞解决循环引用

这个时候如果block2中直接使用weakSelf或者再对strongSelf转一次weak就可以解决问题了:

__weak typeof(self) weakSelf = self;
self.block1 = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    __weak typeof(strongSelf) weakSelfAgain = strongSelf;
    weakSelf.block2 = ^{
        //NSLog(@"%@",weakSelf);
        NSLog(@"%@",weakSelfAgain);
    };
   weakSelf.block2();
};
self.block1();

此时如果block2中还有延迟函数就又要进行一次临时变量强持有了:


__weak typeof(self) weakSelf = self;
self.block1 = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    __weak typeof(strongSelf) weakSelfAgain = strongSelf;
    weakSelf.block2 = ^{
        __strong typeof(weakSelfAgain) strongSelfAgain = weakSelfAgain;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelfAgain.name);
        });
    };
   weakSelf.block2();
};
self.block1();

就无限套娃就完事了。

2.4.2 临时变量置为nil

__weak typeof(self) weakSelf = self;
self.block1 = ^{
    __block ViewController* vc = weakSelf;
    weakSelf.block2 = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
   weakSelf.block2();
};
self.block1();

对于block2中有延时任务的时候也可以通过临时变量使用完置为nil解决循环引用,当然没有延时任务直接使用weakSelf就完事了。

前面分析了block的分类以及循环引用,那么有以下疑问:

  1. block copy后就从栈区到堆区了,究竟是怎么处理的呢?
  2. block是如何捕获变量的呢?
  3. block捕获的对象是怎么保存和释放的呢?

三、block 的 Clang 底层分析

一个最简单的block:

#include <stdio.h>

int main(){
    int a = 18;
    void(^block)(void) = ^{
        printf("%d",a);
    };
    block();
    return 0;
}

通过clang编译成.cpp文件,此时main函数变成了:

int main(){
    int a = 18;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

为了方便忽略类型强转还原后如下:

int main(){
    int a = 18;
    //(__main_block_impl_0)函数调用的结果取地址给到 block,参数是 __main_block_func_0,&__main_block_desc_0_DATA 以及 a
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
    //调用 block->FuncPtr,参数是 block 自己
    block->FuncPtr(block);
    return 0;
}

3.1 __main_block_impl_0

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

通过结构体的构造函数生成block3个参数与结构体的成员变量对应。当block不持有临时变量时:

image.png

此时如果捕获的是对象类型呢?

IMG_6.png
可以看到仍然是对objc的赋值,这里变成了引用类型也就是持有。

既然block在底层是一个结构体,而oc对象在底层也是结构体。那么block是否是oc的对象呢?

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

NSLog(@"%d",[block isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]);

输出:1。那么可以确定其实block也是oc对象。在源码中有如下定义:

image.png

block也是oc对象,继承自NSObject

3.2 __main_block_func_0

第一个参数fp(__main_block_func_0)赋值给了FuncPtrFuncPtr是在block调用的时候进行调用的。fp是函数式保存,这样就说明如果block不执行它的功能就不会被调用。
__main_block_func_0内部获取了结构体存储的值,对于值类型/引用类型以及__block修饰的类型的存储与调用如下:

image.png

那么__block究竟干了什么呢?

3.2.1 基本类型的__block

mainb变成了__Block_byref_b_0(精简后的去掉了类型强转):

__Block_byref_b_0 b = {
    (void*)0,
    (__Block_byref_b_0 *)&b,//取b本来的的地址
    0,
    sizeof(__Block_byref_b_0),
    20};

__Block_byref_b_0结构如下:

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

这里看__forwarding就是指向b。在block的构造函数中赋值给b的是__forwarding也就是b的指针。所以加了__block后基本数据类型就指向了外部的变量,这个时候修改ref就是修改外部的基本类型了。

3.2.2 引用类型的 __block

objc2变成了__Block_byref_objc2_1类型:

__Block_byref_objc2_1 objc2 = {
    (void*)0,
    (__Block_byref_objc2_1 *)&objc2,//取objc2本来的地址,指向的内存空间
    33554432,
    sizeof(__Block_byref_objc2_1),
    __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_objc2_1的结构如下:

struct __Block_byref_objc2_1 {
  void *__isa;
__Block_byref_objc2_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *objc2;
};

__forwarding指向objc2指向的内存空间。也就是__forwardingobjc2两个指针指向了同一片内存空间,不是之前的指向objc2了。所以就能在block内部修改objc2本身了。

__Block_byref_objc2_1中还发现了__Block_byref_id_object_copy以及__Block_byref_id_object_dispose,对应的传递的函数为__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131(只有定义没有调用):

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_object_assign_Block_object_dispose

3.3 __main_block_desc_0

block构造函数的第二个参数是__main_block_desc_0_DATA

image.png

__main_block_copy_0以及__main_block_dispose_内部对block持有的引用类型以及__block修饰的类型(ref)进行了_Block_object_assign_Block_object_dispose的调用,他们的实现并没有在.cpp文件中。

3.2 __weak

__weak为什么能解决循环引用呢?

NSObject *objc = [NSObject alloc];
__weak NSObject *objc2 = [NSObject alloc];
void(^block)(void) = ^{
    NSLog(@"%@,%@",objc,objc2);
};
block();
image.png
可以看到blockweak变量的持有也是__weak类型,调用依然是。也就是对应的 遇弱捕弱,遇强补强

四、汇编分析block底层

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

在有缓存的情况下可能会省略掉,最好使用一个新的工程测试。

4.1 定位到 _Block_copy

block的赋值下断点查看汇编调用:

image.png
下符号断点objc_retainBlock或者直接跟踪进去:
image.png
定位到了libobjc objc_retainBlock,在源码中它的实现如下:
id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

内部调用了_Block_copy,它的实现并不在objc中:

image.png
继续对_Block_copy下符号断点:
image.png
定位到了libsystem_blocks.dylib,那也就是说block的实现是在libsystem_blocks中的。libsystem_blocks本身没有开源,可以通过libclosure工程替代。

可以将libclosure编译成可运行的工程调试。

4.2 汇编分析 _Block_copy

断点进入_Block_copy后读取寄存器查看参数:

image.png
这个时候是一个全局block,修改代码让block持有变量再次查看:
image.png
变成了栈block,也就是说在_Block_copy之前仍然是栈block。在汇编ret继续断点查看x0
image.png

4.3 汇编分析 签名函数

在打印block的时候有一个signature,它是否v8@?0就是上面block的签名呢?

image.png

五、libclosure 分析 block 底层实现

5.1 _Block_copy 分析

/*
 拷贝 block,参数是 block (Block_layout 对象)本身。
 1.堆上 block 引用计数 + 1。
 2.栈上 block 先拷贝到堆上,引用计数初始化为1。然后调用 copy helper 方法(如果存在)。
 3.全局 block 直接返回block本身。
 返回 拷贝后的 block 地址。
 */
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;
    //block 不存在,直接返回 NULL。
    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    //block 强转为 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 不进行操作直接返回。
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {//栈 block,需要拷贝到 堆上
        // Its a stack block.  Make a copy.
        //获取原始 栈block 大小
        size_t size = Block_size(aBlock);
        //在堆上重新开辟一块 与 aBlock 相同大小的内存空间
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        // 开辟失败,返回 NULL
        if (!result) return NULL;
        //将 aBlock 内存上的数据全部复制到新开辟的 result 上
        memmove(result, aBlock, size); // bitcopy first
......
        // reset refcount
        //重新设置引用计数以及释放标志位(清除置为0)
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 将 result 标记位为堆上,需要手动释放;并且引用计数初始化为 1
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        //拷贝成员变量的工作 调用 Block_descriptor_2 以及 Block_descriptor_3
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        //isa标记为栈block
        result->isa = _NSConcreteMallocBlock;
        //返回新的block
        return result;
    }
}

5.1.1 latching_incr_int

/*
 引用计数加 1,最多不超过 BLOCK_REFCOUNT_MASK。
 volatile的作用是:作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化
 */
static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        // 如果 old_value 在第 1~15 位都已经变为 1 了,即引用计数已经满了,就返回 BLOCK_REFCOUNT_MASK
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        // 比较 where的值 是否等于 old_value,如果等于,就将新值 oldValue + 2 放入 where
        // 否则继续下一轮循环
        // 这里加 2,是因为 flag 的第 0 位已经被占了,引用计数是第 1~15 位,所以加上 0b10,引用计数只是加 1
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

引用计数加 1,最多不超过 BLOCK_REFCOUNT_MASK

5.1.2 _Block_call_copy_helper

/*
 调用 block 的 copy helper 方法,即 Block_descriptor_2 中的 copy 方法
 */
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    //获取 copy 函数
    if (auto *pFn = _Block_get_copy_function(aBlock))
        //调用函数,最终会调用到 _Block_object_assign
        pFn(result, aBlock);
}

获取Block_descriptor_2copy进行调用。

5.2 Block_layout 结构

_Block_copyblock是以Block_layout接收的,也就是说block在底层是Block_layout类型:

struct Block_layout {
    void * __ptrauth_objc_isa_pointer isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;//保留字段
    BlockInvokeFunction invoke;//最终回调
    struct Block_descriptor_1 *descriptor;//描述信息
    // imported variables
};
void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };

// imported variables注释说明了应该还有其它字段。

5.2.1 flags 标志位说明

// Values for Block_layout->flags to describe block objects
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
};

5.2.2 Block_descriptor_1

struct Block_descriptor_1 {
    uintptr_t reserved;//保留字段
    uintptr_t size;//大小
};

但是在上面clang分析的时候__main_block_desc_0不仅仅是这两个字段,并且汇编打印的时候block中也有其它字段,比如copydispose就没有出现在Block_layoutBlock_descriptor_1中。但是在Block_descriptor_1的周围发现了Block_descriptor_2Block_descriptor_3

image.png
也就是通过flags标志位BLOCK_HAS_COPY_DISPOSEBLOCK_HAS_SIGNATURE确定是否有Block_descriptor_2Block_descriptor_3的。

那么是在什么时机进行拼接生成的呢?
既然有设置就有获取,搜索_Block_descriptor_2的获取:

image.png
可以看到在desc1后面拼接了desc2以及desc3,当然这个过程中会根据标志位判断他们是否存在。
对应结构如下:
image.png

读取block内存结构如下:

image.png
block偏移8 + 4 + 4字节得到invoke(与打印出来的invoke地址相同),偏移8 + 4 + 4 + 8得到desc指针:
image.png
desc指针指向desc1desc2以及desc3中保存的值。与copydisposesignature是完全对应的。

desc根据标记位以及内存平移进行访问。

5.3 block捕获变量的生命周期

上面分析了Block_layout的内存结构,那么变量是如何捕获和释放的呢?
clang分析的时候__main_block_copy_0以及__main_block_dispose_赋值给了copy以及dispose

image.png
其实现调用了_Block_object_assign_Block_object_dispose。这里的copydisposedesc2中的,与block本身的copy没有关系。copy的是捕获的引用类型以及__block修饰的成员变量,

搜索_Block_object_assign有注释(翻译过后)如下:

 这是给编译器提供的 API
 
 block 可以引用 4 种不同的类型的对象,当 block 被拷贝到堆上时,需要 help,即帮助拷贝一些东西。
 1)基于 C++ 栈的对象
 2)Objective-C 对象
 3)其他 Block
 4)被 __block 修饰的变量
 
 block 的 helper 函数是编译器合成的(比如编译器写的 __main_block_copy_1() 函数),它们被用在 _Block_copy() 函数和 _Block_release() 函数中。copy helper 对基于 C++ 栈的对象调用调用 C++ 拷贝构造函数,对其他三种对象调用 _Block_object_assign 函数。 dispose helper 对基于 C++ 栈的对象调用析构函数,对其他的三种调用 _Block_object_dispose 函数。
 
 _Block_object_assign 和 _Block_object_dispose 函数的第三个参数 flags 有可能是:
 1)BLOCK_FIELD_IS_OBJECT(3) 表示是一个对象
 2)BLOCK_FIELD_IS_BLOCK(7) 表示是一个 block
 3)BLOCK_FIELD_IS_BYREF(8) 表示是一个 byref,一个被 __block 修饰的变量;如果 __block 变量还被 __weak 修饰,则还会加上 BLOCK_FIELD_IS_WEAK(16)
 
 所以 block 的 copy/dispose helper 只会传入四种值:3,7,8,24
 
 上述的4种类型的对象都会由编译器合成 copy/dispose helper 函数,和 block 的 helper 函数类似,byref 的 copy helper 将会调用 C++ 的拷贝构造函数(不是常拷贝构造),dispose helper 则会调用析构函数。还一样的是,helpers 将会一样调用进两个支持函数中,对于对象和 block,参数值是一样的,都另外附带上 BLOCK_BYREF_CALLER (128) bit 的信息。#疑问:调用的这两个函数是啥?BLOCK_BYREF_CALLER 里究竟存的是什么??
 
 所以 __block copy/dispose helper 函数生成 flag 的值为:对象是 3,block 是 7,带 __weak 的是 16,并且一直有 128,有下面这么几种组合:
    __block id                   128+3       (0x83)
    __block (^Block)             128+7       (0x87)
    __weak __block id            128+3+16    (0x93)
    __weak __block (^Block)      128+7+16    (0x97)

对应的有如下枚举定义:

// Values for _Block_object_assign() and _Block_object_dispose() parameters
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对捕获变量的类型有如下处理:

使用以下代码进行验证:

NSObject *objc = [NSObject alloc];
void(^block1)(void) = ^{
    NSLog(@"%@",objc);
};
__block NSObject *objc2 = [NSObject alloc];
__weak NSObject *objc3 = [NSObject alloc];
__weak __block NSObject *objc4 = [NSObject alloc];
void(^block)(void) = ^{
    NSLog(@"%@,%@,%@,%@,%@",objc,block1,objc2,objc3,objc4);
};
block();
image.png
__weak底层是与ref相同的处理。

对于错误1.cannot create _weak reference because the current deployment target does not support weak references需要指定-fobjc-runtime版本。
2.cannot create __weak reference in file using manual reference需要指定-fobjc-arc
完整命令:
xcrun clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 block.m -o block.cpp

5.3.1 _Block_object_assign

//参数 destArg 是一个二级指针,指向真正的目标指针。
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:
        // _Block_retain_object_default = fn (arc),交给了系统arc处理。
        _Block_retain_object(object);
        //src 给到 dest 指向同一块内存。地址指向相同,地址不同。
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        //block会被传过来的block进行拷贝
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        //__block 对象,也就是 ref 拷贝,使 dest 指向拷贝到堆上的 byref
        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        *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:
        *dest = object;
        break;

      default:
        break;
    }
}

根据传递的类型的不同进行不同的处理:

5.3.2 _Block_byref_copy

/*
    1.如果 byref 原来在栈上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3,
      被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable,
      原来 byref 的 forwarding 也会指向堆上的 byref;
    2.如果 byref 已经在堆上,就只增加一个引用计数。
      参数 dest是一个二级指针,指向了目标指针,最终,目标指针会指向堆上的 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);
        //isa指向置为空
        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(src 的 forwarding 也指向了 copy,相当于引用了 copy)
        
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        //堆空间 forwarding 指向自己
        copy->forwarding = copy; // patch heap copy to point to itself
        //栈空间原先的对象 forwarding 指向 拷贝的堆空间。原始的 forwarding 与 拷贝的 forwarding 都指向 堆空间的。
        src->forwarding = copy;  // patch stack to point to heap copy
        //size大小赋值
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {//有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);
            //函数赋值
            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;
            }

            //byref_keep的调用(ref copy 的调用,最终也是调用了_Block_object_assign,走的是普通对象的逻辑)。
            (*src2->byref_keep)(copy, src);
        }
        else { //如果 src 没有 copy/dispose helper
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            // 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap 有引用计数,已经拷贝到堆区了
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        // src 已经在堆上,就只将引用计数加 1
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

5.3.3 Block_byref

__block修饰的变量会被转换成Block_byref结构体:

image.png
Block结构体本身一样,它的一些字段也是可选的。
对应clang的赋值:
image.png
Block_ref内部的copy传递的参数是objc本身。

__block修饰的引用类型的三层copy

5.4 block 释放

既然有copy那么就有对应的释放,__block修饰的引用类型的变量被block捕获的时候有3copy,那么释放应该也是3层。

5.4.1 _Block_release

/*
 1.block 在堆上,才需要 release,在全局区和栈区都不需要 release.
 2.先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁
 */
void _Block_release(const void *arg) {
    //强转
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    //block不存在直接返回。
    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
        _Block_call_dispose_helper(aBlock);
        //销毁实例
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

5.4.2 latching_decr_int_should_deallocate

/*
 1.引用计数减 1,如果引用计数减到了 0,就将 block 置为 deallocating 状态
 2.返回值是 block 是否需要被 dealloc
 */
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        // 如果引用计数还是满的,就不能 dealloc,#疑问:引用计数满了以后就不能减了么?满了后永远不释放了?
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false; // latched high
        }
        // 如果引用计数为 0,按照正常的逻辑,它应该已经被置为 deallocating 状态,不需要再被 dealloc,所以返回 false
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;   // underflow, latch low
        }
        //引用计数减 1
        int32_t new_value = old_value - 2;
        bool result = false;
        // 如果 old_value 在 0~15 位的值是 0b10,即引用计数是 1,且不是 deallocating 状态
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
            // old_value 减 1 后,新值 new_value 在 0~15 位值是 0b01,即引用计数变为0,且 BLOCK_DEALLOCATING 位变为 1
            new_value = old_value - 1;
            // 需要被 dealloc
            result = true;
        }
        // 将新值 new_value 放入 where 中
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}

5.4.3 _Block_object_dispose

/*
 调用 block 的 dispose helper 方法,即 Block_descriptor_2 中的 dispose 方法
 */
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
    //找到dispose
    if (auto *pFn = _Block_get_dispose_function(aBlock))
        //调用dispose,最终会调用到 _Block_object_dispose
        pFn(aBlock);
}

_Block_call_dispose_helper最终会调用到_Block_object_dispose

image.png
blockdispose也就是调用_Block_object_dispose释放持有的对象以及ref

_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
        //ref 调用 _Block_byref_release,对 byref 对象做 release 操作
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        // block 继续调用 _Block_release,block 做 release 操作
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        //对象交给ARC
        _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;
    }
}

5.4.4 _Block_byref_release

/*
 对 byref 对象做 release 操作。
 1.堆上的 byref 需要 release,栈上的不需要 release。
 2.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;//获取 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)) {
            // 如果 byref 有 dispose helper,就先调用它的 dispose helper
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                //dispose函数获取
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                //调用 _Block_object_dispose 释放objc
                (*byref2->byref_destroy)(byref);
            }
            //释放ref
            free(byref);
        }
    }
}

__block修饰的引用类型的三层disposecopy完全相反:

六、总结

上一篇 下一篇

猜你喜欢

热点阅读