美文网首页iOS点点滴滴
Block访问对象类型

Block访问对象类型

作者: YY_Lee | 来源:发表于2019-01-28 17:22 被阅读6次

在上篇文章中有说到ARC环境下,编译器会根据情况自动将栈上的block拷贝到堆上,具体情况以下:

  • block作为函数返回值时
  • 将block赋值给强指针时
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD API的方法参数时。

下面通过代码验证这些情况:

block作为函数返回值和将block赋值给强指针:

typedef void(^BlockDemo)(void);

- (void)viewDidLoad {
  [super viewDidLoad];
  int a = 1;
  int c = 2;
  // 访问自动变量
  NSLog(@"%@",^{NSLog(@"%d",a);});//打印结果:<__NSStackBlock__: 0x7ffeefac59e0>
  // block作为函数/方法返回值
  NSLog(@"%@",[[self getBlock] class]); //打印结果:__NSMallocBlock__
  BlockDemo strongBlock = ^{NSLog(@"%d",c);};
  // 强指针指向block
  NSLog(@"%@",[strongBlock class]); //打印结果:__NSMallocBlock__
}

- (BlockDemo)getBlock {
  int b = 1;
  return ^{
      NSLog(@"%d",b);
  };
}

从上面代码的打印结果可以看出,单纯的一个访问auto变量的block是NSStackBlock类型,但当这样一个block作为函数返回值,当有强指针指向这样的block时,它是NSMallocBlock类型。

block作为GCD API的方法参数:

- (void)GCDBlcokCaptureObject {
  Person *person = nil;
  person = [Person new];
  person.name = @"jacden";
  person.friends = @[@"s",@"2"];
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      NSLog(@"----name:%@-----",person.name);
      NSLog(@"------dispatch_after-------");
});

## 情况1和情况2分别和上面代码一起执行

## 情况1
person = [Person new];
person.name = @"lily";

打印结果:
/*
 person dealloc
----name:jacden-----
------dispatch_after-------
 person dealloc
*/


## 情况2
person.name = @"lily";
打印结果:
/*
----name:lily-----
------dispatch_after-------
 person dealloc
*/
}

解释下上面的打印结果,首先1s后block块的代码能执行说明这个block在堆上不是在栈上,能打印person的name值说明block内部强引用了person对象,不然GCDBlcokCaptureObject方法执行完后person、block就会销毁,就会再有打印。

情况1先打印person dealloc,这是因为dispatch_after是异步执行的,1s后才会执行它block内部代码,情况1的代码会先执行,情况1代码执行完方法GCDBlcokCaptureObject也就执行完毕了,因此情况1person指针销毁,新创建的person对象没有强指针指向它,于是销毁调用了dealloc方法。name打印结果为jacden, 是因为block捕获auto变量跟外部变量的类型是一致的,此时外部是Person *指针,block内部会有个Person *指针指向外部变量,情况1是修改了外部person的指向,而block内部的Person *指针仍指向原来的变量。所有打印结果仍然是jacden;1s后block执行完毕,block销毁,不再有指针指向创建的第一个person对象,该对象销毁调用dealloc方法。

情况2只是修改了外部person指针指向的对象的属性值,即所指向的内存中的对象发生了改变,指针指向的地址跟block内部指向的地址是一样的,所有打印结果是最新值;1s后block执行完毕,block销毁,不再有指针指向创建的第一个person对象,该对象销毁调用dealloc方法。

对于情况2,有个迷惑的点,修改person对象的属性值和修改person这个指针是不一样的,需要好好理解下。

下面我们来看下block内部访问对象类型的auto变量的一些情况;

  • block在栈上,不会对auto变量产生强引用
  • block被拷贝到堆上,内部会调用copy函数,copy函数会调用_Block_object_assign函数,_Block_object_assign函数根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)等作出相应的操作,形成对auto变量的强引用或弱引用
  • 当block从堆上移除时,调用block内部dispose函数,dispose函数内部调用_Block_object_dispose函数自动释放引用的auto变量;
函数 调用时机
copy函数 栈上的block复制到堆时
dispose函数 堆上的block被废弃时

访问变量用strong修饰时:

- (void)blockCaptureObject {
    Person *person = [Person new];
    person.name = @"jack";
    
    BlockDemo block = ^{
        NSLog(@"%@",person.name);
    };
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });
}

下面是通过clang转换成c++后的代码
struct __ViewController__blockCaptureObject_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__blockCaptureObject_block_desc_0* Desc;
  Person *__strong person;  ##这里显示是强指针
  __ViewController__blockCaptureObject_block_impl_0(void *fp, struct __ViewController__blockCaptureObject_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __ViewController__blockCaptureObject_block_func_0(struct __ViewController__blockCaptureObject_block_impl_0 *__cself) {
  Person *__strong person = __cself->person; // bound by copy
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_a592c8_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));
}

