Block本质
block本质为一个结构体也可以说是一个匿名函数
我们可以利用clang来进行分析
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_lg_name_0 *lg_name; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_lg_name_0 *_lg_name, int flags=0) : lg_name(_lg_name->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们也可以找到block的源码
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
Block分类
block分为6种
void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 }; //常用在系统级别
void * _NSConcreteFinalizingBlock[32] = { 0 }; //常用在系统级别
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 }; //常用在系统级别
常用的有三种:
- _NSConcreteStackBlock 栈
- _NSConcreteMallocBlock 堆
- _NSConcreteGlobalBlock 全局
void(^block)(void) = ^{
NSLog(@"=====");
};
block();
NSLog(@"%@",block);
打印结果<__NSGlobalBlock__: 0x10a84e078>
为GlobalBlock
- 当block捕获变量之后就会发生变化,变成
MallocBlock
,如下:
__block int a = 1;
void(^block)(void) = ^{
NSLog(@"=====%d",a);
};
block();
NSLog(@"%@",block);
- StackBlock发生在copy之前
比如
void(^block)(void) = ^{
NSLog(@"=====%d",a);
};
block();
NSLog(@"%@",block);
NSLog(@"%@",^{
NSLog(@"1233 - %d",a);
//<__NSStackBlock__: 0x7ffee8278c38>
});
循环引用问题
typedef void(^Block)(void);
@interface ViewController ()
@property(nonatomic,copy)Block block;
@property(nonatomic,assign)int age;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
NSLog(@"=====%d",self.age);
};
self.block();
// Do any additional setup after loading the view.
}
@end
这段代码就会引起循环引用问题,self持有了block,block中也持有了self.
解决办法1 __weak 修饰
__weak typeof(self) weakSelf = self;
self.block = ^{
//类似于中介者模式
//weakself ->self(weak修饰引用计数不增加)->block->self
NSLog(@"=====%d",weakSelf.age);
};
- 声明一个weakself临时变量,weak修饰的引用计数不增加,只是将self放入到弱引用表中,这里dealloc方法正常调用,调用之后self置位nil,这时候循环引用链打断,不在相互持有.
这样写其实还会有问题,来看一下代码
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"=====%d",weakSelf.age);
});
};
self.block();
这个时候我们pop回去了,调用了dealloc,self也进行释放,这个时候在打印age结果就为错的.
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
// strongSelf -> weakself -> self(引用计数不增加 nil) -> block->strongself
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"=====%d",strongSelf.age);
});
};
self.block();
- strongSelf -> weakself -> self(引用计数不增加 dealloc之后将self置位nil,打断循环引用) -> block->strongself
- 用strong对weakself进行一个临时的强引用,这样就能够实现,在打印完之后再进行析构的效果.
继续看这样要一段代码
(void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.handler = ^{
typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"Self is %@", strongSelf);
};
NSTimeInterval interval = 6.0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), weakSelf.handler);
}
- (void)dealloc {
NSLog(@"Released");
}
- 问题
这个时候如果pop
回去,dealloc
调用,证明已经释放,但是为什么handler
还是会执行输出self is null
- 答疑
把Block
赋值给了handler
,这时就把Block
拷贝到了堆上,并被handler
持有,而作为GCD参数使用,又会将已经在堆上的Block
引用计数+1,这时Block
的引用计数大于1,即使self释放,Block
release 一次,但是他的引用计数仍然大于0,所以没有被废弃,所以会执行
解决办法2 手动置空
__block ViewController *vc = self;
self.block = ^{
NSLog(@"=====%d",vc.age);
vc = nil;
};
self.block();
- vc -> self -> block -> vc 行程一个闭环
- 在vc = nil的时候打断循环引用
- 这里注意一定要对block进行调用,不调用block代码永远不执行,循环引用闭环也无法打破
解决办法3 把self当做参数传进来
修改block
typedef void(^Block)(ViewController *);
self.block = ^(ViewController * vc){
NSLog(@"=====%d",vc.age);
};
self.block(self);
当做参数传进来并没有形成持有关系,不会造成循环引用
小结: 解决block循环应用的三种方式
- 用__weak修饰
- 声明临时变量__block VC = self 并在block中手动置位nil
- 把self当做一个参数传进去
Block为什么要block()进行调用
我们利用clang命令来进行分析
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
//将block也就是fp传进来然后赋值给FuncPtr
impl.FuncPtr = fp;
Desc = desc;
}
};
//block中的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block");
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//简化代码 构造函数将block中代码也就是__main_block_func_0传进去
// void(*block)(void) = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//简化代码
block->FuncPtr(block);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
从以上代码中我们可以得出,
-
__main_block_func_0
函数中代码就是我们代码块中的代码,这里将__main_block_func_0
通过构造函数传进去,并保存在FuncPtr中并没有调用.这里只是声明 - 具体的函数实现需要手动调用
Block为什么能够捕获外部变量
int a = 1;
void(^block)(void) = ^{
printf("block=====%d",a);
};
block();
重新clang
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("block=====%d",a);
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 1;
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block->FuncPtr(block);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
发现__main_block_impl_0
中多了一个属性a.
__main_block_func_0
中重新定义了一个局部变量a = __cself->a
,其实这里相当于进行了一个值copy,传进来的a跟新加的属性a是不一样的.所以这里是修改不了捕获的变量的
小结:
- block会自动生成一个属性来捕获外界变量
- 没有经过__block修饰的变量a和新生成的属性a,地址是不一样的,所以block中修改a跟外界的变量是没有关系的
__block原理
__block int a = 1;
修饰之后继续clang
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
printf("block=====%d",(a->__forwarding->a));
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
1};
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344));
block->FuncPtr(block);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
__block之后会生成 一个__Block_byref_a_0
的结构体,然后进行初始化.将外界a的变量指针保存在__forwarding中.将值保存在__Block_byref_a_0的属性a中.
然后在调用的时候
//这里是进行了指针拷贝,block内部的a和外界变量a是同一个地址
__Block_byref_a_0 *a = __cself->a; // bound by ref
小结:
- __block修饰之后,会生成相应的结构体
__Block_byref_a_0
,保存了原始变量的指针和值 - 调用的时候传递一个指针地址给block,所以经过__block修饰之后就可以对外界变量进行修改
Block结构
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;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_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
//下边可能有Block_descriptor_2 Block_descriptor_3
};
为什么说可能有2和3呢,因为他是根据一个枚举来进行动态添加的(2 BLOCK_HAS_COPY_DISPOSE ; 3 BLOCK_HAS_SIGNATURE)
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
};
通过调用一下方法进行判断添加
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
sizeof
内存大小,那么我们可以通过内存偏移来进行读取.
Block内存变化
打断点然后看汇编,汇编会来到
objc_retainBlock
,我们在这里打断点,然后读取寄存器,
(lldb) register read x0
x0 = 0x0000000100cc0038 004-Block结构与签名`__block_literal_global
(lldb) po 0x0000000100cc0038
<__NSGlobalBlock__: 0x100cc0038>
signature: "v8@?0"
invoke : 0x100cbe198 (/private/var/containers/Bundle/Application/E874D6A7-6D8F-49D1-A88C-D65A9A862D99/004-Block结构与签名.app/004-Block结构与签名`__29-[ViewController viewDidLoad]_block_invoke)
是一个_NSGlobalBlock_
.
NSString *name = @"JasonLee";
void (^block1)(void) = ^{
NSLog(@"LG_Block - %@",name);
};
block1();
访问外界变量,再来看汇编
register read x0
x0 = 0x000000016fa49ba8
(lldb) po 0x000000016fa49ba8
<__NSStackBlock__: 0x16fa49ba8>
signature: "v8@?0"
invoke : 0x1003ba16c (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__29-[ViewController viewDidLoad]_block_invoke)
copy : 0x1003ba198 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__copy_helper_block_e8_32s)
dispose : 0x1003ba1a0 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__destroy_helper_block_e8_32s)
此时变成了__NSStackBlock__
.
我们继续stepIn
一步一步往下走,最终来到_Block_copy
,
libsystem_blocks.dylib`_Block_copy:
-> 0x1981ef838 <+0>: pacibsp
0x1981ef83c <+4>: stp x22, x21, [sp, #-0x30]!
0x1981ef840 <+8>: stp x20, x19, [sp, #0x10]
0x1981ef844 <+12>: stp x29, x30, [sp, #0x20]
0x1981ef848 <+16>: add x29, sp, #0x20
.
.
.
0x1981ef944 <+268>: b 0x1981ef94c ; <+276>
0x1981ef948 <+272>: mov x20, #0x0
0x1981ef94c <+276>: mov x0, x20
0x1981ef950 <+280>: ldp x29, x30, [sp, #0x20]
0x1981ef954 <+284>: ldp x20, x19, [sp, #0x10]
0x1981ef958 <+288>: ldp x22, x21, [sp], #0x30
0x1981ef95c <+292>: retab
最后一行return出断点,继续读取寄存器
(lldb) register read x0
x0 = 0x00000002823c49c0
(lldb) po 0x00000002823c49c0
<__NSMallocBlock__: 0x2823c49c0>
signature: "v8@?0"
invoke : 0x1003ba16c (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__29-[ViewController viewDidLoad]_block_invoke)
copy : 0x1003ba198 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__copy_helper_block_e8_32s)
dispose : 0x1003ba1a0 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__destroy_helper_block_e8_32s)
此时block变为__NSMallocBlock__
堆block
小结:
- 当block访问外界变量时会从全局block变为栈block然后在进行block_copy之后变为堆block
block签名
从上边的读取寄存器我们可以看到signature: "v8@?0"
,证明block是有签名的
BlockCopy(第一层拷贝)
我们知道block怎么从栈去copy到堆区呢,刚才我们汇编也看到了,当捕获外界变量时会调用_Block_copy
,来看下源码
// 栈 -> 堆 研究拷贝
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
//判1空
if (!arg) return NULL;
// The following would be better done as a switch statement
//强转
aBlock = (struct Block_layout *)arg;
//block的内存管理 不受底层runtime管理,自己管理
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
//如果已经是一个全局block,则直接return
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
//重点来了
// Its a stack block. Make a copy.
//重新申请一个跟aBlock大小一样的空间地址
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
//判空如果申请地址失败直接返回NULL
if (!result) return NULL;
//接下来就是平移,拷贝操作
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
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
//然后将block标记为堆block
result->isa = _NSConcreteMallocBlock;
return result;
}
}
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
//这里说下为什么要+2 而不是+1 ,因为这里0X1代表的是一个BLOCK_DEALLOCATING是否正在析构的枚举
/**
// 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
};
*/
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
第二层拷贝(捕获变量的内存空间)
重新回到clang的cpp文件
void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_lg_name_0 *)&lg_name,
570425344)
);
必定会调用__main_block_desc_0_DATA
,看下他是个什么东西
__main_block_desc_0_DATA = { 0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
}
然后调用__main_block_copy_0
static void __main_block_copy_0(
struct __main_block_impl_0*dst,
struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->lg_name,
(void*)src->lg_name,
8/*BLOCK_FIELD_IS_BYREF*/);
}
那么来看看_Block_object_assign
的源码 注意这里传进来的是个8/BLOCK_FIELD_IS_BYREF/
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:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
//根据传进来的flag BLOCK_FIELD_IS_BYREF 代码回来到这行
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
/**
// Runtime entry points for maintaining the sharing knowledge of byref data blocks.
// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
*/
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) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//✔️ 问题 - __block 修饰变量 block具有修改能力
//这里无论是在原来的区域还是在新的区域同时指向同一个对象,所以block具有了修改能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
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;
}
(*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;
}
第三层copy
继续看clang之后的代码
__Block_byref_lg_name_0 lg_name = {
(void*)0,
(__Block_byref_lg_name_0 *)&lg_name,
33554432,
sizeof(__Block_byref_lg_name_0),
__Block_byref_id_object_copy_131, == keep
__Block_byref_id_object_dispose_131,
((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_5s_4100t0cd5rn_d7gx0n5wqh8w0000gn_T_main_0098f0_mi_0)};
// bref 结构体
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
这里为什么要看__Block_byref_id_object_copy_131 呢? 在_Block_byref_copy中有这样一行代码(*src2->byref_keep)(copy, src);
byref_keep是个什么呢
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
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结构体是一样的根据flag判断是否有Block_byref_2 Block_byref_3. __Block_byref_id_object_copy_131在第五个位置,那么也就是说__Block_byref_id_object_copy_131 = keep.
_Block_object_assign我们熟悉,刚研究过
那么为什么_Block_object_assign((char*)dst + 40, *(void * ) ((char)src + 40), 131); 这里要加上40呢,我们继续回来看看__block修饰以后生成的结构体
struct __Block_byref_lg_name_0 {
void *__isa; //8字节
__Block_byref_lg_name_0 *__forwarding; //8字节
int __flags; //4字节
int __size; //4字节
void (*__Block_byref_id_object_copy)(void*, void*); //8字节
void (*__Block_byref_id_object_dispose)(void*); //8字节
NSString *lg_name;
};
5 * 8 = 40 内存便宜40正好得到我们的name.找到我们要修改的对象,然后在进行对象copy.这就是我们的block的三层拷贝.
总结:
block分类
block分为6中,常用的有
- _NSConcreteStackBlock 栈
- _NSConcreteMallocBlock 堆
- _NSConcreteGlobalBlock 全局 三种
解决block循环应用的三种方式
- 用__weak修饰
- 声明临时变量__block VC = self 并在block中手动置位nil
- 把self当做一个参数传进去
block内存变化
当block访问外界变量时会从全局block变为栈block然后在进行block_copy之后变为堆block
block签名
从上边的读取寄存器我们可以看到signature: "v8@?0"
,证明block是有签名的
block捕获外界变量
- block会自动生成一个属性来捕获外界变量
- 没有经过__block修饰的变量a和新生成的属性a,地址是不一样的,所以block中修改a跟外界的变量是没有关系的
- 经过__block修饰,之后会发生三层拷贝,这时候存储的不在是值,而是捕获对象的指针.拷贝之后的指针和原指针指向的是同一片地址所以block才具有了修改的能力.