截获对象
以下源代码生成并持有NSMutableArray类的对象,由于附有__strong修饰符的复制目标变量的作用域立即结束,所以对象被立即释放被废弃。
{
id array = [[NSMutableArray alloc] init];
}
复制代码
typedef void (^blk_t)(id);
blk_t blk ;
{
id array =[[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld",[array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
复制代码
变量作用域结束的同时,变量array被废弃,其强引用失效,因此复制给变量array的NSMutableArray类的对象必定被释放并废弃,但是执行以上代码,结果正常:
2017-12-14 09:48:26.226464+0800 ImageOrientation[7963:2739050] array count = 1
2017-12-14 09:48:26.226621+0800 ImageOrientation[7963:2739050] array count = 2
2017-12-14 09:48:26.226759+0800 ImageOrientation[7963:2739050] array count = 3
复制代码
转换后的代码如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6__v3tcjsp9277_82bgpvhr8l6c0000gn_T_main_3310c3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
int main(int argc, char * argv[]) {
typedef void (*blk_t)(id);
blk_t blk ;
{
id array =((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
}
复制代码
OC的运行时库能够准确把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block用结构体中即使含有附有__strong修饰符或者__weak修饰符的变量,也可以恰当地进行初始化和废弃。为此需要使用在__main_block_desc_0结构体中增加的成员变量copy和dispose,以及作为指针赋值给成员变量的__main_block_copy_0函数和__main_block_dispose_0
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};
复制代码
__main_block_copy_0函数使用_Block_object_assign函数将对象类型对象赋值给Block用结构体成员变量array中并持有该对象。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
复制代码
_Block_object_assign函数相当于retain实例方法,将对象赋值在对象类型的结构体成员变量中。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
复制代码
__main_block_dispose_0函数使用_Block_object_dispose方法,_Block_object_dispose方法相当于release实例方法,释放赋值在对象类型的结构体成员变量中的对象。
虽然copy函数和dispose函数指针被赋值在__main_block_desc_0结构体成员变量copy和dispose中,但是转换后的代码中并没有调用,而是在Block从栈上复制到堆上式以及堆上的Block被废弃时调用。
函数 | 调用时机 |
---|---|
copy | 栈上的Block被复制到堆时 |
dispose | 堆上的Block被废弃时 |
栈上的Block复制到堆有以下几种情况:
- 调用Block的copy实例方法时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch 的API中传递Block时
以上可以总结为:__Block_copy函数被调用时Block从栈复制到堆。
相对地,在释放复制到堆上的Block后,谁都不持有Block而使其被废弃时调用dispose函数。dispose函数相当于dealloc实例方法。
实际上,在解释__block说明符时已经使用了copy和dispose函数。
#include <stdio.h>
int main(int argc, char * argv[]) {
__block int val = 0;
void(^blk)(void) = ^{val=1;};
blk();
printf("val = %d\n",val);
}
复制代码
转换后的代码:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val)=1;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
printf("val = %d\n",(val.__forwarding->val));
}
复制代码
其不同之处在:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
用BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF参数区分copy函数和dispose函数的对象类型是对象还是__block变量。
对象 | BLOCK_FIELD_IS_OBJECT |
---|---|
__block变量 | BLOCK_FIELD_IS_BYREF |
由此可知,Block中使用额复制给附有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的Block所持有,因而可以超出其变量作用域而存在。
__block变量和对象
__block说明符可指定任何类型的自动变量。
__block id obj = [[NSObject alloc] init];
复制代码
该代码等同于
__block id __strong obj = [[NSObject alloc] init];
复制代码
ARC有效时,id类型以及对象类型变量必定附加所有权修饰符,__strong修饰符为默认缺省。
转换后的代码:
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id obj;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
}
复制代码
在Block中使用附有__strong修饰符的id类型或对象类型自动变量的情况下,当Block从栈复制到堆时,使用_Block_object_assign函数,持有Block截获的对象。当堆上的Block被废弃时,使用_Block_object_dispose函数,释放Block截获的对象。
在__block变量从栈复制到堆时,使用_Block_object_assign函数,持有赋值给__block变量的对象。当堆上的__block变量被废弃时,使用_Block_object_dispose函数,释放赋值给持有__block变量的对象。
由此可知,即使对象赋值复制到堆上的附有__strong修饰符的对象类型__block变量中,只要__block变量在堆上继续存在,那么该对象就会继续处于被持有的状态。
如果使用__weak修饰符,运行以下代码,《Objective-C高级编程 iOS与OS X多线程和内存管理》书中的解释为,__weak修饰的变量array在作用域外被赋值为nil,所以运行结果都为0,但是我实际运行的实际情况是1,2,3
int main(int argc, char * argv[]) {
typedef void (^blk_t)(id);
blk_t blk ;
{
id array =[[NSMutableArray alloc] init];
id __weak array2 = array;
blk = [^(id obj){
[array2 addObject:obj];
NSLog(@"array count = %ld",[array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
复制代码
然后我用clang -rewrite-objc -F /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/Foundation.framework main.m
试图转换成c++ 代码,直接报错 TAT
cannot create __weak reference because the current deployment target does
not support weak references
id __attribute__((objc_ownership(weak))) array2 = array;
复制代码
然后我将代码中的__weak替换为__unsafe_unretained修饰符,然后转换成功,代码如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array2;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array2, id _array, int flags=0) : array2(_array2), array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array2 = __cself->array2; // bound by copy
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6__v3tcjsp9277_82bgpvhr8l6c0000gn_T_main_aef6da_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array2, (void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
int main(int argc, char * argv[]) {
typedef void (*blk_t)(id);
blk_t blk ;
{
id array =((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(none))) array2 = array;
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array2, array, 570425344)), sel_registerName("copy"));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
}
复制代码
我自己的理解是:转换后的代码Block用结构体里包含了成员变量array和array2,说明该Block自动捕获了这两个变量,再加上Block调用了copy实例方法,Block从栈上复制到堆上,而转换成的__main_block_copy_0方法其中调用了_Block_object_assign方法,持有了该Block转换成的__main_block_impl_0的成员变量array和array2,所以到调用的时候这俩对象都存在,所以运行结果是1,2,3。
欢迎各位一起讨论,是不是书上的运行结果错了?? ( ̄y▽ ̄)~*捂嘴偷笑
Block循环引用
typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id) init
{
self = [super init];
blk_ = ^{NSLog(@"self = %@",self);};
return self;
}
-(void) dealloc
{
NSLog(@"dealloc");
}
@end
int main(int argc, char * argv[]) {
id o =[[MyObject alloc] init];
NSLog(@"%@",o);
return 0;
}
复制代码
编译时有warning
Capturing 'self' strongly in this block is likely to lead to a retain cycle
复制代码
运行结果为:
2017-12-14 20:51:48.691611+0800 ImageOrientation[10678:3502717] <MyObject: 0x60400000ccd0>
复制代码
MyObject类对象的Block类型成员变量blk_持有复制为Block的强引用。即MyObject类对向持有Block。init实例方法中执行的Block语法使用附有__strong修饰符的id类型变量self。并且由于Block语法赋值在了成员变量blk_中,因此通过Block语法生成在栈上的Block此时复制到了堆,并持有所使用的self。self持有Block,Block又持有self,发生了循环引用。
另外,编译器给出的warning已经提醒了~
为了避免循环引用,可以将self赋值给一个附有__weak修饰符的变量。
-(id) init
{
self = [super init];
id __weak temp = self;
//在该代码中,由于self必定存在,所以不必判断temp的值是否为nil
blk_ = ^{NSLog(@"self = %@",temp);};
return self;
}
复制代码
另外,以下代码同样发生了循环引用。是因为self引用了Block,而Block截获了self的成员变量obj_。
typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
-(id) init
{
self = [super init];
blk_ = ^{NSLog(@"obj_ = %@",obj_);};
//blk_ = ^{NSLog(@"obj_ = %@",self->obj_)};
return self;
}
复制代码
解决该问题也是可以用__weak修饰符。
-(id) init
{
self = [super init];
id __weak temp = obj_;
blk_ = ^{NSLog(@"obj_ = %@", temp);};
return self;
}
复制代码
此外,还可以用__block变量来避免循环引用。
typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id) init
{
self = [super init];
__block id temp = self;
blk_ = ^{
NSLog(@"self = %@",temp);
temp = nil;
};
return self;
}
-(void)execBlock
{
blk_();
}
-(void) dealloc
{
NSLog(@"dealloc");
}
@end
int main(int argc, char * argv[]) {
id o =[[MyObject alloc] init];
[o execBlock];
return 0;
}
复制代码
该代码如果不调用exeBlock实例方法,即不执行赋值给成员变量blk_的Block,就会引起循环引用并内存泄漏。
在生成并持有MyObject类对象的状态下会引起以下循环引用:
- MyObject类对象持有Block
- Block持有__block变量
- __block变量持有MyObject类对象
通过执行了execBlock方法,Block被执行,__block变量temp被赋值成nil。因此__block变量temp对MyObject类对象的强引用失效
- MyObject类对象持有Block
- Block持有__block变量
对比__block变量和__weak(__unsafe_unretained)修饰符避免循环引用的优点:
- 通过__block变量可控制对象的持有期间
- 在不能使用__weak修饰符的环境中不使用__unsafe_unretained
在执行Block时可动态地决定是否将nil或者其他对象赋值在__block变量中。
使用__block变量的缺点是为了避免循环引用一定要执行Block。
copy/release
ARC无效时,一般需要手动将Block从栈复制到堆。这时用copy方法来赋值,用release方法类释放。
只要Block有一次被复制并且配置在堆上,就可以通过retain实例方法持有。
但是对于配置在栈上的Block调用retain实例方法则不起任何作用。因此推荐用copy实例方法来持有Block。
另外,由于Blocks是C语言的扩展,所以在C语言中也可以使用Block语法,此时可以使用Block_copy函数和Block_release函数代替copy/release方法。
另外,ARC无效时,__block说明符被用来避免Block中的循环引用,这是由于当Block从栈复制到堆时,若Block使用的变量为附有__block说明符的id类型或者对象类型的自动变量,不会被retain;若使用的不是附有__block说明符的自动变量,则被retain。