iOS Block的本质(二)

1. 介绍引入block本质

  1. 通过上一篇文章Block的本质(一)已经基本对block的底层结构有了基本的认识,block的底层就是__main_block_impl_0
  2. 通过以下这张图展示底层各个结构体之间的关系。

2. block的变量捕获

  • 为了保证block内部能够正常访问外部的变量,block有一个变量捕获机制。

局部变量

  1. auto变量

    • Block的本质(一)我们已经了解过block对age变量的捕获。
    • auto自动变量,离开作用域就销毁,局部变量前面自动添加auto关键字。自动变量会捕获到block内部,也就是说block内部会专门新增加一个参数来存储变量的值。
    • auto只存在于局部变量中,访问方式为值传递,通过上述对age参数的解释我们也可以确定确实是值传递。
  2. static变量

    • static 修饰的变量为指针传递,同样会被block捕获。
  3. 分析aotu修饰的局部变量和static修饰的局部变量之间的差别

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    auto int a = 10;
    static int b = 10;
    void(^block)(void) = ^{
    NSLog(@"age is %d, height is %d", a, b);
    };
    a = 1;
    b = 2;
    block();
    }
    return 0;
    }
    // log : 信息--> age = 10, height = 2
    // block中a的值没有被改变而b的值随外部变化而变化。
  4. 重新生成c++代码看一下内部结构中两个参数的区别。

    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a; // a 为值
    int *b; // b 为指针
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcremainackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    int *b = __cself->b; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, a, (*b));
    } static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  5. 从上述源码中可以看出,a,b两个变量都有捕获到block内部。但是a传入的是值,而b传入的则是地址。

  6. 为什么两种变量会有这种差异呢,因为自动变量可能会销毁,block在执行的时候有可能自动变量已经被销毁了,那么此时如果再去访问被销毁的地址肯定会发生坏内存访问,因此对于自动变量一定是值传递而不可能是指针传递了。而静态变量不会被销毁,所以完全可以传递地址。而因为传递的是值得地址,所以在block调用之前修改地址中保存的值,block中的地址是不会变得。所以值会随之改变。

  7. 全局变量

    • 我们同样以代码的方式看一下block是否捕获全局变量
    int age_ = 10;
    static int height_ = 10;
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    void(^block)(void) = ^{
    NSLog(@"age is %d, height is %d", age_, height_);
    };
    age_ = 1;
    height_ = 2;
    block();
    }
    return 0;
    }
    // log 信息--> age = 1, height = 2
  8. 同样生成c++代码查看全局变量调用方式

    int age_ = 10;
    static int height_ = 10;
    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0){
    impl.isa = &_NSConcremainackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, age_, height_);
    } static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  9. 通过上述代码可以发现,__main_block_imp_0并没有添加任何变量,因此block不需要捕获全局变量,因为全局变量无论在哪里都可以访问。

    • 局部变量因为跨函数访问所以需要捕获,全局变量在哪里都可以访问 ,所以不用捕获。
  10. block的变量总结

    • 总结:局部变量都会被block捕获,自动变量是值捕获,静态变量为地址捕获。全局变量则不会被block捕获

3. 变量捕获拓展

  1. 以下Persion类代码中block变量分析

    @interface Person : NSObject
    @property (copy, nonatomic) NSString *name; - (void)test; - (instancetype)initWithName:(NSString *)name;
    @end #import "Person.h"
    @implementation Person
    int age_ = 10;
    - (void)test
    {
    void (^block)(void) = ^{
    NSLog(@"-------%d", [self name]);
    };
    block();
    } - (instancetype)initWithName:(NSString *)name
    {
    if (self = [super init]) {
    self.name = name;
    }
    return self;
    }
    @end
  2. 同样转化为c++代码查看其内部结构


    int age_ = 10;
    struct __Person__test_block_impl_0 {
    struct __block_impl impl;
    struct __Person__test_block_desc_0* Desc;
    Person *self;
    __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
    };
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
    Person *self = __cself->self; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_Person_1027e6_mi_0, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
    }
    static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __Person__test_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
    void (*dispose)(struct __Person__test_block_impl_0*);
    } __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0}; static void _I_Person_test(Person * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    } static instancetype _I_Person_initWithName_(Person * self, SEL _cmd, NSString *name) {
    if (self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"))) {
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
    }
    return self;
    } static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
    extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }
    // @end struct _prop_t {
    const char *name;
    const char *attributes;
    };
  3. 可以发现,self同样被block捕获,接着我们找到test方法可以发现,test方法默认传递了两个参数self和_cmd。

  4. 同理得,类方法也同样默认传递了类对象self和方法选择器_cmd。

  5. 不论对象方法还是类方法都会默认将self作为参数传递给方法内部,既然是作为参数传入,那么self肯定是局部变量。上面讲到局部变量肯定会被block捕获。

  6. 在block内部使用name成员变量或者调用实例的属性

    - (void)test
    {
    void(^block)(void) = ^{
    NSLog(@"%@",self.name);
    NSLog(@"%@",_name);
    };
    block();
    }

  7. 得到结论:在block中使用的是实例对象的属性,block中捕获的仍然是实例对象,并通过实例对象通过不同的方式去获取使用到的属性。

