Object-C高级编程读书笔记(5)——Block的对象类型截取

本文详细探讨了Objective-C中Block对象对于不同类型数据的处理方式,特别是对象类型(如NSArray、NSString等)的存储机制。文章通过示例代码解释了Block如何根据不同所有权类型(__strong和__weak)存储对象,以及如何避免Block与对象之间的循环引用问题。

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

在之前的博客中,我们探讨了Block对于普通类型数据的截取,其实现很简单,就是在Block对象中保存一份值拷贝。

那么,对于OC中的对象类型(包括系统自带类型NSArray,NSString和自定义对象类型),Block又是怎么存储的呢?在《OC高级编程》书中对于该部分,可能是由于XCode编译器版本不同的原因,有些错误,现在就我个人的理解,总结一下。

一个事实

在ARC有效时,id类型以及对象类型变量必定附加所有权修饰符,缺省为__strong修饰符

Block对于不同所有权对象变量的存储

既然知道了对象类型变量或id类型变量有所有权修饰符的不同,那么,对于Block中对象类型的存储,也可以分为对于__strong对象(默认)和__weak对象的存储。

代码1:

typedef void(^blk_t)(id);

int main(int argc, const char * argv[]) {
    blk_t blk;
    {
         id array = [[NSMutableArray alloc] init];
        
        id array2 = array;
        blk = ^(id obj){
            [array2 addObject:obj];
            NSLog(@"Now the count of array = %ld", (long)[array2 count]);
        };
    }
    
    blk([[NSString alloc] init]);
    blk([[NSString alloc] init]);
    blk([[NSString alloc] init]);
    return 0;
}

输出结果为


上面的NSString对象被成功的插入到了Block对象中的array2中,说明Block保存住了array2对象。Block中的array2并没有因为Block外的array2和array的作用域结束而释放。


代码2

typedef void(^blk_t)(id);

int main(int argc, const char * argv[]) {
    blk_t blk;
    {
         id array = [[NSMutableArray alloc] init];
        
        id <span style="color:#FF0000;">__weak</span> array2 = array;
        blk = ^(id obj){
            [array2 addObject:obj];
            NSLog(@"Now the count of array = %ld", (long)[array2 count]);
        };
    }
    
    blk([[NSString alloc] init]);
    blk([[NSString alloc] init]);
    blk([[NSString alloc] init]);
    return 0;
}
输出结果


array2中的元素并没有增加,我们再将Block中的array2打印出来,会发现此时array2已经被置为null,可见,当加上__weak所有权修饰符的变量置于Block中后,该变量会因为Block变量外的变量的释放而释放。


那么对于外部传入到Block中的__strong对象变量与__weak对象变量,Block又是怎么处理的呢?

同样,我们用clang命令将代码编译为C++代码

__strong array2对应的代码

typedef void(*blk_t)(id);


<span style="color:#FF0000;">struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array2;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array2, int flags=0) : array2(_array2) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};</span>
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
  id array2 = __cself->array2; // bound by copy

            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_3c4b88_mi_0, (long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array2, sel_registerName("count")), array2);
        }
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*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 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, const char * argv[]) {
    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 array2 = array;
        blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array2, 570425344));
    }

    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    return 0;
}



__weak array2对应的代码

typedef void(*blk_t)(id);

<span style="color:#FF0000;">
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __weak id array2;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __weak id _array2, int flags=0) : array2(_array2) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};</span>
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
  __weak id array2 = __cself->array2; // bound by copy

            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_bbd239_mi_0, (long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array2, sel_registerName("count")), array2);
        }
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*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 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, const char * argv[]) {
    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_gc(weak))) array2 = array;
        blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array2, 570425344));
    }

    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    return 0;
}

发现转换后的代码基本毫无区别,唯一的区别是在Block的impl中。类似普通类型变量,在Block的impl中同样添加了对应的成员变量来存储外部传入的变量,这里为

id array2;

