• Block详解一(底层分析)


    Block专辑:

    Block讲解一

    MRC-block与ARC-block

      

    本篇博客不再讲述Block的基本定义使用,最近而是看了很多的block博客讲述的太乱太杂,所以抽出时间整理下block的相关底层知识,在讲述之前,提出几个问题,如果都可以回答出来以及知道原理,大神绕过,反之,希望本篇博客对大家面试或者block不熟悉者有所帮助,以后会不断更新博客,欢迎关注和指正!!!

    1. blcok的原理是怎样的?本质又是什么?
    2. __block的作用是什么?有什么使用注意点?
    3. block的属性修饰词为什么是copy修饰?使用block有哪些使用注意事项?
    4. block在修改NSMutableArray,需要不需要添加__block?

    一、 block本质

    • blcok本质是OC对象,它内部也有个isa指针,在OC中有isa指针的对象,可以认定为OC对象
    • block对象是封装了函数调用以及函数调用环境的OC对象

    通过下面例子看下block结构

    文件结构

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            void (^block)(int, int) = ^(int a, int b){
                NSLog(@"This is a block  --%d",age);
                NSLog(@"This is a block");
                NSLog(@"This is a block");
            };
            block(10, 20);
        }
        return 0;
    }

    通过clang编译器将OC代码编译成C语言代码,并生成了在后缀名为.cpp的C++文件中,clang命令为

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

    然后查看编译出来的c++ main.cpp文件,和main.m同一个地方,将它移入到项目中,并在Build Phases->Compile Sources中删除main.cpp,然后就可以编译成功

     这次开始查看main.cpp的,对比两个文件,找出main.cpp的对应的main函数

     看到上面main.cpp左边的调用block,10,20前面有很多的强制类型转换,最后可以是funcPtr (block,10,20)在.cpp中海油一个__main_block_imp_0地址,查看其地址内容

    二、block变量捕获机制

     举例1: block变量捕获-auto变量

    经常书写int age = 10,前面都是有默认关键字auto的(也可以不书写,经常这样的),下面的结果是什么?

    int age = 10 等价于 auto int age = 10 (auto自动变量,离开作用域就会销毁)

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            void (^block)(void) = ^{
                NSLog(@"age is %d", age);
            };
            age = 20;
            block();
        }
        return 0;
    }

    猜一下运行结果

     再次运行查看编译出来的main.cpp,重复上面的步骤,对比main.cpp

     查看__main_block_impl_0的结构,传入的参数age = 10,block内部新增了一个变量用于存储age

     block的内部的age = 10 ,并不会改变,所以打印结果为10(当创建block的内容,age = 10已经存在了block中,并不会随外部改变而改变)

    举例2:block变量捕获-static变量

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            auto int age = 10;
            static int height = 10;
            void (^block)(void) = ^{
                NSLog(@"age is %d, height is %d", age, height);
            };
            age = 20;
            height = 20;
            block();
        }
        return 0;
    }

    结果为age = 10,height = 20

    将其编译为cpp,对比下 

     查看__main_block_impl_0的代码结构

    发现block捕获到了age和height,所以block会捕获到局部变量,而静态局部变量block存放的是地址,所以未来修改height的值时,取出的是所指向的最新的height值

    举例3: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 = 20;
            height = 20;
            block();
        }
        return 0;
    }

    查看.cpp文件有没有捕获到全局变量?--直接访问

     对于上面的block变量捕捉机制,总结如下:

     三、block的类型

    block的底层结构如下

    block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

     到底什么类型的block属于__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__?

     先不铺垫那么多,直接给出结论:

     下面来一一验证结论

     就是像上面提问那样,按总结的访问了auto变量应该是NSStackBlock,怎么成为了NSMallocBlock了呢?

    这是因为编译器默认在ARC环境下,应该切换到MRC环境下,看一下真正的block类型,至于ARC的,下篇讲述!!!

    将编译器改为去除ARC,在Build Settings -> automatic Reference Counting 中将ARC改为MRC

     改成MRC后再次允许查看结果

     那什么时候是NSMallocBlock呢?

    NSStackBlock调用copy变为NSMallocBlock,但是NSGlobalBlock调用了copy依然是NSGlobalBlock,NSMallocBlock调用了copy方法引用计数会+1

     那么在ARC环境下什么时候block会调用copy呢?(从栈空间->堆空间)

    在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如有以下情况:

    • block作为函数返回值时
    • block赋值给__strong指针时
    • block作为Cocoa API中方法名有usingBlock的方法参数时 如:[array enumeratorObjectsUsingBlock]
    • block作为GCD API的方法参数时

    四、对象类型的auto变量

    上面讲述auto变量是Int等基本类型,现在改成对象类型,如Person类对象[ARC环境下面]

     1 typedef void(^ZXYBlock)(void);
     2 int main(int argc, const char * argv[]) {
     3     @autoreleasepool {
     4         ZXYBlock block ;
     5         {
     6             Person *person = [[Person alloc]init];
     7             person.age = 10;
     8             block = ^{
     9                 NSLog(@"---------%d", person.age);
    10             };
    11         }
    12         NSLog(@"block执行完");
    13     }
    14     return 0;
    15 }
    16 
    17 @interface Person : NSObject
    18 @property (nonatomic, assign) int age;
    19 @end
    20 
    21 @implementation Person
    22 -(void)dealloc {
    23     NSLog(@"person对象已释放");
    24 }
    25 @end

    将上面的代码打breakPoint断点在12行处,查看Person对象是否在打括号{}内释放

    发现在打印之前并没有释放person对象,猜想block引用了person,导致block执行完之后才被释放(block当autoReleasePool执行完之后才会被释放) 查看c++代码

     查看main函数调用

     通过上面查看结构体struct __main_block_desc 里面多了两个copy和dispose(相当于retain)对person进行捕捉到age变量,当block不被释放,person对象也不会被释放

    当断点改到14行,执行完block时,查看结果

     block被释放,完成打印释放

     总结:对象访问的auto变量

    当block内部访问了对象类型的auto变量时

    • 如果block在栈上,将不会对auto变量产生强引用
    • 如果block被拷贝到堆上
    1. 会调用block内部的copy函数
    2. copy函数内部会调用Block_object_assign 函数
    3. Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

    • 如果block从堆上移除
    1. 会调用block内部的dispose函数
    2. dispose函数内部会调用_Block_object_dispose函数
    3. _Block_object_dispose函数会自动释放引用的auto变量

    以上就是block详解一的内容,下一篇讲述block剩下的知识点,欢迎关注!!!

  • 相关阅读:
    unnitest简单场景应用
    接口基础之request
    docker常用命令
    管理之心理学
    管理团队挑战和提升
    如何留下核心成员
    管理之面试技巧
    复杂接口请求怎样写http请求
    gitlab使用(一)
    不使用AutoLayout快速兼容适配iPhone6/6 Plus
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/12439357.html
Copyright © 2020-2023  润新知