• 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下解决循环引用

  • 相关阅读:
    android传送照片到FTP服务器
    Android对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
    android上传图片至服务器
    android中如何处理cookie
    【265】shell文件创建链接
    【264】◀▶ Windows 批处理(CMD)
    【263】Linux 添加环境变量 & 全局 shell 脚本
    【262】pscp命令 实现windows与linux互传文件
    合泰 HT66F30 定时器初始化
    错误 是否保存对以下各项的更改 devenv.sin
  • 原文地址:https://www.cnblogs.com/CoderHong/p/10055004.html
Copyright © 2020-2023  润新知