当传入Block中的是__strong类型对象时,Block会将对应变量声明为stong属性

id array2;
当传入Block中的是__weak类型对象时,Block会将对应变量声明为weak属性

__weak id array2;

这样就解释了为什么代码1中的array2没被释放(此时Block对该对象保持引用。即使Block外部对应的对象都被释放,在Block中仍然存在一份引用,对象并不会被系统真正释放)

而在代码2中,由于在Block中是weak变量,当Block外的对象释放时,由于Block本身并没有对该对象保持引用,导致对象引用计数为0,对象释放,Block中的weak array2为置为nil。


总结一下,在Block对于对象类型的变量截取遵循以下原则:

1.传入Block中若为__strong类型对象,Block中对应添加strong成员变量保持对其引用

2.传入Block中的若无__weak类型对象,Block对应添加weak成员变量,并不会保持其引用,当外部计数为0时,weak对象置为nil。


Block循环引用

了解了上面Block对于对象类型变量的截取规则后,我们就可以理解下面在使用Block成员变量时,常见的一个循环引用问题。

有如下代码

typedef void(^blk_t)(void);
@interface MyObjec:NSObject
@property(nonatomic, strong) blk_t blk; 
@end

@implementation MyObject
-(instancetype) init
{
     self = [super init];
    if(self)
    {
        _blk = ^{ NSLog(@"I am %@", self);};
    }
    return self;
}
@end

上面会引起循环引用 导致MyObject对象永远不会被释放。

这是为什么呢?

细看MyObject对象的声明,其包含一个strong的属性blk,即MyObject对象持有blk。

而在blk的定义中,

_blk = ^{ NSLog(@"I am %@", self);};
在Block中传入了self对象(ARC下默认对象均为__strong所有权类型)。结合上面的讨论,当__strong对象类型传入Block中时,Block会对其强引用。

如此,MyObject对象对blk强引用,blk对self(即MyObject对象)强引用,循环引用。


要打破循环引用,我们只需要修改代码为

typedef void(^blk_t)(void);
@interface MyObjec:NSObject
@property(nonatomic, strong) blk_t blk; 
@end

@implementation MyObject
-(instancetype) init
{
     self = [super init];
    if(self)
    {
        id __weak tmp = self;
        _blk = ^{ NSLog(@"I am %@", tmp);};
    }
    return self;
}
@end

这样,由于传入Block中的是一个__weak对象,因此Block不会对其强引用,而是相应的用weak存储。又因为blk变量是属于MyObject对象的属性,因此也不用担心在调用Block时,tmp被释放的问题。

__block关键字对象截取

在对象声明的前面,我们同样可以加上__block变量来表明其为一个block变量。

typedef void(^blk_t)(id);

int main(int argc, const char * argv[]) {
    blk_t blk;
    {
         id array = [[NSMutableArray alloc] init];
        
        id __block __weak array2 = array;
        blk = ^(id obj){
            [array2 addObject:obj];
            NSLog(@"Now the count of array = %ld %@", (long)[array2 count], array2);
        };
    }
    
    blk([[NSString alloc] init]);
    blk([[NSString alloc] init]);
    blk([[NSString alloc] init]);
    return 0;
}

在这里我们定义了一个__weak的__block变量 array2。

通过clang转换为C++代码,如下:

typedef void(*blk_t)(id);

<span style="color:#FF0000;">struct __Block_byref_array2_0 {
  void *__isa;
__Block_byref_array2_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 __weak id array2;
};</span>

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_array2_0 *array2; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array2_0 *_array2, int flags=0) : array2(_array2->__forwarding) {
    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) {
  __Block_byref_array2_0 *array2 = __cself->array2; // bound by ref

            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)(array2->__forwarding->array2), sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_758659_mi_0, (long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)(array2->__forwarding->array2), sel_registerName("count")), (array2->__forwarding->array2));
        }
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, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 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, const char * argv[]) {
    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"));

        <span style="color:#FF0000;">__Block_byref_array2_0 array2</span> = {(void*)1,(__Block_byref_array2_0 *)&array2, 33554432, sizeof(__Block_byref_array2_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, array};
        blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array2_0 *)&array2, 570425344));
    }

    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
    return 0;
}

