iOS Block的本质(三)

1. block对对象变量的捕获,ARC 环境

  1. block一般使用过程中都是对对象变量的捕获,那么对象变量的捕获同基本数据类型变量相同吗?

  2. 查看一下代码思考:当在block中访问的为对象类型时,对象什么时候会销毁?

    // ARC环境下代码
    typedef void (^Block)(void);
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Block block;
    {
    Person *person = [[Person alloc] init];
    person.age = 10; block = ^{
    NSLog(@"------block内部%d",person.age);
    };
    } // 执行完毕,person没有被释放
    NSLog(@"--------");
    } // person 释放
    return 0;
    }
  3. 大括号执行完毕之后,person依然不会被释放。上一篇文章提到过,person为aotu变量,传入的block的变量同样为person,即block有一个强引用引用person,所以block不被销毁的话,peroson也不会销毁。

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

2. block对对象变量的捕获,ARC 环境

  1. 在MRC环境下即使block还在,person却被释放掉了。因为MRC环境下block在栈空间,栈空间对外面的person不会进行强引用。

    //MRC环境下代码
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Block block;
    {
    Person *person = [[Person alloc] init];
    person.age = 10;
    block = ^{
    NSLog(@"------block内部%d",person.age);
    };
    [person release];
    } // person被释放
    NSLog(@"--------");
    }
    return 0;
    }
  2. block调用copy操作之后,person不会被释放。

    block = [^{
    NSLog(@"------block内部%d",person.age);
    } copy];
  3. 上文中也提到过,只需要对栈空间的block进行一次copy操作,将栈空间的block拷贝到堆中,person就不会被释放,说明堆空间的block可能会对person进行一次retain操作,以保证person不会被销毁。堆空间的block自己销毁之后也会对持有的对象进行release操作。

  4. 也就是说栈空间上的block不会对对象强引用,堆空间的block有能力持有外部调用的对象,即对对象进行强引用或去除强引用的操作。

  5. 在__weak修饰之后,person在作用域执行完毕之后就被销毁了。

    typedef void (^Block)(void);
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Block block;
    {
    Person *person = [[Person alloc] init];
    person.age = 10; __weak Person *weakPerson = person;
    block = ^{
    NSLog(@"------block内部%d",waekPerson.age);
    };
    }
    NSLog(@"--------");
    }
    return 0;
    }
  6. 将代码转化为c++来看一下上述代码之间的差别。

    • 在转换C++代码时用__weak修饰变量,需要告知编译器使用ARC环境及版本号否则会报错,添加说明-fobjc-arc -fobjc-runtime=ios-8.0.0
    • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
     struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *__weak person;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _person, int flags=0) : person(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
    };
  7. 用__weak修饰的变量,在生成的__main_block_impl_0中也是使用__weak修饰捕获。

3. __main_block_copy\_0__main_block_dispose_0函数分析:

  1. 当block中捕获对象类型的变量时,我们发现block结构体__main_block_impl_0的描述结构体__main_block_desc_0中多了两个参数copy和dispose函数,查看源码:

  2. copy和dispose函数中传入的都是__main_block_impl_0结构体本身。

  3. copy本质就是__main_block_copy_0函数,__main_block_copy_0函数内部调用_Block_object_assign函数,_Block_object_assign中传入的是person对象的地址,person对象,以及8。

  4. dispose本质就是__main_block_dispose_0函数,__main_block_dispose_0函数内部调用_Block_object_dispose函数,_Block_object_dispose函数传入的参数是person对象,以及8。

4. _Block_object_assign函数调用时机及作用

  1. 当block进行copy操作的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部会调用_Block_object_assign函数。

  2. _Block_object_assign函数会自动根据__main_block_impl_0结构体内部的person是什么类型的指针,对person对象产生强引用或者弱引用。可以理解为_Block_object_assign函数内部会对person进行引用计数器的操作,如果__main_block_impl_0结构体内person指针是__strong类型,则为强引用,引用计数+1,如果__main_block_impl_0结构体内person指针是__weak类型,则为弱引用,引用计数不变。