static void __ViewController__blockCaptureObject_block_copy_0(struct __ViewController__blockCaptureObject_block_impl_0*dst, struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __ViewController__blockCaptureObject_block_dispose_0(struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __ViewController__blockCaptureObject_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__blockCaptureObject_block_impl_0*, struct __ViewController__blockCaptureObject_block_impl_0*);
  void (*dispose)(struct __ViewController__blockCaptureObject_block_impl_0*);
} __ViewController__blockCaptureObject_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockCaptureObject_block_impl_0), __ViewController__blockCaptureObject_block_copy_0, __ViewController__blockCaptureObject_block_dispose_0};

static void _I_ViewController_blockCaptureObject(ViewController * self, SEL _cmd) {
    Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_a592c8_mi_0);

    BlockDemo block = ((void (*)())&__ViewController__blockCaptureObject_block_impl_0((void *)__ViewController__blockCaptureObject_block_func_0, &__ViewController__blockCaptureObject_block_desc_0_DATA, person, 570425344));

    dispatch_after(dispatch_time((0ull), (int64_t)(2 * 1000000000ull)), dispatch_get_main_queue(), ((void (*)())&__ViewController__blockCaptureObject_block_impl_1((void *)__ViewController__blockCaptureObject_block_func_1, &__ViewController__blockCaptureObject_block_desc_1_DATA, block, 570425344)));
}

打印结果:
jack
person dealloc

从代码可以看出block内部会有个Person *的强指针指向外部的person,__ViewController__blockCaptureObject_block_desc_0函数相比访问基本数据类型,多了两个成员变量copy函数和dispose函数,在函数的初始化时将__ViewController__blockCaptureObject_block_copy_0函数赋值给了copy函数,将__ViewController__blockCaptureObject_block_dispose_0赋值给了dispose函数。这两个函数就是上面所列举的用来处理person的内存。另外2s后执行block块代码,能打印出来name的值说明block内部强引用着person。block执行完,person执行dealloc方法,说明block不再有强引用着person。

访问变量用weak修饰时:

- (void)blockCaptureObject {
   Person *person = [Person new];
   person.name = @"jack";
   
   __weak Person *weakPerson = person;
   BlockDemo block = ^{
       NSLog(@"%@",weakPerson.name);// 打印结果:null
   };
   
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       block();
   });
}

下面是通过clang转换成c++后的代码

struct __ViewController__blockCaptureObject_block_impl_0 {
 struct __block_impl impl;
 struct __ViewController__blockCaptureObject_block_desc_0* Desc;
 Person *__weak weakPerson; ##这里显示是弱指针
 __ViewController__blockCaptureObject_block_impl_0(void *fp, struct __ViewController__blockCaptureObject_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
   impl.isa = &_NSConcreteStackBlock;
   impl.Flags = flags;
   impl.FuncPtr = fp;
   Desc = desc;
 }
};
static void __ViewController__blockCaptureObject_block_func_0(struct __ViewController__blockCaptureObject_block_impl_0 *__cself) {
 Person *__weak weakPerson = __cself->weakPerson; // bound by copy

       NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_b47619_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("name")));
   }
static void __ViewController__blockCaptureObject_block_copy_0(struct __ViewController__blockCaptureObject_block_impl_0*dst, struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __ViewController__blockCaptureObject_block_dispose_0(struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __ViewController__blockCaptureObject_block_desc_0 {
 size_t reserved;
 size_t Block_size;
 void (*copy)(struct __ViewController__blockCaptureObject_block_impl_0*, struct __ViewController__blockCaptureObject_block_impl_0*);
 void (*dispose)(struct __ViewController__blockCaptureObject_block_impl_0*);
} __ViewController__blockCaptureObject_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockCaptureObject_block_impl_0), __ViewController__blockCaptureObject_block_copy_0, __ViewController__blockCaptureObject_block_dispose_0};

static void _I_ViewController_blockCaptureObject(ViewController * self, SEL _cmd) {
   Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
   ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_b47619_mi_0);

   __attribute__((objc_ownership(weak))) Person *weakPerson = person;
   BlockDemo block = ((void (*)())&__ViewController__blockCaptureObject_block_impl_0((void *)__ViewController__blockCaptureObject_block_func_0, &__ViewController__blockCaptureObject_block_desc_0_DATA, weakPerson, 570425344));

   dispatch_after(dispatch_time((0ull), (int64_t)(2 * 1000000000ull)), dispatch_get_main_queue(), ((void (*)())&__ViewController__blockCaptureObject_block_impl_1((void *)__ViewController__blockCaptureObject_block_func_1, &__ViewController__blockCaptureObject_block_desc_1_DATA, block, 570425344)));

}

打印结果:
person dealloc
(null)

从代码可以看出block会有个Person *的弱指针指向外部的person,同样__ViewController__blockCaptureObject_block_desc_0函数相比访问基本数据类型,多了两个成员变量copy函数和dispose函数。另外执行block块代码前,person就调用了dealloc方法,打印name的值为(null),说明block内部没有强引用着person,blockCaptureObject方法执行完person就销毁了。

相关文章

网友评论

    本文标题:Block访问对象类型

    本文链接:https://www.haomeiwen.com/subject/ianojqtx.html