iOS:block的底层原理

    技术2022-07-15  76

    大家好,我是OB!今天来聊聊大家的老熟人Block!

    block

    block 本质就是NSObject对象,把方法包装成了block块

    来看看block的真面目

    void(^OBblock)(void) = ^{ NSLog(@"-------"); }; OBblock(); /* * 编译后 */ void(*OBblock)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA ); OBblock->FuncPtr(OBblock);

    编译后

    struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; //block 最终变成了 __main_block_impl_0类型的结构体 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //构造函数,返回 __main_block_impl_0 类型的实例 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //block块中的实现,放在这个函数中 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_fo_T_main_0072ab_mi_0); }

    值捕获

    //全局变量 int height_ = 20; static int weight_ = 20; void test() { auto int num = 20; static int age = 20; void(^OBblock)(void) = ^{ NSLog(@"num:%d age:%d %d %d",num,age,height_,weight_); }; num = 10; age = 10; OBblock(); }

    再看看结构体发生变化没

    void(*OBblock)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, num, //值捕获,20已经被赋值到结构体中了 &age //指针捕获 ); //全局变量height_,weight_没有捕获 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int num; int *age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int *_age, int flags=0) : num(_num), age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };

    多了num *age构造函数也多了一个: num(_num) age(_age)(将_nun的值自动赋值给num变量),但是 全局变量height_,weight_没有捕获却没有捕获全局变量,

    调用函数实现:num直接取值__cself->num,static变量:__cself->age指针传递,获取age地址取值,全局变量height_,weight_直接访问

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int num = __cself->num; // bound by copy int *age = __cself->age; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__..._mi_0,num,(*age),height_,weight_); }

    捕获与否取决去block的函数执行块,执行时,能不能访问改变量。

    变量类型block捕获访问方式能否修改局部变量:auto捕获值传递添加__block 修饰局部变量:static捕获指针传递可直接修改全局变量:static/auto不捕获直接访问可直接修改

    block类型

    block 本质是一个结构体,换成OC那就是block本质是NSObject;

    void getType(id obj) { if (obj == NULL) { return; } Class currentClass = [obj class]; NSLog(@"%@",currentClass); getType([(NSObject*)currentClass superclass]); }

    可以看到结果:block 也是继承 NSObject

    Test_05_block[56306:5305484] __NSGlobalBlock__ Test_05_block[56306:5305484] __NSGlobalBlock Test_05_block[56306:5305484] NSBlock Test_05_block[56306:5305484] NSObject

    进一步探索发现:block的三种类型

    block访问的变量类型block类型copy操作没有访问局部auto变量__NSGlobalBlock__一直在数据区什么也不做,反正也没有访问变量,一般不考虑访问局部auto变量__NSStackBlock__从栈区copy到了堆区,StackBlock变成了MallocBlockNSStackBlock调用了copy__NSMallocBlock__引用计数加一

    注意:NSStackBlock调用了copy变成__NSMallocBlock__其实就是对象的生命周期或者是变量的生命周期不统一,需要放到一个能长久访问的地方,防止程序访问时访问不到值。所以ARC才会自动给NSStackBlock copy一下,变成__NSMallocBlock__

    循环引用

    使用block会造成block捕获对象,会造成对象的引用计数+1,这时就会可能造成循环引用

    解决循环引用方法操作安全性__weak指针指向的对象销毁时,自动置为nil安全__unsafe_unretained指针指向的对象销毁时,还指向那个地址不安全__block利用在block中可以修改对象的特性,将obj=nil安全

    不使用 __weak

    struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Animal *animal; //对象引用计数+1 ... };

    使用 __weak后

    struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Animal *__weak animal; //变成弱引用 ... };

    block和delegate相比

    优点

    回调的block代码块定义在委托对象函数内部,使代码更为紧凑,使用方便被委托对象不再需要实现具体某个protocol,代码更为简洁

    缺点

    delegate运行成本低,block成本很高。 block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除;delegate只是保存了一个对象指针,直接回调,没有额外消耗如果在block里面使用了self,容易导致循环引用问题,要用weakdelegate 更适用于多个回调方法,block 则适用于少量回调
    Processed: 0.024, SQL: 12