iOS Block的本质(四)

1. block内修改变量的值

int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
Block block = ^ {
// age = 20; // 无法修改
NSLog(@"%d",age);
};
block();
}
return 0;
}
  1. 默认情况下block不能修改外部的局部变量。通过之前对源码的分析可以知道。
  2. age是在main函数内部声明的,说明age的内存存在于main函数的栈空间内部,但是block内部的代码在__main_block_func_0函数内部。__main_block_func_0函数内部无法访问age变量的内存空间,两个函数的栈空间不一样,__main_block_func_0内部拿到的age是block结构体内部的age,因此无法在__main_block_func_0函数内部去修改main函数内部的变量。

方式一:age使用static修饰。

  • 前文提到过static修饰的age变量传递到block内部的是指针,在__main_block_func_0函数内部就可以拿到age变量的内存地址,因此就可以在block内部修改age的值。

方式二:__block

  1. __block用于解决block内部不能修改auto变量值的问题,__block不能修饰静态变量(static) 和全局变量。

    • __block int age = 10;
  2. 编译器会将__block修饰的变量包装成一个对象,查看其底层c++源码。

  3. 上述源码中可以发现,首先被__block修饰的age变量声明变为名为age的__Block_byref_age_0结构体,也就是说加上__block修饰的话捕获到的block内的变量为__Block_byref_age_0类型的结构体。

  4. 通过下图查看__Block_byref_age_0结构体内存储哪些元素。

    • __isa指针 :__Block_byref_age_0中也有isa指针也就是说__Block_byref_age_0本质也一个对象。
    • __forwarding:__forwarding是__Block_byref_age_0结构体类型的,并且__forwarding存储的值为(__Block_byref_age_0 *)&age,即结构体自己的内存地址。
    • __flags :0
    • __size :sizeof(__Block_byref_age_0),即__Block_byref_age_0所占用的内存空间。
    • age :真正存储变量的地方,这里存储局部变量10。
    • 接着将__Block_byref_age_0结构体age存入__main_block_impl_0结构体中,并赋值给__Block_byref_age_0 *age;
    • 之后调用block,首先取出__main_block_impl_0中的age,通过age结构体拿到__forwarding指针,上面提到过__forwarding中保存的就是__Block_byref_age_0结构体本身,这里也就是age(__Block_byref_age_0),在通过__forwarding拿到结构体中的age(10)变量并修改其值。
    • 后续NSLog中使用age时也通过同样的方式获取age的值。
  5. 为什么要通过__forwarding获取age变量的值?

  • __forwarding是指向自己的指针。这样的做法是为了方便内存管理,之后内存管理章节会详细解释。
  • 到此为止,__block为什么能修改变量的值已经很清晰了。__block将变量包装成对象,然后在把age封装在结构体里面,block内部存储的变量为结构体指针,也就可以通过指针找到内存地址进而修改变量的值。

2. __block修饰对象类型

