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. Docker 与 宿主机之间的文件cp

    Docker 与 宿主机之间的文件cp 第一种方法是官方比较推荐的,其实和第二种方法实现是一样的. 第一种方法例: 将主机/www/runoob目录拷贝到容器96f7f14e99ab的/www目录下. ...

  2. Swift协议中类继承协议的mutating问题

    之前实际开发的时候遇到的一个小问题,网上也没有找到相关说明.本来当时弄明白了想着记下来的,但是比较忙就耽搁了,趁今天休息记录一下. 首先,我们看一下下面这个两数之和的协议 protocol Test: ...

  3. opengl VAO ,VBO

    A Vertex Array Object (VAO) is an object which contains one or more Vertex Buffer Objects and is des ...

  4. 51nod1247(gcd)

    题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1247 题意:中文题诶- 思路:(a, b)可以直接到达(a+b ...

  5. [Xcode 实际操作]九、实用进阶-(2)遍历设备(输出系统)上的所有字体

    目录:[Swift]Xcode实际操作 在实际工作中,经常需要调整界面元素的字体种类. 本文将演示输出系统提供的所有字体,方便检索和使用. 在项目导航区,打开视图控制器的代码文件[ViewContro ...

  6. 利用多项式实现图像几何校正(Matlab实现)

    1.原理简述:     根据两幅图像中的一些已知对应点(控制点对),建立函数关系式,通过坐标变换,实现失真图像的几何校正. 设两幅图像坐标系统之间畸变关系能用解析式来描述: 根据上述的函数关系,可以依 ...

  7. python-selenium-robotframework安装问题

    背景 当前系统安装了两个不同版本的python,分别是python27和python36(如图1),如图 说明 系统安装的两个python版本,python2中的python.exe默认不做修改:py ...

  8. assembly x86(nasm)画三角形等图形的实现(升级版)

    https://www.cnblogs.com/lanclot-/p/10962702.html接上一篇 本来就有放弃的想法,可是有不愿退而求次, 然后大神室友写了一个集海伦公式计算三角形面积, 三点 ...

  9. react native的Navigator组件示例

    import React, {Component} from 'react';import {ScrollView, StyleSheet, Text, View, PixelRatio} from ...

  10. 51 Nod 1640 天气晴朗的魔法( Kruskall )

    #include <bits/stdc++.h> typedef long long LL; using namespace std; ; struct node{ LL u,v,w; n ...