5. _Block_object_dispose函数调用时机及作用

  1. 当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。

  2. _Block_object_dispose会对person对象做释放操作,类似于release,也就是断开对person对象的引用,而person究竟是否被释放还是取决于person对象自己的引用计数。

6. 总结

  1. 一旦block中捕获的变量为对象类型,block结构体中的__main_block_desc_0会出两个参数copy和dispose。因为访问的是个对象,block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作。比如说对对象进行retarn操作,因此一旦block捕获的变量是对象类型就会会自动生成copy和dispose来对内部引用的对象进行内存管理。

  2. 当block内部访问了对象类型的auto变量时,如果block是在栈上,block内部不会对person产生强引用。不论block结构体内部的变量是__strong修饰还是__weak修饰,都不会对变量产生强引用。

  3. 如果block被拷贝到堆上。copy函数会调用_Block_object_assign函数,根据auto变量的修饰符(__strong,__weak,unsafe_unretained)做出相应的操作,形成强引用或者弱引用

  4. 如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。

7. 拓展

  1. 下列代码person在何时销毁?在person 类中重写 delloc 方法

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    Person *person = [[Person alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"%@",person);
    });
    NSLog(@"touchBegin----------End");
    } // Log : 打印结果 : touchBegin----------End
    // Log :打印结果 : <Person: 0x6080000080d0>
    // Log :打印结果 : person 对象调用 delloc
    • 上文提到过ARC环境中,block作为GCD API的方法参数时会自动进行copy操作,因此block在堆空间,并且使用强引用访问person对象,因此block内部copy函数会对person进行强引用。当block执行完毕需要被销毁时,调用dispose函数释放对person对象的引用,person没有强指针指向时才会被销毁。
  2. 下列代码person在何时销毁 ?

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    Person *person = [[Person alloc] init]; __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"%@",weakPerson);
    });
    NSLog(@"touchBegin----------End");
    }
    // Log : 打印结果 : touchBegin----------End
    // Log :打印结果 : person 对象调用 delloc
    // Log :打印结果 : null
    // 结果是 person 先销毁才执行block 打印结果为 null
    • block中对weakPerson为__weak弱引用,因此block内部copy函数会对person同样进行弱引用,当大括号执行完毕时,person对象没有强指针引用就会被释放。因此block块执行的时候打印null。
  3. 通过示例代码进行总结。

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    Person *person = [[Person alloc] init]; __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"weakPerson ----- %@",weakPerson); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"person ----- %@",person);
    });
    });
    NSLog(@"touchBegin----------End");
    }
    // Log : 打印结果 : touchBegin----------End
    // Log :1s 后 打印结果 : weakPerson -> <Person: 0x608000006050>
    // Log :3s 后 打印结果 : person -> <Person: 0x608000006050>
    // Log :打印结果 : person 对象调用 delloc
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    Person *person = [[Person alloc] init]; __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"person ----- %@",person); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"weakP ----- %@",waekP);
    });
    });
    NSLog(@"touchBegin----------End");
    }
    // Log : 打印结果 : touchBegin----------End
    // Log :1s 后 打印结果 : person -> <Person: 0x608000006050>
    // Log :打印结果 : person 对象调用 delloc
    // Log :3s 后 打印结果 : weakPerson -> null