4. block的类型

1.类型分析

  1. 通过源码分析得到,block中的isa指针指向的是_NSConcreteStackBlock类对象地址。那么block是否就是_NSConcreteStackBlock类型的呢?

  2. 我们通过代码用class方法或者isa指针查看具体类型。

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    // __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
    void (^block)(void) = ^{
    NSLog(@"Hello");
    }; NSLog(@"%@", [block class]);
    NSLog(@"%@", [[block class] superclass]);
    NSLog(@"%@", [[[block class] superclass] superclass]);
    NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
    }
    return 0;
    }
    // log 打印结果 __NSGlobalBlock__
    // log 打印结果 __NSGlobalBlock
    // log 打印结果 NSBlock
    // log 打印结果 NSObjcet
  3. 从上述打印内容可以看出block最终都是继承自NSBlock类型,而NSBlock继承于NSObjcet。那么block其中的isa指针其实是来自NSObject中的。这也更加印证了block的本质其实就是OC对象。

2.类型分类

  1. block有3中类型

    • __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
    • __NSStackBlock__ ( _NSConcreteStackBlock )
    • __NSMallocBlock__ ( _NSConcreteMallocBlock )
  2. 通过代码查看一下block在什么情况下其类型会各不相同

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    // 1. 内部没有调用外部变量的block
    void (^block1)(void) = ^{
    };
    // 2. 内部调用外部变量的block
    int a = 10;
    void (^block2)(void) = ^{
    NSLog(@"log :%d",a);
    };
    // 3. 直接调用的block的class
    NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
    NSLog(@"%d",a);
    } class]);
    }
    return 0;
    }
    // 最后一行 Log :打印结果 __NSGlobalBlock__, __NSStackBlock__ ,__NSMallocBlock__
  3. 上述代码转化为c++代码查看源码时却发现block的类型与打印出来的类型不一样,c++源码中三个block的isa指针全部都指向_NSConcreteStackBlock类型地址。

  4. 我们可以推测runtime运行时过程中也许对类型进行了转变。最终类型当然以runtime运行时类型也就是我们打印出的类型为准。

