block几乎天天都在使用,也是面试题高发区。可是原理还是有点晦涩的,现在就静下心来听我慢慢道来!
dealloc
,这就是循环引用
;self.block = ^{
[self sayHello];
};
self和block相关引用
;解决循环引用常见的方式有以下几种:
__weak + __strong + dance
,利用中介者模式;__block修饰引用对象
,同样是利用中介者模式但是需要手动释放引用对象;self作为参数传入
;NSProxy
;@property(nonatomic, copy) void(^block)(void);
__weak typeof(self) weakSelf = self;
self.block = ^(void){
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",strongSelf.name);
};
weakSelf和self是指向同一片内存空间的
;weak这部分可以在OC底层探索19-weak和assign区别浅谈有详细的分析;@property(nonatomic, copy) void(^block)(void);
__block UIViewController *vc = self;
self.block = ^(void){
NSLog(@"%@",vc.name);
vc = nil; //手动释放
};
__block
修饰,第四部分会分析;block必须调用,不调用依旧会造成循环引用
;@property(nonatomic, copy) void(^block)(UIViewController *);
self.block = ^(UIViewController *vc){
NSLog(@"%@",vc.name);
};
//调用
self.block(self);
ReactCocoa
中还挺常见的;NSProxy 和 NSObject是同级的一个类,也可以说是一个虚拟类,只是实现了NSObject的协议;
@interface HRTestProxy : NSProxy
@property (nonatomic, strong, nullable) NSObject *target;
@end
@implementation HRTestProxy
// NSProxy实现逻辑和方法慢速转发一致,通过模板类对详细进行转发
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
methodSignatureForSelector
,forwardInvocation
这两个方法有详细的说明;-(void)demo{
HRTestProxy *myProxy = [HRTestProxy alloc];
myProxy.target = self;
self.block = ^(void){
[myProxy performSelector:@selector(say)];
};
//调用
self.block();
}
-(void)say{
NSLog(@"%@",self.name);
}
NSProxy
的特性将self -> block -> self
,改为self -> block -> NSProxy
的结构,从而完成循环引用的打破;分析结构一定是离不开clang
,
clang命令: clang -rewrite-objc main.m -o main.cpp
void (^mallocBlock)(void) = ^void {
NSLog(@"HR_Block");
};
将上方代码进行编译;
结构体/对象
,isa:_NSConcreteStackBlock
和objc_object
是一致的;&__main_block_impl_0(方法, &参数)
,将isa、大小参数、执行函数等信息赋值;__main_block_impl_0
里获取FuncPtr
函数指针,也就是__main_block_func_0(block自己)
,并将block自己
传入;这个位置可以理解为OC方法中的隐藏参数: (id self,Sel cmd)
__NSGlobalBlock__
全局block,存储在全局区
void(^block)(void) = ^{
NSLog(@"HR");
};
__NSMallocBlock__
堆区block
int a = 10;
void(^block)(void) = ^{
NSLog(@"HR - %d", a);
};
__NSStackBlock__
栈区block
int a = 10;
void(^__weak block)(void) = ^{
NSLog(@"HR - %d", a);
};
值拷贝
)int a = 10;
void (^mallocBlock)(void) = ^void {
NSLog(@"HR_Block - %d",a);
};
mallocBlock();
__main_block_impl_0
结构体内多了一个int a
,也就是说block会把捕获的外界变量进行copy到自身结构里
,当然这些操作都是编译器帮我们完成了;__main_block_func_0
里参数int a = __cself->a;
进行了值拷贝
;两个地址完全是不同
.验证了之前值拷贝的说法;指针拷贝
)在2.2中出现了值拷贝,而且在block中也不允许使用修改捕获参数,想过要block内部修改就需要__block
声明变量。
__block int a = 10;
void (^mallocBlock)(void) = ^void {
NSLog(@"HR_Block - %d",a++);
};
编译之后block结构体的变化很大;
__block
修改的被捕获变量被声明成__Block_byref_a_0
这样的一个结构。把指针的地址和值进行保存,所以才可以在block块内完成值的修改
;__Block_byref_a_0 *a = __cself->a;
中的赋值和直接捕获是不一样的,这里是引用了__Block_byref_a_0
的地址。在a++
操作的时候(a->__forwarding->a)++)
;因为在初始化的时候保存的就是_a->__forwarding
;_Block_object_assign
,_Block_object_dispose
就是对外界捕获参数的操作,在block的三层拷贝的时候会着重分析;这里就需要简单知道:block需要对外界变量进行持有;因为这里都是c++的操作,无法操作ARC完成引用计数的操作;__Block_byref_id_object_copy_xx
会出现这样一个方法. 源码位置:
在工程中增加符号断点_Block_copy
。
libclosure源码下载想要继续深入就需要分析源码;
Block真正的结构类型,之前看到__main_block_impl_0
都是以它为模板生成的,所以Block_layout
是一个模板类型;
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
// 所有捕捉的外部函数声明
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
// 涉及到__block修饰的外部变量时会出现
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature; // 保存block的方法签名
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Block_layout
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
//__Block 修饰的结构体 byref_keep 和 byref_destroy 函数 - 来处理里面持有对象的保持和销毁
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
__Block_byref_a_0
就是以Block_byref
为模板进行创建的。对象
则会出现Block_byref_2
中的这部分结构。利用Block_layout结构获取Block方法签名篇幅有点大,就新起了一篇;代码很有意思欢迎阅读!!
编译器是不可以直接在堆区进行创建的需要进行malloc的内存申请,可是__NSMallocBlock__
这个类型的Block就是存储在堆区的,那么就一定进行过malloc操作
;
_Block_copy
-lldb调试还记得之前提到过的_Block_copy
这个符号断点吗?
lldb获取寄存器rax(模拟器)的值
,可以看到当前是一个__NSStackBlock__
;等到方法执行完之后,在查看寄存器rax(模拟器)的值
_Block_copy
函数之后,Block从__NSStackBlock__
变成了一个__NSMallocBlock__
,这个函数就很关键了。_Block_copy
-源码 第一次拷贝void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// 这就是block第一次copy
aBlock = (struct Block_layout *)arg;
// 如果Block需要释放则直接释放
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果Block是一个全局Block,不做任何操作
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// 从堆区申请内存空间,并将栈区内容移动到堆区
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// 完成赋值、标示符的修改
result->invoke = aBlock->invoke;
#endif
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 将Block的类型转换为_NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
堆区申请空间
,并将栈区所有数据移动到堆区
;_NSConcreteMallocBlock
,完成类型的修改;_Block_object_assign
-源码 第二次拷贝Block 捕获外界变量的操作
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没有做任何处理
_Block_retain_object(object);
// Block对捕获的外界参数进行了一次强引用,这就是导致循环引用的本质
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
// 如果外界参数是一个Block类型,那么再进行一次_Block_copy
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// 外界参数被__block修饰
*dest = _Block_byref_copy(object);
break;
//还有些其他类型,这里就省略了
}
}
_Block_byref_copy
-源码 第三次拷贝__block 捕获外界变量的操作 内存拷贝 以及常规处理
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 申请了一片堆区控件
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
//copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
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;
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);
copy3->layout = src3->layout;
}
//等价于 __Block_byref_id_object_copy_131
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
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) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
__block
修饰之后会进入进行第三次拷贝
,如果本次拷贝的变量又支持copy
那么将进行更深层次的_Block_object_assign
;