int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
NSLog(@"%@",person);
Block block = ^{
person = [[Person alloc] init];
NSLog(@"%@",person);
};
block();
}
return 0;
}
  1. 生成c++源码分析

  2. 通过源码查看,将对象包装在一个新的结构体中。结构体内部会有一个person对象,不一样的地方是结构体内部添加了内存管理的两个函数__Block_byref_id_object_copy__Block_byref_id_object_dispose

  3. 上文提到当block中捕获对象类型的变量时,block中的__main_block_desc_0结构体内部会自动添加copy和dispose函数对捕获的变量进行内存管理。

  4. 那么同样的当block内部捕获__block修饰的对象类型的变量时,__Block_byref_person_0结构体内部也会自动添加__Block_byref_id_object_copy__Block_byref_id_object_dispose对被__block包装成结构体的对象进行内存管理。

  5. 当block内存在栈上时,并不会对__block变量产生内存管理。当blcok被copy到堆上时会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_\Block_object_assign函数会对__block变量形成强引用(相当于retain)

    • 首先通过下图看一下block复制到堆上时内存变化
  6. 当block被copy到堆上时,block内部引用的__block变量也会被复制到堆上,并且持有变量,如果block复制到堆上的同时,__block变量已经存在堆上了,则不会复制。

  7. 当block从堆中移除的话,就会调用dispose函数,也就是__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数,会自动释放引用的__block变量。

  8. block内部决定什么时候将变量复制到堆中,什么时候对变量做引用计数的操作。

  9. __block修饰的变量在block结构体中一直都是强引用,而其他类型的是由传入的对象指针类型决定。

  10. 通过下面代码深入的观察一下。

    typedef void (^Block)(void);
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    int number = 20;
    __block int age = 10; NSObject *object = [[NSObject alloc] init];
    __weak NSObject *weakObj = object; Person *p = [[Person alloc] init];
    __block Person *person = p;
    __block __weak Person *weakPerson = p; Block block = ^ {
    NSLog(@"%d",number); // 局部变量
    NSLog(@"%d",age); // __block修饰的局部变量
    NSLog(@"%p",object); // 对象类型的局部变量
    NSLog(@"%p",weakObj); // __weak修饰的对象类型的局部变量
    NSLog(@"%p",person); // __block修饰的对象类型的局部变量
    NSLog(@"%p",weakPerson); // __block,__weak修饰的对象类型的局部变量
    };
    block();
    }
    return 0;
    }
  11. 将上述代码转化为c++代码查看不同变量之间的区别

    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc; int number;
    NSObject *__strong object;
    NSObject *__weak weakObj;
    __Block_byref_age_0 *age; // by ref
    __Block_byref_person_1 *person; // by ref
    __Block_byref_weakPerson_2 *weakPerson; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, NSObject *__strong _object, NSObject *__weak _weakObj, __Block_byref_age_0 *_age, __Block_byref_person_1 *_person, __Block_byref_weakPerson_2 *_weakPerson, int flags=0) : number(_number), object(_object), weakObj(_weakObj), age(_age->__forwarding), person(_person->__forwarding), weakPerson(_weakPerson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
    };
  12. 上述__main_block_impl_0结构体中看出,没有使用__block修饰的变量(object 和 weakObj)则根据他们本身被block捕获的指针类型对他们进行强引用或弱引用,而一旦使用__block修饰的变量,__main_block_impl_0结构体内一律使用强指针引用生成的结构体。

  13. 接着我们来看__block修饰的变量生成的结构体有什么不同。

    struct __Block_byref_age_0 {
    void *__isa;
    __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
    }; struct __Block_byref_person_1 {
    void *__isa;
    __Block_byref_person_1 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    Person *__strong person;
    }; struct __Block_byref_weakPerson_2 {
    void *__isa;
    __Block_byref_weakPerson_2 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    Person *__weak weakPerson;
    };
  14. 如上面分析的那样,__block修饰对象类型的变量生成的结构体内部多了__Block_byref_id_object_copy和__Block_byref_id_object_dispose两个函数,用来对对象类型的变量进行内存管理的操作。而结构体对对象的引用类型,则取决于block捕获的对象类型的变量。weakPerson是弱指针,所以__Block_byref_weakPerson_2对weakPerson就是弱引用,person是强指针,所以__Block_byref_person_1对person就是强引用。

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
  15. __main_block_copy_0函数中会根据变量是强弱指针及有没有被__block修饰做出不同的处理,强指针在block内部产生强引用,弱指针在block内部产生弱引用。被__block修饰的变量最后的参数传入的是8,没有被__block修饰的变量最后的参数传入的是3。

  16. 当block从堆中移除时通过dispose函数来释放他们。

    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/); }

