16、iOS底层探索-Block
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
转译成.cpp文件
- 可以看到先创建为 栈block,并捕获了外部obj变量
- .cpp中,block会被转换成_block_impl
的结构体
swift
struct __block_impl {
void *isa; // 指针
int Flags;
int Reserved;
void *FuncPtr; // 存储代码块
};
- block本质是结构体
2.2、汇编探索
- 我们在block1处打上断点运行,并进入汇编调试
- 在汇编中找
callq
看到,block1中调用了objc_retainBlock
符号(用__weak修饰后不会调用该符号),并且 此时是一个_NSConcreteStackBlock
,也就是 栈block,那么我们可以尝试对 objc_retainBlock 下一个符号断点: - 可以看到其中使用了一个
_Block_copy
- 我们继续对 _Block_copy 下符号断点
- 在 _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;
};
- 在.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文件:
- 对比于不加__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
- switch case 的值: 看注释可以知道 __block 修饰时会走 BLOCK_FIELD_IS_BYREF 分支
- __Block_byref_copy
小结
-
当外部变量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 时我们看到:
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
其中部分内容存放在 Block_descriptor_2 和 Block_descriptor_3 中,它们的空间连续,使用时 通过内存平移调取
- block的签名是
@?