iOS block

作者: 爱学的小姚 | 来源:发表于2021-11-05 19:48 被阅读0次

    block的数据结构

    先来一个最简单的block,看看这个block到底执行了什么

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            void(^block)(void) = ^{
                NSLog(@"this is block");
            };
            block();
        }
        return 0;
    }
    

    cdmain.m的目录下,执行:

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
    

    我们可以看到上述的代码,转换为c++代码为:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __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;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_6ab938_mi_0);
            }
    
    //  main函数
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
            
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    block声明简化后为:

    *block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
    

    全局搜索__main_block_impl_0,我们在结构体__main_block_impl_0里面找到了同名构造函数。

    结论:block是包含isa指针的对象。

    block的类型

    通过打印classsuperclass可以看出继承关系如下所示,class__NSGlobalBlock__

    image.png
    当我们传递一个局部变量进block的时候,发现class发生改变,变成__NSMallocBlock__
    image.png
    ARC环境转成MRC之后,
    image.png
    执行相同的代码,发现blockclass变成__NSStackBlock__
    image.png
    结论:block是对象类型,有完整的继承链,当声明block时,会转化成__NSGlobalBlock____NSStackBlock____NSMallocBlock__三种类型。

    1、创建一个不包含外部变量的block的时候,则该block在全局区,是__NSGlobalBlock__类型。
    2、block包含局部变量的时候,MRC环境下,则该block在栈区,是__NSStackBlock__类型。
    3、block包含局部变量的时候,ARC环境下,则该block被拷贝到堆区,是__NSMallocBlock__类型。
    ARC环境下成为__NSMallocBlock__条件:有需要自己处理对象生命周期的时候,会将栈区的__NSStackBlock__类型copy到堆区,生成__NSMallocBlock__类型。

    经典问题:如下代码打印结果是什么,以及为什么(涉及到__block

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            int a = 10;
            void(^block)(void) = ^{
                NSLog(@"a = %d" , a);
            };
            a = 20;
            block();
        }
        return 0;
    }
    

    打印结果为10,现在开始解析原因,将上述代码重新编译成C++代码之后,代码为:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_cf236d_mi_0 , a);
            }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            int a = 10;
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
            a = 20;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    在创建block的时候,__main_block_impl_0结构中,会创建一个int a;,在block执行构建函数的时候,将外面的a作为参数传进去,再对__main_block_impl_0结构中,创建的int a;参数进行赋值操作,此时,block内部的a,其初始值即为外面a的值,为10,后面执行a = 20;的操作实际上是对外面的a的操作,并没有影响到内部的结果,所以打印结果为:a = 10,打印的实际上是捕获到的值。

    那么我们如何处理a值不变的问题呢?

    方法一:使用局部静态变量

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            static int a = 10;
            void(^block)(void) = ^{
                NSLog(@"a = %d" , a);
            };
            a = 20;
            block();
        }
        return 0;
    }
    

    底层代码实现原理如下:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *a = __cself->a; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_8dcf0c_mi_0 , (*a));
            }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            static int a = 10;
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
            a = 20;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    构建函数的时候,传递了static int a = 10;的地址,block内部用一个int *a接收地址,所以调用的时候,实际上是捕获静态局部变量的地址,所以可以值会同步打印。【局部静态变量,block获取指针地址】

    方法二:使用全局变量

    static int a = 10;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void(^block)(void) = ^{
                NSLog(@"a = %d" , a);
            };
            a = 20;
            block();
        }
        return 0;
    }
    

    底层代码实现原理如下:

    static int a = 10;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __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;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_7445b2_mi_0 , a);
            }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
            a = 20;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    因为是全局变量,所以直接调用全局变量。

    方法三(重要):使用__block
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block int a = 10;
            void(^block)(void) = ^{
                NSLog(@"a = %d" , a);
            };
            a = 20;
            block();
        }
        return 0;
    }
    

    底层代码实现原理如下:

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_bc1a46_mi_0 , (a->__forwarding->a));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
            (a.__forwarding->a) = 20;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    当我们执行__block int a = 10;的时候,实际底层代码转换为:

            __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    

    __main_block_impl_0中会出现一个__Block_byref_a_0 *a;,是包含isa指针的对象,__Block_byref_a_0中包含int a;,我们在__main_block_desc_0结构中,多出了__main_block_copy_0__main_block_dispose_0函数,

      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    

    copy操作实际上是调用_Block_object_assign函数,进行copy(拷贝)操作,dispose操作实际上是调用_Block_object_dispose函数,进行dispose(销毁)操作。

    结论:当__block作用于局部变量时,会将局部变量封装成一个__Block_byref_XXX_0类型的对象,局部变量的值复制给了__Block_byref_XXX_0类型对象中的同名成员变量中,此时暴露在外面的__block int a = 10;实际上已经封装成了一个对象,当我们对a进行赋值操作的时候,实际上执行的是(a.__forwarding->a) = 20;操作,__forwarding存储的地址就是__Block_byref_a_0 *a的地址,目的是为了保证栈区的block copy到堆区的时候依然可以找到相对应的地址

    block循环引用

    申明一个BlockObject对象

    @interface BlockObject : NSObject
    @property (nonatomic, assign) int num;
    @property (nonatomic, copy) void(^block)(void);
    @end
    
    @implementation BlockObject
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    //  main.m
    #import <Foundation/Foundation.h>
    #import "BlockObject.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BlockObject *objc = [[BlockObject alloc] init];
            objc.num = 10;
            objc.block = ^{
                NSLog(@"%d", objc.num);
            };
        }
        NSLog(@"-----------");
        return 0;
    }
    

    运行结果显示,没有执行dealloc里面的方法,造成了循环引用。

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      BlockObject *objc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, BlockObject *_objc, int flags=0) : objc(_objc) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      BlockObject *objc = __cself->objc; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)objc, sel_registerName("num")));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            BlockObject *objc = ((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockObject"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)objc, sel_registerName("setNum:"), 10);
            ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)objc, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, objc, 570425344)));
        }
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_1);
        return 0;
    }
    

    block里面包含对象类型时,__main_block_impl_0包含了BlockObject *objc;对象,__main_block_copy_0objc对象执行了copy操作,objcretain+1。当执行__main_block_dispose_0的时候,则出现:objc对象中引用了block属性,在block对象中,有了一个被强引用的objc属性,当执行dispose的时候,则会发生互相等待被销毁的循环引用问题。

    解决方法1:使用__weak
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BlockObject *objc = [[BlockObject alloc] init];
            objc.num = 10;
            __weak typeof(objc) weakObjc = objc;
            objc.block = ^{
                NSLog(@"%d", weakObjc.num);
            };
        }
        NSLog(@"-----------");
        return 0;
    }
    

    由于__weak会用到runtime相关的东西,OC转为C++可以使用如下方式:

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
    

    可以看到如下数据结构:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      BlockObject *__weak weakObjc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, BlockObject *__weak _weakObjc, int flags=0) : weakObjc(_weakObjc) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    __weak实际上是对block里面的对象执行了weak操作,结构为:BlockObject *__weak weakObjc;__main_block_copy_0objc对象执行了copy操作,实际上也只是进行了一次weak引用,所以不会造成循环引用。

    解决方法2:使用__block
    //   BlockObject.h
    @interface BlockObject : NSObject
    
    @property (nonatomic, assign) int num;
    @property (nonatomic, copy) void(^block)(void);
    
    - (void)test;
    
    @end
    
    //   BlockObject.m
    @implementation BlockObject
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    
    - (void)test {
        __block id blockSelf = self;
        self.block = ^{
            NSLog(@"%p", blockSelf);
            blockSelf = nil;
        };
        self.block();
    }
    @end
    
    //  main.m
    #import <Foundation/Foundation.h>
    #import "BlockObject.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BlockObject *objc = [[BlockObject alloc] init];
            objc.num = 10;
            [objc test];
        }
        NSLog(@"-----------");
        return 0;
    }
    

    这个方法一般用的比较少,实际上实现原理是形成三角关系,由于__block创建了一个__Block_byref_XXX_0类型的对象,block内部执行了blockSelf = nil;的操作实际上就是切断三角关系,达到去除循环引用的效果。

    相关文章

      网友评论

          本文标题:iOS block

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