3. __forwarding指针

  1. 上面提到过__forwarding指针指向的是结构体自己。当使用变量的时候,通过结构体找到__forwarding指针,在通过__forwarding指针找到相应的变量。这样设计的目的是为了方便内存管理。通过上面对__block变量的内存管理分析我们知道,block被复制到堆上时,会将block中引用的变量也复制到堆中。

  2. 我们重回到源码中。当在block中修改__block修饰的变量时。

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_age_0 *age = __cself->age; // bound by ref
    (age->__forwarding->age) = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_main_b05610_mi_0,(age->__forwarding->age));
    }
  3. 通过源码可以知道,当修改__block修饰的变量时,是根据变量生成的结构体这里是__Block_byref_age_0找到其中__forwarding指针,__forwarding指针指向的是结构体自己因此可以找到age变量进行修改。

  4. 当block在栈中时,__Block_byref_age_0结构体内的__forwarding指针指向结构体自己。

  5. 而当block被复制到堆中时,栈中的__Block_byref_age_0结构体也会被复制到堆中一份,而此时栈中的__Block_byref_age_0结构体中的__forwarding指针指向的就是堆中的__Block_byref_age_0结构体,堆中__Block_byref_age_0结构体内的__forwarding指针依然指向自己。

  6. 此时当对age进行修改时

    // 栈中的age
    __Block_byref_age_0 *age = __cself->age; // bound by ref
    // age->__forwarding获取堆中的age结构体
    // age->__forwarding->age 修改堆中age结构体的age变量
    (age->__forwarding->age) = 20;
  7. 通过__forwarding指针巧妙的将修改的变量赋值在堆中的__Block_byref_age_0中。

  8. 我们通过一张图展示__forwarding指针的作用

  9. 因此block内部拿到的变量实际就是在堆上的。当block进行copy被复制到堆上时,_Block_object_assign函数内做的这一系列操作。

4. 拓展

  1. 以下代码是否可以正确执行

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    NSMutableArray *array = [NSMutableArray array];
    Block block = ^{
    [array addObject: @"5"];
    [array addObject: @"5"];
    NSLog(@"%@",array);
    };
    block();
    }
    return 0;
    }
    • 可以正确执行,因为在block块中仅仅是使用了array的内存地址,往内存地址中添加内容,并没有修改arry的内存地址,因此array不需要使用__block修饰也可以正确编译。
    • 因此当仅仅是使用局部变量的内存地址,而不是修改的时候,尽量不要添加__block,通过上述分析我们知道一旦添加了__block修饰符,系统会自动创建相应的结构体,占用不必要的内存空间。
  2. 上面提到过__block修饰的age变量在编译时会被封装为结构体,那么当在外部使用age变量的时候,使用的是__Block_byref_age_0结构体呢?还是__Block_byref_age_0结构体内的age变量呢?

    • 为了验证上述问题,同样使用自定义结构体的方式来查看其内部结构
    typedef void (^Block)(void);
    
    struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    }; struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
    }; struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
    };
    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    struct __Block_byref_age_0 *age; // by ref
    }; int main(int argc, const char * argv[]) {
    @autoreleasepool {
    __block int age = 10;
    Block block = ^{
    age = 20;
    NSLog(@"age is %d",age);
    };
    block();
    struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
    NSLog(@"%p",&age);
    }
    return 0;
    }
    1. 打印断点查看结构体内部结构

    2. 通过查看blockImpl结构体其中的内容,找到age结构体,其中重点观察两个元素:

    3. __forwarding其中存储的地址确实是age结构体变量自己的地址

    4. age中存储这修改后的变量20。

    5. 上面也提到过,在block中使用或修改age的时候都是通过结构体__Block_byref_age_0找到__forwarding在找到变量age的。

    6. 另外apple为了隐藏__Block_byref_age_0结构体的实现,打印age变量的地址发现其实是__Block_byref_age_0结构体内age变量的地址。

    7. 通过上图的计算可以发现打印age的地址同__Block_byref_age_0结构体内age值的地址相同。也就是说外面使用的age,代表的就是结构体内的age值。所以直接拿来用的age就是之前声明的int age。

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

  1. iOS Block的本质(一)

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

  2. # iOS Block的本质(三)

    iOS Block的本质(三) 上一篇文章iOS Block的本质(二)中已经介绍过block变量的捕获,本文继续探寻block的本质. 1. block对对象变量的捕获,ARC 环境 block一般 ...

  3. iOS Block的本质(二)

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

  4. IOS Block的本质

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

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

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

  6. iOS block从零开始

    iOS block从零开始 在iOS4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调. block的结构 先来一段简单的代码看看: void ...

  7. iOS中常用的四种数据持久化方法简介

    iOS中常用的四种数据持久化方法简介 iOS中的数据持久化方式,基本上有以下四种:属性列表.对象归档.SQLite3和Core Data 1.属性列表涉及到的主要类:NSUserDefaults,一般 ...

  8. iOS block 机制

    本文要将block的以下机制,并配合具体代码详细描述: block 与 外部变量 block 的存储域:栈块.堆块.全局块 定义 块与函数类似,只不过是直接定义在另一个函数里,和定义它的那个函数共享同 ...

  9. ipa包如何打包?ios打包ipa的四种方法分享

      今天带来的内容是ios打包ipa的四种方法.总结一下,目前.app包转为.ipa包的方法有以下几种,下面一起来看看吧!    1.Apple推荐的方式,即实用xcode的archive功能 Xco ...