# iOS Block的本质(三)的更多相关文章

  1. iOS Block的本质(四)

    iOS Block的本质(四) 上一篇文章iOS Block的本质(三)中已经介绍过block变量的捕获,本文继续探寻block的本质. 1. block内修改变量的值 int main(int ar ...

  2. iOS Block的本质(二)

    iOS Block的本质(二) 1. 介绍引入block本质 通过上一篇文章Block的本质(一)已经基本对block的底层结构有了基本的认识,block的底层就是__main_block_impl_ ...

  3. iOS Block的本质(一)

    iOS Block的本质(一) 1.对block有一个基本的认识 block本质上也是一个oc对象,他内部也有一个isa指针.block是封装了函数调用以及函数调用环境的OC对象. 2.探寻block ...

  4. IOS Block的本质

    #import "HMViewController.h" #import "HMPerson.h" @interface HMViewController () ...

  5. iOS底层原理总结 - 探寻block的本质(一)

        面试题 block的原理是怎样的?本质是什么? __block的作用是什么?有什么使用注意点? block的属性修饰词为什么是copy?使用block有哪些使用注意? block在修改NSMu ...

  6. iOS block和代理的区别

      block和代理是iOS开发中实现回调的两种方式,大多数情况下是用哪个都可以,主要看个人喜好.本文主要是对两者做一下对比. 1.block简介   在 iOS中, block一共分三种.   (1 ...

  7. (译)IOS block编程指南 1 介绍

    Introduction(介绍) Block objects are a C-level syntactic and runtime feature. They are similar to stan ...

  8. iOS Block界面反向传值

    在上篇博客 <iOS Block简介> 中,侧重解析了 iOS Block的概念等,本文将侧重于它们在开发中的应用. Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C ...

  9. iOS拨打电话的三种方式

    iOS拨打电话的三种方式 1,这种方法,拨打完电话回不到原来的应用,会停留在通讯录里,而且是直接拨打,不弹出提示 1 2 var string = "tel:" + "1 ...

随机推荐

  1. Vue 变化检测问题

    受现代Javascript的限制,Vue不能检测到对象属性的添加和删除,因为Vue在初始化时将属性转为getter/setter,所以属性必须在data对象上才能让Vue转换它,Vue不允许在已经创建 ...

  2. 使用jdk获取网页

    参考:https://www.zhihu.com/question/30626103 由于例子比较老了,现在的百度抓不到完整的页面了 import java.io.BufferedReader; im ...

  3. Flutter实战视频-移动电商-42.详细页_UI主页面架构搭建

    42.详细页_UI主页面架构搭建 详细分成六大部分拆分开 body里面用FutureBuilder异步加载. FutureBuilder里面的furure属性这里用一个方法,必须返回的也是future ...

  4. Ubuntu18.04安装和配置Java JDK11并卸载自带OpenJDK

    卸载自带的OpenJDK方法: 在终端执行命令:sudo apt-get remove openjdk* Ubuntu18.04安装JDK11.0.2 前面步骤一样,可参考:https://www.c ...

  5. window.showModalDialog 在谷歌Uncaught TypeError: undefined is not a function

    if(navigator.userAgent.indexOf("Chrome") >0 ){var winOption = "height="+heigh ...

  6. 洛谷 - P1338 - 末日的传说 - 打表

    https://www.luogu.org/problemnew/show/P1338 先打表看了一下规律,居然看出来n的位置是阶梯往前的.而每个阶梯的头头,必有后半段降序. 再仔细看一下居然每次交换 ...

  7. 微信小程序取消button边框线

    先给button定义个class属性 <button class="an"> 按钮 </button> 然后再css上加上 .an::after { bor ...

  8. [Xcode 实际操作]八、网络与多线程-(1)使用Reachability类库检测网络的连接状态

    目录:[Swift]Xcode实际操作 本文将演示如何使用Reachability网络状态检测库,检测设备的网络连接状态. 需要下载一个开源的类库:[ashleymills/Reachability. ...

  9. 稳定UI运行结果-自动化测试失败重试和截图

    运行自动化测试的时候,有时会因为网络不稳定,测试环境或者第三方环境正在重启而造成用例运行结果不稳定,时而能跑过时而跑不过.这些难以重现的环境因素造成的用例失败会让测试人员很困扰,排查即耗费时间也没有太 ...

  10. Java反编译工具JD-GUI以及Eclipse的反编译插件

    什么是反编译 高级语言源程序经过编译变成可执行文件,反编译就是逆过程.但是通常不能把可执行文件变成高级语言源代码,只能转换成汇编程序. 反编译是一个复杂的过程,所以越是高级语言,就越难于反编译,但目前 ...