先贴一段代码,接下来根据这段代码逐步分析__weak、__strong、__block
这些关键字的作用
一. 查看__weak、__strong、__block在这些修饰符下的源码
main函数实现
typedef void (^Block) (void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int no = 20;
__block int age = 10;
NSObject *object = [[NSObject alloc] init];
__weak NSObject *weakObject = object;
Block block = ^{
age = 20;
NSLog(@"%d", no);
NSLog(@"%d", age);
NSLog(@"%p", weakObject);
};
struct __main_block_impl_0* blockImpl = (__bridge struct __main_block_impl_0*)block;
block();
age = 30;
}
return 0;
}
以下代码是将OC代码转换为C/C++代码的结果
1. 使用__block修饰以后age
变成了__Block_byref_age_0
这种数据结构,是一个OC对象,含有isa
指针,__forwarding
指针指向自己,结构体大小__size
,真正用来存储age
数值的变量。
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
2. __main_block_impl_0这种数据结构比最简单的block
多了三个参数,分别是int no;
和NSObject *__weak weakObject;
和__Block_byref_age_0 *age;
,no
值传递,age
默认使用强引用。weakObject
使用__weak
修饰是因为外部使用了弱引用。如果外部不使用__weak
修饰,默认会使用__strong
修饰,也就是强引用。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int no;
NSObject *__weak weakObject;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3. __main_block_func_0这个函数主要就是调用block
需要执行的具体函数。可以看到访问age
的时候使用了(age->__forwarding->age) = 20;
这种方式,而不是直接使用age->age
,这是为了保证block复制到堆上以后无论是在栈上还是在堆上的block
都可以访问到同一份block
捕获的堆上的变量。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
int no = __cself->no; // bound by copy
NSObject *__weak weakObject = __cself->weakObject; // bound by copy
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_0, no);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_1, (age->__forwarding->age));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_2, weakObject);
}
4. __main_block_copy_0、_Block_object_assign在捕获对象的时候进行对象的内存管理。如果是__block
修饰的类型就是强引用,如果是对象类型根据外部的修饰类型而定,如果外部使用__strong
修饰就是强引用,如果是使用__weak
这里边就使用弱引用。可以看到这里BLOCK_FIELD_IS_BYREF
和BLOCK_FIELD_IS_OBJECT
修饰类型是不同的。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
5. __main_block_desc_0 这里比最简单的block
多了两个函数指针,分别是copy
和dispose
。函数实现就是上边4的两个函数。
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
6. main函数删除了多余的部分如下所示。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int no = 20;
__Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
NSObject *object = objc_msgSend(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
__attribute__((objc_ownership(weak))) NSObject *weakObject = object;
Block block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, no, weakObject, &age, 570425344);
block->FuncPtr(block);
(age.__forwarding->age) = 30;
}
return 0;
}
如上可以看到__block int age = 10;
变成了 __Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
age = 30;
变成了(age.__forwarding->age) = 30;
这里就是在栈上访问age
最终存储的地方,为了保持和block
内部访问到的是同一个地方所以使用了age.__forwarding
先找到堆上的age
这个结构体的地址,然后在访问堆上该结构体的变量age
。
二. 设置断点查看age
具体存放的内存地址
将以下这些结构体放在main函数上边,用来获取成员变量的内存地址的时候使用。
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age;
};
由下边可知直接获取age
的地址和使用&(blockImpl->age->__forwarding->age)
获取的地址是一样的,也就是说直接使用age
就是访问的copy到堆上的__Block_byref_age_0
结构体里边的age
。
(lldb) p/x &age
(int *) $0 = 0x000000010065a598
(lldb) p/x &(blockImpl->age)
(__Block_byref_age_0 **) $1 = 0x000000010065a560
(lldb) p/x &(blockImpl->age->age)
(int *) $2 = 0x000000010065a598
(lldb) p/x &(blockImpl->age->__forwarding->age)
(int *) $3 = 0x000000010065a598
三. __block修饰符
- __block可以用于解决block内部无法修改auto变量值的问题
- __block不能修饰全局变量、静态变量(static)
- 编译器会将__block变量包装成一个对象
![](https://img.haomeiwen.com/i3265625/8e86e024addeabfc.png)
四. __block的内存管理
-
当block在栈上时,并不会对__block变量产生强引用
-
当block被copy到堆时
a) 会调用block内部的copy函数
b) copy函数内部会调用_Block_object_assign函数
c) _Block_object_assign函数会对__block变量形成强引用(retain)
__block的内存管理-引用
-
当block从堆中移除时
a) 会调用block内部的dispose函数
b) dispose函数内部会调用_Block_object_dispose函数
c) _Block_object_dispose函数会自动释放引用的__block变量(release)
![](https://img.haomeiwen.com/i3265625/bee49378d7991182.png)
四. __block的__forwarding指针
![](https://img.haomeiwen.com/i3265625/bf39922dc3d25431.png)
五. 对象类型的auto变量、__block变量
-
当block在栈上时,对它们都不会产生强引用
-
当block拷贝到堆上时,都会通过copy函数来处理它们
__block变量(假设变量名叫做a)
a) _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/); -
对象类型的auto变量(假设变量名叫做p)
a) _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/); -
当block从堆上移除时,都会通过dispose函数来释放它们
__block变量(假设变量名叫做a)
a) _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/); -
对象类型的auto变量(假设变量名叫做p)
a) _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);
六. 被__block修饰的对象类型
-
当__block变量在栈上时,不会对指向的对象产生强引用
-
当__block变量被copy到堆时
a) 会调用__block变量内部的copy函数
b) copy函数内部会调用_Block_object_assign函数
c) _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain) -
如果__block变量从堆上移除
a) 会调用__block变量内部的dispose函数
b) dispose函数内部会调用_Block_object_dispose函数
c) _Block_object_dispose函数会自动释放指向的对象(release)
网友评论