Objective-C 之Block(4)

本文深入探讨了Objective-C中Block的内存管理机制,包括__strong、__weak、__block修饰符的作用及如何避免循环引用等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

截获对象

以下源代码生成并持有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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值