1、Block类型
- 全局block
- 如果没有使用外部变量,或者只使用全局变量或静态变量,则是全局block
- 栈block
- 如果使用了外部变量,赋值弱引用,则是栈block
- 堆block
- 如果使用了外部变量,赋值强引用,则是堆block
2、底层探索
小测验
- (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
的结构体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:
// 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 上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、循环引用
准备一份循环引用代码
#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<UITouch *> *)touches withEvent:(UIEvent *)event {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc {
NSLog(@"%s",__func__);
}
复制代码
3.1、weak打破循环
- (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、手动释放
- (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不会捕获自身的参数
,参数是栈自动管理的
#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()的原因
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文件:
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
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
结构体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
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与签名等内容)// 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的签名是
@?