Block
2020-04-10 本文已影响0人
游城十代2dai
- Block 的本质?
- Block 本质上也是一个 OC 对象, 它的内部有 isa 指针, Block 内封装了函数调用以及环境的 OC 对象
-
__block
的作用? 注意什么?
- 作用是可以解决 block 内部不可以修改 auto 变量的问题, __block 不能修饰 static 和全局变量
- 注意 __block 修饰的变量在栈上的时候不会对指向的对象产生强引用, 当 copy 的时候才会进行强引用, 当然也要看具体修饰符, 如果是 __weak __unsafe_unretained 修饰的就是弱引用, 其中
MRC
下 __block 不会 retain
- Block 的属性修饰符为什么是 copy?
- 如果 block 没有进行 copy 操作, 就不会出现在堆上, 放在堆上, 就由开发者进行内存管理, 使用的时候小心循环引用
- Block 在修改 NSMutableArray 的时候需不需要添加 __block?
- 不需要, 因为比如添加删除元素的时候是对 array 内部的数据结构进行操作, 并不影响 array 的这个变量
- 尽量不要凡事就加__block, 因为__block 本质是生成一个 OC 对象, 导致整个数据结构变得复杂
- 在 Block 内部使用 __strong 目的是什么?
- 目的是保证 Block 执行完之前被 weak 过的对象不会释放, 顺便骗过编译器警告的错误
PS: 由于 Block 的内容不仅仅在编译时期, 还有运行时也有相应的系统管理, 当使用__weak __strong
的时候编译源码要用这个命令
$ xcrun -sdk iphoneos -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0 main.m -o main-arm64.cpp
以下是我测试相关内容的代码, 依旧是创建 Command Line Tool
, 通过对 Block 的 本质--捕捉对象--类型--内存--循环引用
测试 :
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
void _block_nature(void);
void _block_capture(void);
void _block_type(void);
void _block_memory(void);
void _block_circle_reference(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// _block_nature();
// _block_capture();
// _block_type();
// _block_memory();
_block_circle_reference();
}
return 0;
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_age_0 {
void *__isa;
/**
* 这个 forwarding 指针是指向这个结构体的地址, 因为后面再用到 age 的时候都是通过这个指针访问的
* ___block_nature_block_impl_0 这个里面的 age 可能出现在栈上, 但是整体被 copy 过, 就会在堆上, 栈上的 age 访问堆中的 age 才可以
*/
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct ___block_nature_block_desc_0 {
size_t reserved;
size_t Block_size;
};
struct ___block_nature_block_impl_0 {
struct __block_impl impl;
struct ___block_nature_block_desc_0* Desc;
struct __Block_byref_age_0 *age; // by ref
};
void _block_nature(void) {
// Block 本质上也是一个 OC 对象, 它的内部有 isa 指针
// Block 内封装了函数调用以及环境的 OC 对象
__block int age = 10;
void (^block)(int, int) = ^(int a, int b){
NSLog(@"age --- %d", age);
};
age = 20;
block(2, 5);
// 实际调用的结构 ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2, 5);
// 函数被赋值给 FuncPtr, 函数调用的时候就是在用 FuncPtr
struct ___block_nature_block_impl_0 *block_imp = (__bridge struct ___block_nature_block_impl_0 *)block;
NSLog(@"debug --- 请看 block_struct.png 文件");
}
/*
* _block_nature 编译后的代码
struct ___block_nature_block_impl_0 {
struct __block_impl impl;
struct ___block_nature_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
___block_nature_block_impl_0(void *fp, struct ___block_nature_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
* Block 的结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
*/
int gVar1 = 10;
static int gVar2 = 10;
void _block_capture(void) {
auto int lVar1 = 10;
static int lVar2 = 10;
void (^block)(void) = ^{
NSLog(@"gVar1 = %d, gVar2 = %d, lVar1 = %d, lVar2 = %d", gVar1, gVar2, lVar1, lVar2);
};
gVar1 = 20;
gVar2 = 20;
lVar1 = 20;
lVar2 = 20;
block();
}
/**
* 由下面的源码分析可得:
* 1. block 对作用域内的变量或进行捕获, 而全局变量不会进行捕获
* 2. OC 类内部的 block 如果包含 self, 会进行捕获, 因为 OC 的方法会被转换为 C 代码, 默认前两个参数为 self 和 SEL
* - (void)test() {
* void (^block)(void) = ^{
* NSLog(@"%@", self);
* }
* }
*/
/*
* 全局的变量在编译的时候没有被 block 结构体捕获成为自己的成员变量
int gVar1 = 10;
static int gVar2 = 10;
struct ___block_capture_block_impl_0 {
struct __block_impl impl;
struct ___block_capture_block_desc_0* Desc;
// 原本的 auto 修饰的局部变量被 block 捕获成为自己的变量, 但是只有值
int lVar1;
// 原本的 static 修饰的全局变量被 block 捕获成为自己的变量, 捕获到的是变量的地址
int *lVar2;
___block_capture_block_impl_0(void *fp, struct ___block_capture_block_desc_0 *desc, int _lVar1, int *_lVar2, int flags=0) : lVar1(_lVar1), lVar2(_lVar2) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block 保存下来的函数方法
static void ___block_capture_block_func_0(struct ___block_capture_block_impl_0 *__cself) {
int lVar1 = __cself->lVar1; // bound by copy
int *lVar2 = __cself->lVar2; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_7pz8l4jj29d86h_7fg45h7580000gn_T_main_f4f131_mi_2, gVar1, gVar2, lVar1, (*lVar2));
}
// 我的 _block_capture 实现
void _block_capture(void) {
auto int lVar1 = 10;
static int lVar2 = 10;
void (*block)(void) = ((void (*)())&___block_capture_block_impl_0((void *)___block_capture_block_func_0, &___block_capture_block_desc_0_DATA, lVar1, &lVar2));
gVar1 = 20;
gVar2 = 20;
lVar1 = 20;
lVar2 = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
*/
void _block_type(void) {
// Block 是 OC 对象, 也有 isa 那就可以调用 class 方法, 有以下三种类型, 但是一切以运行时为主
void(^block1)(void) = ^ {
};
Class block1Class = [block1 class];
NSLog(@"block1Class --- %@", block1Class); // __NSGlobalBlock__
int lVar = 10;
void(^block2)(void) = ^ {
printf("%d", lVar);
};
Class block2Class = [block2 class];
NSLog(@"block2Class --- %@", block2Class); // __NSMallocBlock__
NSLog(@"block3Class --- %@", [^{ printf("%d", lVar); } class]); // __NSStackBlock__
__block int lVar2 = 10;
void(^block4)(void) = ^ {
lVar2 = 20;
};
Class block4Class = [block4 class];
NSLog(@"block4Class --- %@", block4Class); // __NSMallocBlock__
// block1 可以看出凡是没有访问了 auto 的变量就是 __NSGlobalBlock__
// block2 block4 其实也是 StackBlock, 只不过 ARC 内部做了其他操作, 简单说 copy 了就会变成 __NSMallocBlock__
// 其中全局的被 copy 依旧是全局的, 只有在栈上的才会被放入堆中, 并且对相应的 auto 对象变量进行一次 retain 也就是强引用
// 这就是为什么对象持有 block 要用 copy
// xcrun -sdk iphoneos -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0 main.m -o main-arm64.cpp
}
/**
* Block 的内存管理
*
*/
void _block_memory(void) {
__block int zwy_age = 10;
NSObject *zwy_objc = [[NSObject alloc] init];
NSObject *zwy_objc2 = [[NSObject alloc] init];
__weak NSObject *weak_objc2 = zwy_objc2;
void (^zwy_block)(void) = ^ {
[zwy_objc description];
[weak_objc2 description];
zwy_age = 20;
};
zwy_block();
/**
* 当 block 被拷贝到堆上时会对其捕获的变量进行一次操作如下:
* _Block_object_assign 函数会对 __block (MRC 不会强引用 OC 对象) 和 __strong 的变量进行强引用, 对 __weak 的进行弱引用
* 当 block 被废弃的时候也会对这些变量进行一次 dispose 操作
*/
/*
__Block_byref_zwy_age_2 *zwy_age; // __block 修饰的变量
NSObject *__strong zwy_objc; // block 中的 oc 对象
NSObject *__weak weak_objc2; // block 中的 __weak oc 对象
static void ___block_memory_block_copy_0(struct ___block_memory_block_impl_0*dst, struct ___block_memory_block_impl_0*src) {
_Block_object_assign((void*)&dst->zwy_objc, (void*)src->zwy_objc, 3);
_Block_object_assign((void*)&dst->weak_objc2, (void*)src->weak_objc2, 3);
_Block_object_assign((void*)&dst->zwy_age, (void*)src->zwy_age, 8);
}
*/
}
typedef void(^Block)(void);
@interface Person : NSObject
@property (nonatomic, copy) Block block;
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
void _block_circle_reference(void) {
{ // 产生了循环引用, 即 per 强引用 block, block 强引用 per, 所以出了这个作用域 per 不会 dealloc
Person *per = [[Person alloc] init];
per.age = 10;
per.block = ^{
NSLog(@"%d", per.age);
};
}
{ // 解决办法 __weak __unsafe_unretained
Person *per = [[Person alloc] init];
per.age = 10;
__weak Person *weak_per = per;
per.block = ^{
NSLog(@"%d", weak_per.age);
};
}
{ // 解决办法
__block Person *per = [[Person alloc] init];
per.age = 10;
per.block = ^{
NSLog(@"%d", per.age);
per = nil; // 必须 nil
};
per.block(); // 必须调用
}
sleep(1);
}