美文网首页
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原理解读(三)

    前言 在阅读该篇文章前,推荐阅读ios - block原理解读(一)ios - block原理解读(二) 本文解决...

  • 【iOS开发】一些不错的文章博客整理

    持续更新... RunLoop ibireme深入理解RunLoop Block 声明Block 组件化 iOS组...

  • Block - __block关键字的底层实现原理

    参考文档 iOS中__block 关键字的底层实现原理 你真的理解__block修饰符的原理么? iOS Bloc...

  • iOS Block理解

    1、什么是Block 2、Block语法 3、Block类型变量 4、截获自动变量值 5、__block说明符 6...

  • iOS block 理解

    1、Block生命周期: int (^square) (int) = ^(int a ) {return a*a ...

  • iOS Block理解

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

  • iOS-2 Block

    block块 系列文章: iOS Block浅浅析 - 简书 iOS Block实现原理 iOS Block __...

  • 块与GCD

    没读这本书之前,我对 block理解的成长过程; block编程对于许多初识iOS编程的人来说,很难理解和运用,我...

  • iOS Block存储域及循环引用

    系列文章:iOS Block概念、语法及基本使用iOS Block实现原理iOS Block __block说明符...

  • iOS Block实现原理

    系列文章:iOS Block概念、语法及基本使用iOS Block __block说明符iOS Block存储域及...

网友评论

      本文标题:iOS Block理解

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