zoukankan      html  css  js  c++  java
  • iOS开发系列-Block本质篇

    概述

    在iOS开发中Block使用比较广泛,对于使用以及一些常规的技术点这里不再赘述,主要利用C++角度分析Block内部数据底层实现,解开开发中为什么这样编写代码解决问题。

    Block底层结构窥探

    Block本质也是一个OC对象,内部也有isa指针,最终继承NSObject。它是封装了函数调用以及函数调用环境的OC对象。

    接下来编写一个Block,利用clang编译器指令可以将我们编写的OC代码转换成C++代码,更好的看清Block底层结构。

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            // 定义block
            void(^block)(void) = ^{
                NSLog(@"-----------");
            };
            
            // 执行block
            block();
        }
        return 0;
    }
    
    

    执行命令

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

    上面编写OC代码转换成C++代码main.cpp

    #pragma clang assume_nonnull end
    
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    // Block底层的结构
    struct __main_block_impl_0 {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr; // 保存内部代码块执行的函数地址
      // struct __block_impl impl;
        
      struct __main_block_desc_0* Desc; // Block的信息
      // 构造方法
      __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_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_3df4b0_mi_0);
    }
    
    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)/*block自己结构的内存大小*/};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            // 定义block
            //  block真实类型  struct __main_block_impl_0 *
            void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    
            // 执行block
            block->FuncPtr(block);
        }
        return 0;
    }
    

    Block底层数据结构是__main_block_impl_0类型,定义的block变量是指向__main_block_impl_0类型的对象类型指针。
    __main_block_impl_0构造方法的需要两个参数,第一个参数:__main_block_func_0函数是对Block内部代码块的封装。第二个参数类型是__main_block_desc_0_DATA类型指针,内部主要对Block的信息封装,比如包含block的占用内部空间大小。

    __main_block_impl_0 构造方法中将封装block代码块的__main_block_func_0函数地址传入__main_block_impl_0 对象的FuncPtr成员保存。

    当执行block,直接通过指针访问到FuncPtr成员的函数地址进行调用。

    Block变量的捕获

    在开发中block内部会访问外部的变量,为了保证内部能正常访问外部的变量,block有个变量捕获的机制。

    编写代码验证

    #import <Foundation/Foundation.h>
    
    // 全局变量
    int weight = 120;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            // 定义block
            int age = 10;
            static int height = 178;
            
            void(^block)(void) = ^{
                NSLog(@"age is %d", age);
                NSLog(@"height is %d", height);
                NSLog(@"weight is %d", weight);
            };
            
            // 执行block
            block();
        }
        return 0;
    }
    

    clang转成对应的C++代码

    #pragma clang assume_nonnull end
    
    
    int weight = 120;
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age; // 局部变量age的捕获
      int *height; // static修饰的局部变量的捕获
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    // 执行block内部代码块函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int age = __cself->age; // 取出内部捕获的age
      int *height = __cself->height; // 取出内部捕获的height
        
                // 打印age
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_848c45_mi_0, age);
                // 打印height
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_848c45_mi_1, (*height));
                // 打印weight
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_848c45_mi_2, weight);
            }
    
    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 age = 10;
            static int height = 178;
    
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
    
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    默认定义一个局部变量都为auto自动变量(离开大括号作用域自动销毁),block内部的代码块不确定什么时候执行,固然不能通过指针捕获。
    static修饰变量在数据段,全局有效,因此可以通过内存地址捕获。
    全局变量本身在整个程序全局都可以访问,生命周期整个程序。所以无需捕获,在block执行时直接访问即可。

    Block的类型

    block有3中类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

    为了更好的研究block,先将Xcode的Automatic Reference Counting关闭。

    没有访问auto变量为__NSGlobalBlock__类型
    访问了auto变量为__NSStackBlock__ 类型
    __NSStackBlock__ 类型block进行copy操作,会将栈中空间的block拷贝到堆中引用计数器+1且类型__NSMallocBlock__
    注意必须是对__NSStackBlock__ 类型block进行copy操作才会拷贝到堆中。

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            int age = 12;
            void(^block)(void) = [^{
                NSLog(@"%d", age);
            } copy];
            
            // 释放block
            [block release];
        }
        return 0;
    }
    

    block的copy

    对象类型的auto变量

    #import <Foundation/Foundation.h>
    #import "CHPerson.h"
    
    typedef void(^CHBlock)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            CHPerson *person = [[CHPerson alloc] init];
            
            // 在ARC下 block被q强引用指针引用被拷贝的堆中
            CHBlock block = ^{
                NSLog(@"----%@", person);
            };
            
            block();
            NSLog(@"---------------------");
        }
        return 0;
    }
    

    clang编译器生成对应的c++代码

    #pragma clang assume_nonnull end
    
    typedef void(*CHBlock)(void);
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      CHPerson *person; // block内部捕获CHPerson
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CHPerson *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    // block代码块函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        CHPerson *person = __cself->person; // bound by copy
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_b0818f_mi_0, person);
    }
    // __NSStackBlock__类型block赋值到堆中执行的copy操作
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    // block销毁会对内部引用的对象类型进行release
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      // __NSStackBlock__类型block赋值到堆中执行的copy操作 函数指针
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      // block销毁会对内部引用的对象类型进行release 函数指针
      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; 
            
            // 创建Person对象
            CHPerson *person = ((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CHPerson"), sel_registerName("alloc")), sel_registerName("init"));
    
            
            CHBlock block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
            
            // 执行block
            block->FuncPtr(block);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_b0818f_mi_1);
        }
        return 0;
    }
    

    __bock修饰符

    默认block内部无法修改auto变量的值

    #import <Foundation/Foundation.h>
    #import "CHPerson.h"
    
    typedef void(^CHBlock)(void);
    
    int height = 170;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            // __block修饰的auto变量 
            int age = 10;
            static int weight = 120;
            CHPerson *person = [[CHPerson alloc] init];
            person.age = 12;
            
            NSMutableArray *array = [NSMutableArray array];
            
            CHBlock block = ^{
                [array addObject:@"123"];
                [array addObject:@"jake"];
                person.age = 22;
                
                // 下列写法报错
    //            age = 20;
    //            person = nil;
    //            weight = 125;
    //            array = nil;
            };
            
            block();
            NSLog(@"-------------");
        }
        return 0;
    }
    

    auto变量在block内部捕获只是值传递,内部无法修改auto变量的值。而全局变量与static修饰的变量是可以修改的,那是因为他们作用域是整个程序并且static修饰变量在block捕获的是变量地址,因此可以block内部修改。

    虽然可以通过定义static或者全局变量来实现在block内部修改变量,但是全局变量和static会修改变量的作用域,因此开发中一般使用__block
    __block不能修饰全局变量、静态变量(static)

    #import <Foundation/Foundation.h>
    #import "CHPerson.h"
    
    typedef void(^CHBlock)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            // __block修饰的auto变量 
            __block int age = 10;
            __block CHPerson *person = [[CHPerson alloc] init];
            __block NSMutableArray *array = [NSMutableArray array];
            
            CHBlock block = ^{
                
                // 下列写法报错
                age = 20;
                person = nil;
                array = nil;
            };
            
            block();
            NSLog(@"-------------");
        }
        return 0;
    }
    

    clang编译器生成对应的c++代码

    #pragma clang assume_nonnull end
    
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
        _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
        _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    
    typedef void(*CHBlock)(void);
    
    // 编译器将__block int age 变量包装成一个对象
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    
    // 编译器将__block CHPerson *person 变量包装成一个对象
    struct __Block_byref_person_1 {
      void *__isa;
    __Block_byref_person_1 *__forwarding; // 指向自己
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     CHPerson *person;
    };
    
    // 编译器将__block NSMutableArray *array 变量包装成一个对象
    struct __Block_byref_array_2 {
      void *__isa;
    __Block_byref_array_2 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSMutableArray *array;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
        
      // 内部指向__block变量包装的对象
      __Block_byref_age_0 *age; // by ref
      __Block_byref_person_1 *person; // by ref
      __Block_byref_array_2 *array; // by ref
        
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, __Block_byref_person_1 *_person, __Block_byref_array_2 *_array, int flags=0) : age(_age->__forwarding), person(_person->__forwarding), array(_array->__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_age_0 *age = __cself->age; // bound by ref
      __Block_byref_person_1 *person = __cself->person; // bound by ref
      __Block_byref_array_2 *array = __cself->array; // bound by ref
    
                (age->__forwarding->age) = 20;
                (person->__forwarding->person) = __null;
                (array->__forwarding->array) = __null;
    }
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign(&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_assign(&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_dispose((void*)src->array, 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; 
    
            
            // 编译器将__block int age 变量包装成一个对象
            __Block_byref_age_0 age = {
                0,
                (__Block_byref_age_0 *)&age,
                0,
                sizeof(__Block_byref_age_0),
                10 // age
            };
            
            // 编译器将__block CHPerson *person 变量包装成一个对象
            __Block_byref_person_1 person = {
                    0,
                    (__Block_byref_person_1 *)&person,
                    33554432,
                    sizeof(__Block_byref_person_1),
                    __Block_byref_id_object_copy_131,
                    __Block_byref_id_object_dispose_131,
                    ((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CHPerson"), sel_registerName("alloc")), sel_registerName("init"))
                
            };
            
            // 编译器将__block NSMutableArray *array 变量包装成一个对象
            __Block_byref_array_2 array = {
                    0,
                    (__Block_byref_array_2 *)&array,
                    33554432,
                    sizeof(__Block_byref_array_2),
                    __Block_byref_id_object_copy_131,
                    __Block_byref_id_object_dispose_131,
                    ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))
            };
    
            CHBlock block = &__main_block_impl_0(
                                                 __main_block_func_0,
                                                 &__main_block_desc_0_DATA,
                                                 (__Block_byref_age_0 *)&age,
                                                 (__Block_byref_person_1 *)&person,
                                                 (__Block_byref_array_2 *)&array,
                                                 570425344
                                                 ));
    
            block->FuncPtr(block);
            
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_b553f0_mi_0);
        }
        return 0;
    }
    

    __block之所以可以修改外部的auto变量,是因为编译器将__block变量包装成一个对象。

    __block的内存管理

    ARC下解决循环引用

    MRC下解决循环引用

  • 相关阅读:
    饿了么P7级前端工程师进入大厂的面试经验
    前端程序员面试的坑,简历写上这一条信息会被虐死!
    这次来分享前端的九条bug吧
    移动端开发必会出现的问题和解决方案
    创建一个dynamics 365 CRM online plugin (八)
    创建一个dynamics 365 CRM online plugin (七)
    创建一个dynamics 365 CRM online plugin (六)
    创建一个dynamics 365 CRM online plugin (五)
    使用User Primary Email作为GUID的问题
    怎样Debug Dynamics 365 CRM Plugin
  • 原文地址:https://www.cnblogs.com/CoderHong/p/10055004.html
Copyright © 2011-2022 走看看