iOS Block理解

2020-02-17  本文已影响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复制到堆上,比如以下情况:

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的循环引用问题

上一篇下一篇

猜你喜欢

热点阅读