16、iOS底层探索-Block

语言: CN / TW / HK

highlight: hybrid

1、Block类型

  • 全局block
    • 如果没有使用外部变量,或者只使用全局变量或静态变量,则是全局block
  • 栈block
    • 如果使用了外部变量,赋值弱引用,则是栈block
  • 堆block
    • 如果使用了外部变量,赋值强引用,则是堆block

2、底层探索

小测验

```swift - (void)viewDidLoad {     [super viewDidLoad];

NSObject *obj = [NSObject new];     NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));

void (^block1)(void) = ^{         NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));     };     block1();

void (^__weak block2)(void) = ^{         NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));     };     block2(); }

// 打印结果 134 ``` - 在第一次打印时obj引用计数为1

  • block1是一个堆block捕获obj时会先将obj捕获到栈上,再copy到堆上,因此引用计数会被两次+1,所以block1打印为3

  • 弱引用的 block2是一个栈block,所以obj被捕获到栈上,不再向堆上copy,引用计数+1,打印为4

2.1、cpp文件分析block

image.png 转译成.cpp文件 image.png - 可以看到先创建为 栈block,并捕获了外部obj变量 - .cpp中,block会被转换成_block_impl的结构体 swift struct __block_impl { void *isa; // 指针 int Flags; int Reserved; void *FuncPtr; // 存储代码块 }; - block本质是结构体

2.2、汇编探索

  1. 我们在block1处打上断点运行,并进入汇编调试 image.png
  2. 在汇编中找callq看到,block1中调用了objc_retainBlock符号(用__weak修饰后不会调用该符号),并且 此时是一个_NSConcreteStackBlock,也就是 栈block,那么我们可以尝试对 objc_retainBlock 下一个符号断点: image.png
  3. 可以看到其中使用了一个_Block_copy image.png
  4. 我们继续对 _Block_copy 下符号断点 image.png
  5. _Block_copy 中,看callq指令我们知道了,系统调用了malloc方法,而最终生成了__NSMallocBlock__也就是 堆block

2.3、源码探索

借用上边汇编探索的部分内容,我们知道了block是在 _Block_copy由栈block变为了堆block,那么我们在Block源码中搜索 _Block_copy: ```swift // Copy, or bump refcount, of a block.  If really copying, call the copy helper if present. // 拷贝 block, // 如果原来就在堆上,就将引用计数加 1; // 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话); // 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身 // 参数 arg 就是 Block_layout 对象, // 返回值是拷贝后的 block 的地址 // 运行?stack -> malloc 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;     const char signature = _Block_descriptor_3(aBlock)->signature;

// 如果现在已经在堆上     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 相同大小的内存         struct Block_layout result =             (struct Block_layout )malloc(aBlock->descriptor->size);         // 开辟失败,返回 NULL         if (!result) return NULL;         // 将 aBlock 内存上的数据全部复制新开辟的 result 上         memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

if __has_feature(ptrauth_calls)

// Resign the invoke pointer as it uses address authentication.         result->invoke = aBlock->invoke;

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在全局**:直接返回block本身 - **block在堆上**:引用计数+1 - **block在栈上**:将栈上的所有数据(**包括block捕获后成为其成员变量的外部变量**)全部copy到新开辟的`Block_layout`类型的 result 上swift struct Block_layout { void isa; volatile int32_t flags; // contains ref count int32_t reserved; // libffi -> BlockInvokeFunction invoke; struct Block_descriptor_1 descriptor; // imported variables }; ```

3、循环引用

准备一份循环引用代码 ```swift

import "LZViewController.h"

typedef void(^LZBlock)(void);

@interface LZViewController ()

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

@end

@implementation LZViewController

  • (void)viewDidLoad {     [super viewDidLoad];     self.view.backgroundColor = [UIColor greenColor];

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

  • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {     [self dismissViewControllerAnimated:YES completion:nil]; }

  • (void)dealloc {     NSLog(@"%s",func); } ```

3.1、weak打破循环

```swift - (void)viewDidLoad {     [super viewDidLoad];     self.view.backgroundColor = [UIColor greenColor];

self.name = @"lz";

// weak打破循环

__weak typeof(self)weakSelf = self;     self.block = ^{ // strong保证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(); } ```

3.2、手动释放

```swift - (void)viewDidLoad {     [super viewDidLoad];     self.view.backgroundColor = [UIColor greenColor];

self.name = @"lz"; // 声明变量vc指向self __block LZViewController *vc = self;     self.block = ^{         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{             NSLog(@"%@",strongSelf.name); // 使用后手动释放vc vc = nil;         });     };     self.block(); } ```

3.3、改为block参数

block不会捕获自身的参数,参数是栈自动管理的 ```swift

import "LZViewController.h"

typedef void(^LZBlock)(void); // 在block中增加参数 typedef void(^LZBlock1)(LZViewController *);

@interface LZViewController ()

@property (nonatomic, copy) LZBlock block; @property (nonatomic, copy) LZBlock1 block1; @property (nonatomic, copy) NSString *name;

@end

@implementation LZViewController

  • (void)viewDidLoad {     [super viewDidLoad];     self.view.backgroundColor = [UIColor greenColor];

self.name = @"lz";

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

4、需要调用block()的原因

swift int main(int argc, const char * argv[]) {     @autoreleasepool {         // Setup code that might create autoreleased objects goes here.     }     int a = 10;     void (^block)(void) = ^{         NSLog(@"%d",a);     };     block();     return NSApplicationMain(argc, argv); }

转译.cpp文件: swift struct __block_impl {   void *isa;   int Flags;   int Reserved;   void *FuncPtr; }; image.png - 在.cpp中,block是一个__block_impl类型的结构体 impl - block执行__main_block_impl_0函数进行构造初始化 - __main_block_impl_0 中第一个参数__main_block_func_0是代码块内容执行构造方法时被存储在 impl 的 FuncPtr 中 - 当调用block()时,系统会调取 impl 的 FuncPtr 执行其中的代码块内容

5、__block

swift int main(int argc, const char * argv[]) {     @autoreleasepool {         // Setup code that might create autoreleased objects goes here.     } // 当想在block内部改变外部变量值时,需要添加__block修饰变量     __block int num = 10;     void (^block)(void) = ^{         NSLog(@"%d",--num);     };     // 调用block     block();     return NSApplicationMain(argc, argv); } 转译.cpp文件: image.png - 对比于不加__block关键字,被捕获的外部变量变成了__Block_byref_num_0结构体 swift struct __Block_byref_num_0 { void *__isa; // 默认指向结构体本身 __Block_byref_num_0 *__forwarding; int __flags; int __size; int num; }; - __main_block_func_0中代码块,打印--num的地方变成了--(num->__forwarding->num),也就是说我们需要知道num结构体 中的__forwarding指向

  • __main_block_copy_0中的_Block_object_assign会改变__forwarding指向

_Block_object_assign

image.png - switch case 的值: image.png 看注释可以知道 __block 修饰时会走 BLOCK_FIELD_IS_BYREF 分支

  • __Block_byref_copy image.png

小结

  • 当外部变量num使用__block修饰时,底层cpp中变量num会被变成 __Block_byref_num_0结构体num,其内部有一个__forwarding,默认先指向栈上的结构体num自身,代码块中使用这个num的地方也会变成使用num->__forwarding

  • __main_block_copy_0 中的 _Block_object_assign 方法 在block由栈copy到堆上的过程中,也将指向结构体num自身的__forwarding改为指向了堆上新copy的内容

  • 因此如果修改已被copy到堆上的block外部变量的值,也会影响指向这个堆的原本栈上的值

6、block底层结构

本文 2.3 中我们查看 _Block_copy 时我们看到: image.png

Block_layout

swift struct Block_layout { void *isa; // 指向堆、栈、全局 volatile int32_t flags; // 附加信息,类似指针的MASK掩码,和不同的参数取 与操作能得到不同的值 int32_t reserved; // 保留变量 // libffi -> BlockInvokeFunction invoke; //实现的函数指针 struct Block_descriptor_1 *descriptor; // 存放copy、dispose、大小、签名等信息 }; - block的本质是 Block_layout - 对比.cpp文件中__block_impl类型的结构体 impl,可以发现内容很相似 - flags:附加信息,类似指针的MASK掩码,和不同的参数取 与操作 能得到不同的内容(在Block_descriptor_1中就使用了flags获取copy、dispose与签名等内容swift // Values for Block_layout->flags to describe block objects enum { BLOCK_DEALLOCATING =      (0x0001),  // runtime BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 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_descriptor_1

image.png 其中部分内容存放在 Block_descriptor_2Block_descriptor_3 中,它们的空间连续,使用时 通过内存平移调取

image.png

  • block的签名是@?