美文网首页
iOS Block理解

iOS Block理解

作者: 小豆豆苗 | 来源:发表于2020-02-17 00:46 被阅读0次

    一、Block的本质
    block本质上也是一个OC对象,它的内部也会有一个isa指针,它是封装了函数调用以及函数调用环境的OC对象。
    比如,定义如下一个block

      int age = 20;
    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!");
                NSLog(@"this is a block!");
            };
    
    这段block它的底层源码如下:

    首先上面代码库中的block在内存中是一个叫__main_block_impl_0的结构体,结构体中有两个结构体变量,一个是叫impl的变量,一个叫Desc的指针。
    在impl中我们可以看到它的内部有一个isa指针,所以说我们能确定它是一个OC对象。且在impl中有一个叫funcPtr的指针,它指向的是block执行的代码块(函数)的内存地址。Desc中存放的就是block内存大小等信息。

    二、block的auto值捕获
    1、如下代码打印结果是多少?

        int age = 10;    
        void (^block)(void) = ^{
            NSLog(@"age", age);
        };
        age = 20;
        block();
    

    打印结果:10
    原因:由于之前的知识点,我们知道block在创建的时候会生成一个impl和Desc属性。当block中含有其它变量的时候还会生成对应的变量age,所以它就捕获到了age的值为10,之后 age = 20这句代码只是修改了age的值,由于是值传递方式,并不能改变block中age的值,所以打印结果为10。

    2、如下代码打印结果是多少?

        /*auto:自动变量,离开作用域就会销毁。
        它是默认的关键字,所以通常情况下是省略的。*/
        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();
    

    打印结果:age is 10, height is 20。

    原因:
    如图,block捕获到的auto变量为普通变量,捕获到的static变量为指针,由于指针所指向的是变量的内存地址,当height = 20时,内存中的值也变成了20。

    三、Block的类型
    block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。
    NSGlobalBlock ( _NSConcreteGlobalBlock )
    NSStackBlock ( _NSConcreteStackBlock )
    NSMallocBlock ( _NSConcreteMallocBlock )

    image.png 1.NSGlobalBlock:没有访问auto类型变量,但是可以访问静态变量或者全局变量。它存储在数据区。
    1.
    void (^block1)(void) = ^{
        NSLog(@"block");
    }
    NSLog(@"%@",[block1 class]) //__NSGlobalBlock__
    
    2.
    static int age = 10
    void (^block1)(void) = ^{
        NSLog(@"%d",age);
    }
    NSLog(@"%@",[block1 class]) //__NSGlobalBlock__
    

    2.NSStackBlock:访问auto类型变量的block类型。它存储在栈区。
    PS:栈区的数据在调用之后会自动销毁。

    3.
    int age = 10
    void (^block1)(void) = ^{
        NSLog(@"%d",age);
    }
    NSLog(@"%@",[block1 class]) //__NSStackBlock__
    
    4.
    void (^block)(void)
    void test( ) {
      int age = 10
      block = ^{
          NSLog(@"block----%d",age);
      }
    }
    main(){
      test( );
      block( ); //打印结果为:block----272632488
    }
    /*由于block是栈类型的,执行test方法之后,block内部的age变量被销毁,再调用block方法就会打印出一个脏数据*/
    

    3.NSMallocBlock:对stack类型的block进行copy操作,得出的类型为Malloc类型的block,也就是堆类型的block变量。堆数据需要手动管理内存,需要手动释放。

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

    • block作为函数返回值时
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时,如数组中的遍历方法:enumerateObjectsUsingBlock:
    • block作为GCD API的方法参数时,如dispatch_once(&onceToken,^{})

    2、ARC下block属性的写法:
    @property(strong, nonatomic) void(^block)(void);
    @property(copy, nonatomic) void(^block)(void);

    3、MRC下block属性的写法:
    @property(copy, nonatomic) void(^block)(void);

    五、对象类型的auto变量
    之前我们所说的auto类型变量讲的是基本数据类型,如int age = 10。当我们遇到自定义的对象类型,那么它的auto变量在block中是怎么处理呢?

    6.
    void (^Block)(void)
      Person * person = [[Person alloc]init];
      person.age = 10
      Block block =  ^{
          NSLog(@"block----%d",person.age);
      } ;
    }
    /*因为Person类是局部变量,创建block的时候将Person捕获,
    block内部产生了一个Person * person的指针对象。
    所以person对象不会释放,它会等block释放时再进行释放*/
    

    结论:当block内部访问了对象类型的auto变量时:
    1、如果block是在栈上,不管是使用强指针还是弱指针对auto变量进行访问,都不会对auto变量产生强引用。
    2、如果block被拷贝到堆上,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
    3、如果block从堆上移除,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

    六、__block修饰符
    我们在实际开发过程中,有可能需要暂时修改auto变量的值。此时,如果使用static来修饰或者声明为全局变量的话是会达到修改的目的,但是一经修改就不能恢复。此时用__block修饰变量可以解决这个问题。

    typedef void (^Block)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block int age = 10;
            Block block = ^{
                age = 20;
                NSLog(@"age is %d", age);
            };
            block(); //打印结果:age is 20
        }
        return 0;
    }
    

    1、__block可以用于解决block内部无法修改auto变量值的问题
    2、__block不能修饰全局变量、静态变量(static)

    3、编译器会将__block变量包装成一个对象。如下图所示,block里面有一个__Block_byref_age_0类型的age指针,它指向的对象里面有个age属性。

    七、__block的内存管理
    1、当block在栈上时,并不会对__block变量产生强引用。
    2、当block被copy到堆时,_Block_object_assign函数会对__block变量形成强引用。
    3、被__block修饰的对象类型:当__block变量在栈上时,不会对指向的对象产生强引用;当__block变量被copy到堆时,_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)。

    八、block的循环引用问题

    • 在block内部使用外部指针且会造成循环引用情况下,需要用_weak修饰外部指针:
      _weak typeof(self) weakSelf = self;
    • 在block内部如果调用延时函数还是用弱指针会取不到该指针,因为已经销毁了,需要在block内部再将弱指针重新强引用一下:
      __strong typeof(self) strongSelf =weakSelf;

    相关文章

      网友评论

          本文标题:iOS Block理解

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