美文网首页
Block 之 block引用外部对象

Block 之 block引用外部对象

作者: ychen3022 | 来源:发表于2018-11-02 13:49 被阅读8次
前言:block变量捕获
在学习block的变量捕获时,我们知道了block对全局变量不捕获,对局部变量捕获。但讲解block引用的变量都是基本数据类型,那引用对象类型的时候呢? image.png
1、block引用外部对象

那下面就MRC环境,我们对三种类型的block来进行分析:
在类MJPerson的delloc方法中加入打印代码来观察person什么时候销毁。

@implementation MJPerson
-(void)dealloc{
    [super dealloc];//ARC情况下不需要调用[super dealloc],MRC情况下需要
    NSLog(@"person-----dealloc");
}
@end
  • NSGlobalBlock
    不捕获外部变量,所以不在讨论范围内

  • NSStackBlock

//情况1: block在栈上 ,不论有没有用__weak修饰对象,不会影响打印结果
- (void)viewDidLoad { //这个括号内都是MyBlock的作用域
    [super viewDidLoad];

    typedef void(^MyBlock)();
    MyBlock block;

    {//这个括号是person的作用域

        MJPerson *person = [[MJPerson alloc] init];
        person.age = 10;
        person.name = @"Alice";

        //使用__weak修饰 用weakPerson代替person,也不会影响打印结果
        __weak MJPerson *weakPerson = person;
        block = ^{
            NSLog(@"person的age为%d",person.age);
            NSLog(@"person的name为%@", weakPerson.name);
        };
        NSLog(@"block的类型是%@",[block class]);

        NSLog(@"将要离开person的作用域了");
        [person release];//MRC环境下要手动释放
    }
    //按照局部变量的作用域来说,person离开了这个括号的时候会被释放,但是block还没有被释放
    NSLog(@"将要离开block的作用域了");
    [block release];//MRC环境下要手动释放
}

//问题:在ARC环境下,person会什么时候释放吗?
//答:在ARC情况下,block会自己copy变成NSMallocBlock类型的block,所以我们研究NSMallocBlock类型的情况就好。往下面看。
=======================================================
打印结果:
BlockTest[2933:174034] block的类型是__NSStackBlock__
BlockTest[2933:174034] 将要离开person的作用域了
BlockTest[2933:174034] person-----dealloc
BlockTest[2933:174034] 将要离开block的作用域了

从上面的结果可以看出,NSStackBlock类型的block引用外部对象时,不论有没有用__weak修饰对象,都不会对其强引用(对象person的引用计数没有加1)。

  • NSMallocBlock
//情况2:block在堆上 : 这个里面需要好好分析是 weak还是strong
-(void)viewDidLoad{
    [super viewDidLoad];

    typedef void(^MyBlock)();
    MyBlock block;

    {//这个括号是person的作用域

        MJPerson *person = [[MJPerson alloc] init];
        person.age = 10;
        person.name = @"Alice";
   
        //注意:case1 和 case2 不能同时存在
        
        //case1:不用__weak 修饰
        block = [^{
            NSLog(@"person的age为%d",person.age);
            NSLog(@"person的name为%@",person.name);
        } copy];

        //case2:用__weak 修饰 MJPerson
        __weak MJPerson *weakPerson = person;
        block = [^{
            NSLog(@"person的age为%d",weakPerson.age);
            NSLog(@"person的name为%@",weakPerson.name);
        } copy];


        NSLog(@"block的类型是%@",[block class]);

        NSLog(@"将要离开person的作用域了");
        [person release];//MRC环境下要手动释放
    }

    //按照局部变量的作用域来说,person离开了这个括号的时候会被释放,但是block还没有被释放
    NSLog(@"将要离开block的作用域了");
    [block release];//MRC环境下要手动释放
}
=======================================================
case1:不用__weak 修饰
打印结果:
BlockTest[3256:195500] block的类型是__NSMallocBlock__
BlockTest[3256:195500] 将要离开person的作用域了
BlockTest[3256:195500] 将要离开block的作用域了
BlockTest[3256:195500] person-----dealloc
=======================================================
case2:用__weak 修饰 MJPerson
打印结果:
BlockTest[3411:205726] block的类型是__NSMallocBlock__
BlockTest[3411:205726] 将要离开person的作用域了
BlockTest[3411:205726] person-----dealloc
BlockTest[3411:205726] 将要离开block的作用域了

看表面现象总结:NSMallocBlock类型的block引用外部对象时,如果被__weak修饰时,不会强引用(对象person的引用计数不加1),否则被强引用(对象person的引用计数加1)。

2、探究本质

编译一下ViewController.m文件,看源码

//和之前的编译命令不一样来,因为文件了含有__weak,需要运行时环境才可编译,所以加上runtime=ios-8.0.0(其中ios版本不是非要写8.0.0)
//要在环境为ARC下面执行命令哟

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 ViewController.m

引用了MJPerson对象的block底层结构

   MJPerson *person = [[MJPerson alloc] init];
   person.age = 10;
   person.name = @"Alice";
   //没有用__weak修饰,其实就是默认strong修饰的    
   block = [^{
        NSLog(@"person的age为%d",person.age);
        NSLog(@"person的name为%@",person.name);
   } copy];

=======================================================
编译后的底层block结构:
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  //注意:这里是__strong类型的person
  MJPerson *__strong person;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

引用了__weak MJPerson对象的block底层结构

  MJPerson *person = [[MJPerson alloc] init];
  person.age = 10;
  person.name = @"Alice";
 //weak修饰       
  __weak MJPerson *weakPerson = person; 
  block = [^{
       NSLog(@"person的age为%d",weakPerson.age);
       NSLog(@"person的name为%@",weakPerson.name);
  } copy];
=======================================================
编译后的底层block结构:
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  //注意:这里是__weak类型的person
  MJPerson *__weak weakPerson;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, MJPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

相比之前学习block捕获auto变量的时候,这里多了一个copy和dispose操作。

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  //注意:下面的copy、dispose是函数指针,分别对应着__ViewController__viewDidLoad_block_copy_0、__ViewController__viewDidLoad_block_dispose_0。
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};

__ViewController__viewDidLoad_block_copy_0函数;

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

__ViewController__viewDidLoad_block_dispose_0函数:

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

_ViewController__viewDidLoad_block_copy_0函数中都有一个_Block_object_assign方法,这个函数会根据auto变量(MJPerson)的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain:计数器加一)或者弱引用

同理,__ViewController__viewDidLoad_block_dispose_0函数中的_Block_object_dispose方法也会在block销毁的时候对auto变量(MJPerson)进行类似(release:计数器减一)释放操作

3 、总结
  • <1>三种类型block是如何访问对象类型的auto变量的?
    1、如果block是在数据区,不会访问auto类型对象,不在讨论范围
    2、如果block是在栈上,将不会对auto变量产生强引用
    3、如果block是在堆上
    (1) block被拷贝到堆上时,会调用block内部的copy函数,然后copy函数内部会调用_Block_object_assign函数,然后_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain:计数器加一)或者弱引用
    (2)block从堆上移除时候,会调用block内部的dispose函数,然后dispose函数内部会调用_Block_object_dispose函,然后_Block_object_dispose函数会自动释放引用的auto变量(release:计数器减一)

相关文章

网友评论

      本文标题:Block 之 block引用外部对象

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