美文网首页
iOS底层原理 - 窥探Block的本质(一)

iOS底层原理 - 窥探Block的本质(一)

作者: julieQY7 | 来源:发表于2022-02-22 17:10 被阅读0次

    通过窥探Block的底层实现,解答以下问题

    1.Block底层数据结构是什么,本质是什么
    2.Block与其所访问的外部变量的关系
    3.Block的内存管理

    Block的本质是什么?是函数?代码块?OC对象?

    简单起见,我们在main.m文件中写一个没有任何参数访问的简单的Block,通过分析其源码窥探Block的本质
    定义一个简单的Block

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

    我们知道OC是基于C/C++实现的,接下来我们通过命令行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名main.m文件转化成对应的.cpp文件,窥探Block的本质。

    第一步:在.cpp文件中检索int main(int argc, const char * argv[])定位main函数位置,从而找到我们写的代码

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            MyBlock block;
            block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        }
        return 0;
    }
    

    为了方便阅读我们将上述源码中的核心部分((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))中的强制类型转换去掉,可以得到&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA),到此可以清楚的看到这里调用了一个函数__main_block_impl_0(...),传入了两个参数__main_block_func_0__main_block_desc_0_DATA。并将函数返回值的地址赋值给了block。接下来我们分别分析函数及这两个参数。

    首先我们看下函数__main_block_impl_0
    .cpp文件中进一步检索函数__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;
      }
    };
    

    通过上述源码,可以看到__main_block_impl_0(...)是结构体struct __main_block_impl_0的构造函数。也就是说Block的本质就是结构体struct __main_block_impl_0

    继续检索传入的参数__main_block_func_0

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d9b536_mi_0);
    }
    

    可以看出这就是我们写的NSLog(@"this is block");即block的实现部分。

    继续检索__main_block_desc_0_DATA

    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 __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;
      }
    };
    

    进一步分析其成员变量,其中struct __main_block_desc_0我们上边已经看过了。接下来我们检索成员变量struct __block_impl

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    

    可与看到一个很熟悉的指针isa指针,我们知道oc对象的基本特点就是有一个isa指针,看到这你可能会猜想block本身是否也是一个oc对象呢?
    答案是肯定的block本质的确是一个oc对象,下面我们将通过其他方式验证这一点。我们知道几乎所有的oc对象都继承自NSObject,尝试打印Block的父类。

    NSLog(@"block class : %@", [block class]);
    NSLog(@"block super class : %@", class_getSuperclass([block class]));
    NSLog(@"block super super class : %@", class_getSuperclass(class_getSuperclass([block class])));
    NSLog(@"block super super super class : %@", class_getSuperclass(class_getSuperclass(class_getSuperclass([block class]))));
    
    // 输出log
    block class : __NSGlobalBlock__
    block super class : __NSGlobalBlock
    block super super class : NSBlock
    block super super super class : NSObject
    

    从上述log可以看出,block本身的确是一个oc对象,且其继承自NSObject

    Block对基础数据类型的捕获

    定义三种基本类型变量,并在Block中访问他们

    int globalInt = 1; // 全局变量
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MyBlock block;
            static int staticInt = 5; // 静态变量
            int autoInt = 10; // 自动变量(本质上是auto int autoInt = 10;省略了关键字auto,通常我们定义的都是这种变量)
            block = ^{
                NSLog(@"staticInt = %d", staticInt);
                NSLog(@"autoInt = %d", autoInt);
                NSLog(@"globalInt = %d", globalInt);
            };
            block();
        }
        return 0;
    }
    

    通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名得到对应的.cpp文件。
    .cpp文件中检索int main(int argc, const char * argv[])定位main函数

    int globalInt = 1;
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            MyBlock block;
            static int staticInt = 5;
            int autoInt = 10;
            block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticInt, autoInt));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    观察上述源码,可以看到在构建block的结构体struct __main_block_impl_0时,比之前多传了两个参数&staticIntautoInt,但是并没有传globalInt,即:static变量传了地址(地址传递),局部auto变量传了值(值传递),全局变量没有传递。
    接下来我们去struct __main_block_impl_0中看下这些参数的处理

    Block对基础数据类型的捕获.png

    观察上述代码,可以看到结构体struct __main_block_impl_0中多了两个成员变量int *staticInt;int autoInt;,并且构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticInt, int _autoInt, int flags=0) : staticInt(_staticInt), autoInt(_autoInt)将外边传过来的_staticInt_autoInt分别赋值给了int *staticInt;int autoInt;也就是说Block分别对静态变量和自动变量进行了地址捕获和值捕获

    在看下__main_block_func_0__main_block_desc_0

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *staticInt = __cself->staticInt; // 取结构体中的int型指针变量staticInt给局部int型指针变量staticInt
      int autoInt = __cself->autoInt; // 取结构体中int型变量autoInt给局部int型变量autoInt
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_0, (*staticInt)); // 打印局部int型指针staticInt指向值
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_1, autoInt);//打印局部变量autoInt的值
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_2, globalInt);//打印全局变量globalInt的值
            }
    
    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)};
    

    观察上述代码可知__main_block_desc_0没有变化

    小结

    • Block不会捕获全局变量
    • Block会对静态变量进行地址捕获
    • Block会对自动变量进行值捕获

    那么Block为什么这么做呢,认真思考下就会发现这是一个很合乎情理的设计,对于全局变量由于它是全局的,本身就可以在任意地方访问,所以Block自然不需要捕获它,就像函数中访问全局变量不需要将其当参数传进去一样;对于局部静态变量,首先我们想象一种使用场景,Block及局部静态变量是在一个普通函数中的,并且Block作为函数返回值使用,在这种情况下,因为出了函数我们就无法访问到局部变量了,所以首先Block肯定要对这个静态局部变量进行捕获,再者我们知道静态变量不会随着大括号即函数的结束而销毁,所以Block只需要对其进行地址捕获即可。对于普通的自动局部变量,由于其会被销毁,所以需要对其进行值捕获

    Block对OC对象的捕获

    为了演示,我们先创建一个类LJPerson

    @interface LJPerson : NSObject
    
    @property (copy, nonatomic) NSString    *name;
    @property (assign, nonatomic) int       age;
    
    @end
    

    和基础变量一样,我们也创建三种不同的LJPerson变量,并在Block中访问他们

    typedef void(^MyBlock)(void);
    
    LJPerson *globalPerson; // 全局变量
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MyBlock block;
            
            globalPerson = [[LJPerson alloc] init];
            globalPerson.name = @"globalPerson";
            globalPerson.age = 1;
            
            static LJPerson *staticPerson; // 静态变量
            staticPerson = [[LJPerson alloc] init];
            staticPerson.name = @"staticPerson";
            staticPerson.age = 10;
            
            LJPerson *autoPerson = [[LJPerson alloc] init]; // 自动变量
            autoPerson.name = @"autoPerson";
            autoPerson.age = 20;
            
            block = ^{
                NSLog(@"globalPerson = %@", globalPerson.name);
                NSLog(@"staticPerson = %@", staticPerson.name);
                NSLog(@"autoPerson = %@", autoPerson.name);
            };
            block();
        }
        return 0;
    }
    

    同样的生成对应的.cpp文件,并检索main函数int main(int argc, const char * argv[])和结构体struct __main_block_impl_0

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            MyBlock block;
    
            globalPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_0);
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setAge:"), 1);
    
            static LJPerson *staticPerson;
            staticPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_1);
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setAge:"), 10);
    
            LJPerson *autoPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_2);
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setAge:"), 20);
    
            block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticPerson, autoPerson, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      LJPerson **staticPerson; // 地址捕获
      LJPerson *autoPerson; // 值捕获
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LJPerson **_staticPerson, LJPerson *_autoPerson, int flags=0) : staticPerson(_staticPerson), autoPerson(_autoPerson) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    观察上述源码,可知Block对OC变量的捕获机制和基础数据类型一样。即:Block对变量的捕获方式只取决于其是全局变量、静态变量、还是自动变量。
    那么Block对OC类型变量的处理与基础数据类型变量的处理究竟有什么不同呢?
    检索Block的成员变量struct __main_block_desc_0

    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};
    

    对比之前的源码,可以发现这里多了两个函数copydispose
    观察结构体对象__main_block_desc_0_DATA的入参,可知copydispose函数分别对应参数__main_block_copy_0__main_block_dispose_0,检索函数__main_block_copy_0__main_block_dispose_0

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->staticPerson, (void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->autoPerson, (void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    

    观察函数两个函数,可以看到__main_block_copy_0内部分别对staticPersonautoPerson对象各调用了一次_Block_object_assign方法,__main_block_dispose_0内部分别对staticPersonautoPerson各调用了一次_Block_object_dispose方法。也就是说Block除了捕获OC对象的外,还会对OC对象做内存管理。

    小尾巴

    到此我们已经解决了开篇中的第1、2个问题,并且知道了Block会对OC对象做内存管理,那么在后续的文章里我们将继续探索Block是如何进行内存管理的以及当我们用__weak__block修饰变量时究竟是在做些什么

    相关文章

      网友评论

          本文标题:iOS底层原理 - 窥探Block的本质(一)

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