美文网首页将来跳槽用
iOS相关知识(六)-- block

iOS相关知识(六)-- block

作者: 奋斗的小马达 | 来源:发表于2021-09-23 16:27 被阅读0次

    1、block的本质

    block本质上也是一个OC对象,它内部也有个isa指针
    block是封装了函数调用以及函数调用环境的OC对象
    
    

    2、block的变量捕获(capture)

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


    block的变量捕捉.png

    结论:

    局部变量(block内部会捕捉变量)
    
    1、auto 
    使用auto修饰的局部变量,变量的作用域就在局部 离开作用域就会被销毁
     block内部 捕捉auto修饰的局部变量 在block创建的时候直接把值给传递进去了
    
    
    2、static
    使用auto修饰的局部变量,只要程序没有退出就一直存在在内存中不被销毁
     block内部 捕捉static 修饰的局部变量 在block创建的时候直接把地址给传递进去了
    
    全局变量 (block内部不会捕捉变量)
    直接访问全局变量的值,不会捕获变量
    
    也就是说 block 内部访问局部变量时 block在创建的时候就会在内部自动生成对应的变量 然后将值或指针传递进去
    Block 内部访问全局变量的时候 不会做任何处理 直接访问全局变量的值
    
    原因是:在垮函数调用block的时候 局部变量随时可能会被销毁  所以为了保证block内部访问的值不被销毁,block在创建的时候就直接自动生成对应的变量来存储内部访问的变量。全局变量无论什么地方访问他都会存在 所以不用关心被销毁因此block内部不会去捕捉
    

    3、block的类型

    block有3种类型,可以通过调用class方法或者isa指针查看具体类型,
    最终都是继承自NSBlock类型(NSBlock 继承 NSObject)
    __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
    __NSStackBlock__ ( _NSConcreteStackBlock )
    __NSMallocBlock__ ( _NSConcreteMallocBlock )
    
    

    三种block 内存分配

    三种block 内存分配.png

    上图注解:

    上图 从上至下内存地址排列 是 从低地址到高地址
    
    程序区域:平时写的OC代码 都在 程序区域也就是.text区
    
    数据区域:一般全局变量都放在数据区域也就是.data区
    
    堆:平时alloc 出来的对象都会放在 堆区
    (堆区:动态分配内存,也就是开发者自己去申请内存,且开发者自己管理内存)
    
    栈:存放局部变量,程序会自己管理内存
    
    
    

    如何区分这三种block

    区分这三种block.png

    上图注解:

    globalBlock

    没有访问auto变量的block 都是 globalBlock
    如:
         void (^block1)(void) = ^{
                NSLog(@"Hello");
            };
    或:
    //a 是局部变量
           static int a = 10;
            void (^block1)(void) = ^{
                NSLog(@"Hello----%d",a);
            };
    
    或:
    //a 是全局部变量
           int a = 10;
            void (^block1)(void) = ^{
                NSLog(@"Hello----%d",a);
            };
    
    只记住一点:只要没有访问auto变量的block 都是globalBlock
    

    stackBlock

    如果访问了 auto变量 就是 stackBlock
    
    在ARC环境下,程序在运行的时候会自动对stackBlock进行copy处理 所以在ARC环境下打印出来的block类型就是 mallocBlock
    
    原因:stackBlock 是在栈区,栈区会自动销毁内存,如果垮函数去调用block
    此时block可能已经被释放了 如果再去调用这个block 可能访问的就是一块垃圾内存,
    
    为了解决这个问题,
    

    mallocBlock

    stackBlock 调用copy 就会变成mallocBlock 
    在ARC环境下,程序在运行的时候会自动对stackBlock进行copy处理
    

    在开发过程中我们使用的block一般都是 mallocBlock

    每一种类型的block调用copy后的结果如下所示

    block调用copy后的结果.png

    4、block的copy

    在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
    block作为函数返回值时
    将block赋值给__strong指针时
    block作为Cocoa API中方法名含有usingBlock的方法参数时
    block作为GCD API的方法参数时
    

    5、__block修饰符

    __block可以用于解决block内部无法修改auto变量值的问题
    
    __block不能修饰全局变量、静态变量(static)
    
    编译器会将__block变量包装成一个对象(如下图最后一张)
    
    
    使用__block修饰的变量底层代码结构图.png

    图解:

    编译器在编译的时候会讲__block变量包装成一个对象
    
    block内部会有一个指针  __Block_byref_age_0 *age
    (如:第二个图)指向生成的对象(底层为一个结构体如最后一个图:__block变量生成的对象底层代码)
    
    __block变量生成的对象 里面又有一个指针存放age的值(第三个图中 val)
    同时内部又有一个指针指向他自己(第三个图 __forwarding)
    
    

    修改方式

    先看底层C++代码


    block底层c++代码.png

    图解:

    修改age值的步骤:
    1、先通过block内部的 __Block_byref_age_0 *age指针找到
    __block变量生成对象(底层为结构体)的__forwarding指针(这个指针就是指向他自己)。
    
    2、然后在结构体中找这个age。
    
    3、最后再对其进行赋值操作。
    

    block内部如何修改变量的值

    这个问题一般问的是修改局部变量的值,因为全局变量直接可以访问到,不存在修改不了的情况
    
    
    如何修改局部变量的值:
    第一种:直接使用 static修饰
    第二种:局部变量改为全局变量
    第三种:使用__block修饰变量
    
    最好的方式就是第三种,使用__block修饰变量  因为前两种 都是将变量永久存放在内存中。
    
    

    注意:只要不对变量/对象 的值进行修改 就不要去使用__block

    如下面的代码就不需要使用__block 因为他们只是使用了变量/对象而没有对其进行修改

    #import <Foundation/Foundation.h>
    
    typedef void (^Block)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
        //第一种:
           int age = 10;
            Block block1 = ^{
                NSLog(@"age is %d", age);
            };
            block1();
    
            
        //第二种
            NSMutableArray *arr = [[NSMutableArray alloc]init];
            Block block2 = ^{
                [arr addObject:@"444"];
                [arr addObject:@"555"];
                [arr addObject:@"666"];
                NSLog(@"arr is %@", arr);
            };
            block2();
            
        }
        return 0;
    }
    
    

    6、block循环引用

    有三种方式:__weak 、  __unsafe_unretained 和 __block
    
    区别:
    __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
    
     __unsafe_unretained:不会产生强引用,不安全,
    指向的对象销毁时,指针存储的地址值不变
    
    所以:经常使用__weak来解决循环引用问题
    
    使用__block解决block内部循环引用问题.png

    相关文章

      网友评论

        本文标题:iOS相关知识(六)-- block

        本文链接:https://www.haomeiwen.com/subject/zsgegltx.html