随机推荐

  1. spellchecker inspection helps locate typeos and misspelling in your code, comments and literals, and fix them in one click

    项目layout文件中出现 spellchecker inspection helps locate typos and misspelling in your code, comments and ...

  2. saltstack master minion安装配置简单使用

    首先先了解下saltstack是什么,为什么使用它 它与Ansible.Puppet都属于集中管理工具,由于现在企业规模的不断庞大及业务的增长,所需要管理的服务器数量增多,而且大部分机器都属于同类业务 ...

  3. Ubuntu12.04下安装VirtualBox

    目录: 安装虚拟机VirtualBox 虚拟机VirtualBox安装win7全过程 虚拟机共享文件夹.U盘 一.安装虚拟机VirtualBox VirtualBox下载地址:https://www. ...

  4. Flex AIR操作文件系统

    1.各种文件操作http://blog.csdn.net/zdingxin/article/details/6635376 2.file.browseForDirectory("请选择一个目 ...

  5. D3.js(v3)+react 制作 一个带坐标轴与比例尺的折线图

    本章使用路径生成器来绘制一个折线图.以中国和日本的GDP数据为例:   //数据 var dataList = [ { coountry : "china", gdp : [ [2 ...

  6. CodeForces 363D 【二分+贪心】

    思路: 一开始是没有办法贪心的,但是答案是可以二分的,因为窝能买k辆车的话,窝就一定能买k-1辆车:而且我最好就是拿手上钱较多的那些人去买价格便宜的车,这样肯定是能买到最多的车,而且花的少,因为对于要 ...

  7. 小程序隐藏或自定义 scroll-view滚动条

    css 隐藏滚动条 ::-webkit-scrollbar { width:; height:; color: transparent; } 自定义滚动条样式 ::-webkit-scrollbar ...

  8. Tessellation

    Tessellation细分曲面技术是AMD(ATI)常年研发多代的技术,经过多年发展最终被采纳成为DX11的一项关键技术,因此历来都是宣传重点.和光线追踪不同,现在的光栅化图形渲染技术的核心是绘制大 ...

  9. [Xcode 实际操作]七、文件与数据-(21)ARKit增强现实框架的使用

    目录:[Swift]Xcode实际操作 本文将演示ARKit增强现实框架的使用. 创建一个新的项目:[Create a new Xcode project] ->在打开的模板选择中,选择增强现实 ...

  10. wow.js零基础使用介绍

    wow.js依赖于animate.css,首先需要在 head内引入animate.css或者animate.min.css wow.js或者wow.min.js,然后js文件里再写一行代码. 然后在 ...