将来跳槽用

iOS相关知识(六)-- block

2021-09-23  本文已影响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
上一篇 下一篇

猜你喜欢

热点阅读