我们发现,id __block __weak array2 变量转换为了

<span style="color:#FF0000;">__Block_byref_array2_0 array2</span>
类型。再看其定义

<span style="color:#FF0000;">struct __Block_byref_array2_0 {
  void *__isa;
__Block_byref_array2_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 __weak id array2;
};</span>

这种结构与我们之前对于__block普通变量的解析类似,只不过这里的变量值前面添加了__weak。这样,__block中并不对array2做强引用,当Block外部的array2对象释放时,Block内部对应的array2也会释放。上述代码的执行结果为:


当我们将array2变量声明为__block __strong类型的呢?对应的,在__block中,array2会被强引用,具体细节就不再复述了。


补充:Block对于类成员变量对象,global对象,局部static对象 的截获方式

在做项目时候,发现一个有意思的问题,就是对于Object类中,如果在一个Block中使用类的成员变量,则Block不会对该变量进行单独存储,而会直接使用该变量。如下代码

typedef void(^blk_t)(void);
@interface MyObj:NSObject
@property(nonatomic, strong) NSString *name;
-(void) showName;
@end

@implementation MyObj

-(void) showName
{
    blk_t blk = ^{
        NSLog(@"My name is %@", _name);
    };
    blk();
}

@end

int main(int argc, const char * argv[]) {
    MyObj *obj = [MyObj new];
    obj.name = @"John";
    
    [obj showName];
    return 0;
}

我们翻译成c++实现后, 再看showName方法中的Block的定义
struct __MyObj__showName_block_impl_0 {
  struct __block_impl impl;
  struct __MyObj__showName_block_desc_0* Desc;
  MyObj *self;
  __MyObj__showName_block_impl_0(void *fp, struct __MyObj__showName_block_desc_0 *desc, MyObj *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
这里虽然Block中使用的是_name属性,但在Block中保存的其实是MyObj的对象。

有趣的是,虽然之前规定在Block中只能够改变__block变量的值,但对于对象而言,其属性是可以在Block中改变的。由于Block中是直接引用的对象本身,在属性在Block中改变的同事,对象本身的属性也就被改变了。

我们再看对于global对象,局部static对象,Block又是如何截取他们的。

#import <Foundation/Foundation.h>
typedef void(^blk_t)(void);
NSString *str1 = @"global string";

int main(int argc, const char * argv[]) {
    static NSString *str2 = @"static string";
  //  NSString *str3 = @"noraml string";
    blk_t blk = ^{
        NSLog(@"global string is %@, static string is %@", str1, str2);
        str1 = @"new global";
        str2 = @"new static";
      //  str3 = @"new normal string";
    };
    blk();
    NSLog(@"After block the gloabl string is %@, static string is %@", str1, str2);
    return 0;
}

翻译为C++实现,查看blk的定义

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSString **str2;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString **_str2, int flags=0) : str2(_str2) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSString **str2 = __cself->str2; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_8caa8b_mi_3, str1, (*str2));
        str1 = (NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_8caa8b_mi_4;
        (*str2) = (NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_8caa8b_mi_5;

    }


发现,对于global对象str1,Block中根本没有存储,而是直接使用的str1本身。对于局部static对象,Block也没有直接进行引用,而是对应一个指针的指针。不管怎么样的方式,我们均可在Block中直接改变其值,并能够在Block外得到对应的改变。

那么对于普通的局部变量str3呢?我们将注释去掉,会发现XCode会报一个缺少__block关键字错误,不让我们编译。


看来,对于类的成员变量,global变量,局部static变量,Block对于这些对象的截获,也是有不同的方式的。同时对于这些变量对象,即使不添加__block关键字,也可以在Block中直接修改其值并在Block外部也得到相应的改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值