5. block在内存中的存储

  1. 通过下面一张图看一下不同block的存放区域

  2. 上图中可以发现,根据block的类型不同,block存放在不同的区域中。

    数据段中的__NSGlobalBlock__直到程序结束才会被回收,不过我们很少使用到__NSGlobalBlock__类型的block,因为这样使用block并没有什么意义。

  3. __NSStackBlock__类型的block存放在栈中,我们知道栈中的内存由系统自动分配和释放,作用域执行完毕之后就会被立即释放,而在相同的作用域中定义block并且调用block似乎也多此一举。

  4. __NSMallocBlock__是在平时编码过程中最常使用到的。存放在堆中需要我们自己进行内存管理。

  5. block是如何定义其类型

  6. 接着我们使用代码验证上述问题,首先关闭ARC回到MRC环境下,因为ARC会帮助我们做很多事情,可能会影响我们的观察。

    // MRC环境!!!
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    // Global:没有访问auto变量:__NSGlobalBlock__
    void (^block1)(void) = ^{
    NSLog(@"block1---------");
    };
    // Stack:访问了auto变量: __NSStackBlock__
    int a = 10;
    void (^block2)(void) = ^{
    NSLog(@"block2---------%d", a);
    };
    NSLog(@"%@ %@", [block1 class], [block2 class]);
    // __NSStackBlock__调用copy : __NSMallocBlock__
    NSLog(@"%@", [[block2 copy] class]);
    }
    return 0;
    }
    // Log 打印信息 --> __NSGlobalBlock__ ,__NSStackBlock__ ,__NSMallocBlock__
  7. 通过打印的内容可以验证上图中所示的正确性。

    • 没有访问auto变量的block是__NSGlobalBlock__类型的,存放在数据段中。
    • 访问了auto变量的block是__NSStackBlock__类型的,存放在栈中。
    • __NSStackBlock__类型的block调用copy成为__NSMallocBlock__类型并被复制存放在堆中。
  8. 上面提到过__NSGlobalBlock__类型的我们很少使用到,因为如果不需要访问外界的变量,直接通过函数实现就可以了,不需要使用block。

  9. 但是__NSStackBlock__访问了aotu变量,并且是存放在栈中的,上面提到过,栈中的代码在作用域结束之后内存就会被销毁,那么我们很有可能block内存销毁之后才去调用他,那样就会发生问题,通过下面代码可以证实这个问题。MRC 环境下的。

    void (^block)(void);
    void test()
    {
    // __NSStackBlock__
    int a = 10;
    block = ^{
    NSLog(@"block---------%d", a);
    };
    }
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    test();
    block();
    }
    return 0;
    }
    // Log 打印信息 :MRC 环境下 : block---------272632424
    // Log 打印信息 :ARC 环境下 : block---------10
    • 如果执行copy操作打印结果为10
    void (^block)(void);
    void test()
    {
    // __NSStackBlock__ 调用copy 转化为__NSMallocBlock__
    int age = 10;
    block = [^{
    NSLog(@"block---------%d", age);
    } copy];
    [block release];
    } int main(int argc, const char * argv[]) {
    @autoreleasepool {
    // insert code here...
    test(); block();
    // Log 打印信息 : block---------10
    }
    return 0;
    }
  10. 可以发现a的值变为了不可控的一个数字。为什么会发生这种情况呢?因为上述代码中创建的block是__NSStackBlock__类型的,因此block是存储在栈中的,那么当test函数执行完毕之后,栈内存中block所占用的内存已经被系统回收,因此就有可能出现乱得数据。查看其c++代码可以更清楚的理解。

  11. 为了避免这种情况发生,可以通过copy将__NSStackBlock__类型的block转化为__NSMallocBlock__类型的block,将block存储在堆中,以下是修改后的代码。

    void (^block)(void);
    void test()
    {
    // __NSStackBlock__ 调用copy 转化为__NSMallocBlock__
    int age = 10;
    block = [^{
    NSLog(@"block---------%d", age);
    } copy];
    [block release];
    }
    // Log 打印信息 : block---------10
  12. 那么其他类型的block调用copy会改变block类型吗?下面表格已经展示的很清晰了。

  13. 所以在平时开发过程中MRC环境下经常需要使用copy来保存block,将栈上的block拷贝到堆中,即使栈上的block被销毁,堆上的block也不会被销毁,需要我们自己调用release操作来销毁。而在ARC环境下回系统会自动copy,是block不会被销毁。

6. ARC环境下的block

  • 在ARC环境下,编译器会根据情况自动将栈上的block进行一次copy操作,将block复制到堆上。

  • 会自动将block进行一次copy操作的情况。

  1. block作为函数返回值时

    typedef void (^Block)(void);
    Block myblock()
    {
    int a = 10;
    // 上文提到过,block中访问了auto变量,此时block类型应为__NSStackBlock__
    Block block = ^{
    NSLog(@"---------%d", a);
    };
    return block;
    }
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Block block = myblock();
    block();
    // 打印block类型为 __NSMallocBlock__
    NSLog(@"%@",[block class]);
    }
    return 0;
    }
    Log 打印信息 :---------10
    Log 打印信息 :__NSMallocBlock__
    • 上文提到过,如果在block中访问了auto变量时,block的类型为__NSStackBlock__,上面打印内容发现blcok为__NSMallocBlock__类型的,并且可以正常打印出a的值,说明block内存并没有被销毁。
    • 上面提到过,block进行copy操作会转化为__NSMallocBlock__类型,来讲block复制到堆中,那么说明RAC在 block作为函数返回值时会自动帮助我们对block进行copy操作,以保存block,并在适当的地方进行release操作。
  2. 将block赋值给__strong指针时

    • block被强指针引用时,ARC也会自动对block进行一次copy操作。
     int main(int argc, const char * argv[]) {
    @autoreleasepool {
    // block内没有访问auto变量
    Block block = ^{
    NSLog(@"block---------");
    };
    NSLog(@"%@",[block class]);
    int a = 10;
    // block内访问了auto变量,但没有赋值给__strong指针
    NSLog(@"%@",[^{
    NSLog(@"block1---------%d", a);
    } class]);
    // block赋值给__strong指针
    Block block2 = ^{
    NSLog(@"block2---------%d", a);
    };
    NSLog(@"%@",[block1 class]);
    }
    return 0;
    }
    Log 打印信息 :__NSGlobalBlock__
    Log 打印信息 :__NSStackBlock__
    Log 打印信息 :__NSMallocBlock__
  3. block作为Cocoa API中方法名含有usingBlock的方法参数时

    • 例如:遍历数组的block方法,将block作为参数的时候。
    NSArray *array = @[];
    [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }];
  4. block作为GCD API的方法参数时

    • 例如:GDC的一次性函数或延迟执行的函数,执行完block操作之后系统才会对block进行release操作。
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });

7. block声明写法

  • 通过上面对MRC及ARC环境下block的不同类型的分析,总结出不同环境下block属性建议写法。
  1. MRC下block属性的建议写法

    @property (copy, nonatomic) void (^block)(void);

  2. ARC下block属性的建议写法

    @property (strong, nonatomic) void (^block)(void);

    @property (copy, nonatomic) void (^block)(void);

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

  1. # iOS Block的本质(三)

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

  2. iOS Block的本质(四)

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

  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的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情 ...

  7. iOS Block界面反向传值

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

  8. iOS使用Zbar扫描二维码

    iOS使用Zbar扫描二维码 标签(空格分隔):二维码扫描 iOS Zbar64位 正文: 首先下载一个支持64位系统的ZbarSDK的包,保存在了我的云盘里,地址:ZbarSDK 把文件拖到工程里面 ...

  9. iOS block从零开始

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

随机推荐

  1. SQL SERVER2008 打开脚本总是报“未能完成操作,存储空间不足”

    使用用SQLCMD命令行. 1.快捷键:win+R 2.输入cmd​,确定 3.输入命令:sqlcmd -S <数据库服务器名称> -i C:\<脚本文件路径>.sql 如图所 ...

  2. phpstudy打不开localhost(Apache)

    1.先打开httpd.conf文件,打开httpd-vhosts.conf "Include conf/extra/httpd-vhosts.conf"的注释,启动服务时优先加载此 ...

  3. Eclipse用Runnable JAR file方式打jar包,并用该jar包进行二次开发

    目录: 1.eclipse创建Java项目(带jar包的) 2. eclipse用Export的Runnable JAR file方式打jar包(带jar包的) 打jar包 1)class2json1 ...

  4. Flutter实战视频-移动电商-51.购物车_Provide中添加商品

    51.购物车_Provide中添加商品 新加provide的cart.dart页面 引入三个文件.开始写provide类.provide需要用with 进行混入 从prefs里面获取到数据,判断有没有 ...

  5. C#中的explicit和implicit了解一下吧

    今天在研究公司项目框架的时候看到了下面的用法,public static implicit operator JsonData(int data);.貌似很久没用过这种隐式转换的写法了,因此重新温习一 ...

  6. unity3d项目文件目录发布后,对应的ios/android应用目录

    Unity3d的Resource.AssetBundle与手游动态更新的报告,在这里分享一下,希望能够对各位用Unity的朋友有些许帮助.目录:1.Unity的资源数据加载2.Resource.Str ...

  7. P4091 [HEOI2016/TJOI2016]求和(第二类斯特林数+NTT)

    传送门 首先,因为在\(j>i\)的时候有\(S(i,j)=0\),所以原式可以写成\[Ans=\sum_{i=0}^n\sum_{j=0}^nS(i,j)\times 2^j\times j! ...

  8. Phpstorm建立连接Wampserver的数据库

    phpstorm是一款php集成开发环境软件,集成了很多功能,不但有强大的代码编辑及调试功能,还能连接数据库.本文写的就是如何用phpstorm来建立访问wampserver数据库,查询输出数据,方便 ...

  9. 【源码系列】Eureka源码分析

    对于服务注册中心.服务提供者.服务消费者这个三个主要元素来说,服务提供者和服务消费者(即Eureka客户端)在整个运行机制中是大部分通信行为的主动发起者(服务注册.续约.下线等),而注册中心主要是处理 ...

  10. Mybatis中分页存在的坑1

    站在巨人的肩膀上 https://www.cnblogs.com/esileme/p/7565184.html 环境:Spring 4.2.1 Mybatis 3.2.8 pagehelper 5.1 ...