美文网首页iOS 底层原理
iOS 开发 Block 原理分析

iOS 开发 Block 原理分析

作者: 天空像天空一样蓝 | 来源:发表于2020-12-21 22:00 被阅读0次

    前言

    无论在面试还是在工作中,总会碰到 block 是什么?block 循环引用怎么办?block 修饰符使用什么?等等这种类似的问题。

    一、 什么是 block

    一、Demo1

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            /// 下面的代码,就是一个简单的 block
            ^{
                NSLog(@"Hello Block");
            };
            
        }
        return 0;
    }
    

    分析:上面的代码,就是一个最简单的 block 但是 NSLog 里面的代码不会被打印出来,因为这个 block 没人调用,所以永远不会执行。

    二、Demo2

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
          
            int age = 18;
            
            void (^block)(void) = ^{
                NSLog(@"age--%d", age);
            };
            block();
        }
        return 0;
    }
    

    分析:如上,运行程序,控制台会打印出 age--18。

    三、分析 block 内部实现

    • 通过 clang 编译可以将 OC 代码转化成 C++ 代码,来查看 block 底层的实现原理
    • 在终端上输入 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 会在main.m 这级目录下生成一个 main.cpp 文件,就是我们想要的 转化后的代码
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    // 这个就是 block 内部的结构,
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age;
      // c++ 的构造函数(类似于 OC 的 init 方法),返回一个结构体对象。
      // age(_age) 这句代码,就是把 _age 赋值给 age 
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    // 封装了 block 执行逻辑的函数,传入到 fp,fp 在赋值到 impl.funcPtr
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int age = __cself->age; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__7ngz5gzx5sjgs4dlqrh7t58w0000gn_T_main_679898_mi_0, age);
            }
    
    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 函数
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            int age = 18;
                // 定义 block 变量
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
             // 执行 block 内部的代码
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    

    分析上面的代码,在 main 函数里我们对比,转化后的 C++ 代码和 原生的 OC 代码

    图片.png

    __main_block_impl_0 这个结构体就是 block 的本来面目

    __main_block_impl_0

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age;
      // c++ 的构造函数(类似于 OC 的 init 方法),返回一个结构体对象。
      // age(_age) 这句代码,就是把 _age 赋值给 age 
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    上面这个结构体的构造函数,传入的参数:void *fp、struct __main_block_desc_0 *desc、int _age、int flags=0

    由于 int flags=0 传入的是常亮数值 0 因此可以忽略,int _age 这个参数是因为外面定义的 局部变量,也可以忽略,所以 这个构造函数的必须参数有两个 void *fp、struct __main_block_desc_0 *desc

    上面的 C++ 代码中在 可以看出 void *fp 对应的是 &__main_block_impl_0、struct __main_block_desc_0 *desc 对应的是 &__main_block_desc_0_DATA ,fp又赋值到了imp里面的 FuncPtr,desc 赋值到 Desc 就是 当前结构体的 Desc。

    __main_block_func_0

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int age = __cself->age; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__7ngz5gzx5sjgs4dlqrh7t58w0000gn_T_main_679898_mi_0, age);
            }
    

    上面是 block 执行逻辑的函数,传入到 fp,fp 在赋值到 impl.funcPtr

    __block_impl

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

    __main_block_desc_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 的大小。

    四、Demo3 捕获 auto 变量的 block

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

    如上,我们修改了局部变量的 age 修改为20,但是运行后,block 里面打印的结果仍为 18.

    • 这是因为 block 捕获了 age = 18,进行了值传递,相当于直接把 age= 18 赋值给了 age, 无论外面怎么修改,都不会改变 age 的值,
    • C 语言会在我们定义局部变量的时候,自动给我们的属性加上 auto 修饰

    五、Demo4 捕获 static 变量的 block

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
          
            int age = 18;
            static int height = 170;
            
            void (^block)(void) = ^{
                NSLog(@"age--%d,height--%d", age, height);
            };
            age = 20;
            height = 180;
            block();
        }
        return 0;
    }
    

    修改 height 的值,运行代码,结果为 age--18,height--180

    这是为什么呢,再次编译运行生成 C++ 代码

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            int age = 18;
            static int height = 170;
    
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
            age = 20;
            height = 180;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    从上面的 C++ 代码可以看出传递给block 的height 是地址传递,指针传递

    • block 内部捕获的是 *height 而不是 height。

    六、Demo5 全局变量

    int age = 22;
    int height = 170;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
          
            void (^block)(void) = ^{
                NSLog(@"age--%d,height--%d", age, height);
            };
            age = 20;
            height = 180;
            block();
        }
        return 0;
    }
    

    运行上面的代码,打印结果为 age--20,height--180,同样,我们分析 生成的 C++ 代码

    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));
            age = 20;
            height = 180;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    • block 并没有捕获 任何全局变量

    七、总结

    一、block 的内存结构

    图片.png 图片.png

    二、block 本质

    • block 本质上也是一个 OC 对象,它内部也有一个 isa 指针
    • block 是封装了函数调用以及函数调用环境的 OC 对象

    二、block 的变量捕获

    为了保证 block 内部能正常访问外部的变量,block 有一个变量捕获机制

    • 如果变量的类型是局部变量,无论是 auto 还是 static 修饰,都会被捕获到 block 内部,但是 auto 变量是值传递,static 是指针传递。
    • 如果变量类型是全局变量,不会被 block 捕获到内部,直接使用。
    变量类型 是否能捕获到 block 内部 访问方式
    局部变量 auto 值传递
    局部变量 static 指针传递
    全局变量 直接访问

    为什么 block 要捕获局部变量的值呢,auto:自动变量,离开作用域就销毁

    三、block 的类型

    一、block 的对象特性

    我们知道 block 的本质就是OC 对象,所以OC对象的一些方法同样适用于 block

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
          void (^block)(void) = ^{
              NSLog(@"Hello");
          };
          
          NSLog(@"%@", [block class]);
          NSLog(@"%@", [[block class] superclass]);
          NSLog(@"%@", [[[block class] superclass] superclass]);
          NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
    
        }
        return 0;
    }
    
    2020-12-20 16:55:50.239327+0800 MyBlock[18424:5457366] __NSGlobalBlock__
    2020-12-20 16:55:50.239799+0800 MyBlock[18424:5457366] __NSGlobalBlock
    2020-12-20 16:55:50.239839+0800 MyBlock[18424:5457366] NSBlock
    2020-12-20 16:55:50.239871+0800 MyBlock[18424:5457366] NSObject
    

    得出继承关系为 NSGlobalBlock -> __NSGlobalBlock -> NSBlock ->NSObject

    二、block 的类型查询

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
          int a = 10;
          
          // 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存
          void (^block1)(void) = ^{
              NSLog(@"Hello");
          };
          
          int age = 10;
          void (^block2)(void) = ^{
              NSLog(@"Hello - %d", age);
          };
          
          NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
              NSLog(@"%d", age);
          } class]);
    
        }
        return 0;
    }
    
    在ARC 环境下:
    2020-12-20 17:00:31.886757+0800 MyBlock[18515:5460672] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
    在 MRC 环境下:
    2020-12-20 17:26:28.303551+0800 MyBlock[19030:5476820] __NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__
    

    ​ 在 MRC 环境下使用 copy

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
          int age = 10;
          void (^block2)(void) = [^{
              NSLog(@"Hello - %d", age);
          } copy];
        }
        return 0;
    }
    同样在 MRC 环境下,进行 copy 操作后打印结果和 ARC 环境下一样
    2020-12-20 17:27:42.096753+0800 MyBlock[19064:5478650]  __NSMallocBlock__ 
    

    如上,我们看打印结果得知 block 有 3 种类型,可以通过调用 class 方法或者 isa 指针查看具体类型,最终都是继承自 NSBlock 类型

    __NSGlobalBlock__(_NSConcreteGlobalBlock)没有访问 auto 变量
    __NSMallocBlock__(_NSConcreteMallocBlock)__NSStackBlock__ 调用了 copy
    __NSStackBlock__(_NSConcreteStackBlock)访问了 auto 变量,(在ARC环境下显示NSMallocBlock,在 MRC 环境下,显示 NSStackBlock

    每一种类型的 block 调用 copy 后的结果如下:

    Block 的类 副本源的配置存储域 复制效果
    _NSConcreteStackBlock 从栈复制到堆
    _NSConcreteGlobalBlock 程序的数据区域 什么也不做
    _NSConcreteMallocBlock 引用计数 + 1

    内存分布

    图片.png

    四、block 的 copy

    一、自动复制

    在 ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上

    1、block 作为函数返回值

    typedef void (^MyBlock)(void);
    
    MyBlock myblock() {
        return ^{
            NSLog(@"我被调用了");
        };
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            MyBlock block =  myblock();
       
            block();
    
        }
        return 0;
    }
    

    会打印出结果,如果没有进行 copy

    2、将 block复制给 __strong 指针时

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

    打印 [block class] 显示为NSMallocBlock ,证明是NSStackBlock copy 之后的类型。

    3、block 作为 Cocoa API 中方法名含有 usingBlock的方法参数时

    // 如 NSArray 里面的函数, 此时的 block 都是在堆上的,都是进行了 copy 的
    NSArray *arr = @[];
    [arr sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
                
    }];
    

    4、block 作为GCD API 的方法参数时

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
                
    });
    

    二、block属性的建议写法

    1、MRC 环境下

    • @property (copy, nonatomic) void (^block)(void);

    2、ARC 环境下

    • @property (strong, nonatomic) void (^block)(void);
    • @property (copy, nonatomic) void (^block)(void);

    三、对象类型的 auto 变量

    1、当 block内部访问了对象类型的 auto 变量时

    #import "MyPerson.h"
    typedef void (^MyBlock)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            MyBlock block;
            {
                MyPerson *person = [[MyPerson alloc] init];
                person.age = 18;
                
                block = ^{
                    NSLog(@"-----person.age===%d", person.age);
                };
            }
            
            NSLog(@"-----");
        }
        return 0;
    }
    

    转化C++ 代码

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      MyPerson *person;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    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*/);}
    
    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;
      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};
    
    • 如果 block 是在栈上
      • 将不会对 auto 变量产生强引用。
    • 如果 block 被拷贝到堆上
      • 会调用 block 内部的 copy 函数
      • copy 函数内部会调用 _Block_object_assign 函数
      • _Block_object_assign 函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretained)作出相对应的操作,形成强引用或者弱引用
    • 如果 block 从堆中移除
      • 会调用 block 内部的dispose 函数
      • dispose 函数内部会调用 _Block_object_dispose 函数
      • _Block_object_dispose 函数会自动释放引用的 auto 变量
    函数 调用时机
    copy 函数 栈上的 block 复制到堆时
    dispose 函数 堆上的 block 被废弃时

    把上面的 person 使用 __weak 修饰

    __weak MyPerson *weakPerson = person;
    block = ^{
        NSLog(@"-----person.age===%d", weakPerson.age);
    };
    

    使用 __weak 修饰时,在使用clang转换OC为C++代码时,可能会遇到以下问题

    /var/folders/f_/7ngz5gzx5sjgs4dlqrh7t58w0000gn/T/main-643d6e.mi:28880:28: error:
          cannot create __weak reference because the current deployment target does
          not support weak references
                __attribute__((objc_ownership(weak))) MyPerson *weakPerson = person;
                               ^
    1 error generated.
    

    针对 cannot create __weak reference in file using manual reference 这个问题,解决方案:支持ARC、指定运行时系统版本,比如增加 -fobjc-arc -fobjc-runtime=ios-8.0

    • 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;
      MyPerson *__weak weakPerson;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    五、__block 修饰符

    执行 下面的代码,显然会报错,从上面的代码中得知,block 中的 age 是block内部的,想要main 函数中的 age 是不可能的。

    typedef void (^MyBlock)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            int age = 14;
            
            MyBlock block = ^{
                age = 18;//报错 Variable is not assignable (missing __block type specifier)
                NSLog(@"%d", age);
            };
        }
        return 0;
    }
    

    如果 block 想要修改内部的变量,可以使用 static 或者 全局变量,但是,

    • 这种做法会一直占用内存的空间
    • 使用 static 修饰,会修改 block的类型,使用 static 修饰后,block 的类型变成了 ”NSGlobalBlock“,之前为”NSMallocBlock

    一、使用__block 修饰,修改变量

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            __block int age = 14;
            MyBlock block = ^{
                age = 18;
                NSLog(@"%d", age);
            };
            block();
                    NSLog(@"%@", [block class]);
        }
        return 0;
    }
    

    使用 __block 修饰后,block 的类型 依旧为 ”NSMallocBlock“,使用后发现 block 内部 有一个 __Block_byref_age_0 引用这 age这个指针,__Block_byref_age_0 里面有一个 age 变量,我们修改 age 其实就是修改 __Block_byref_age_0 里面的 age这个值。__forwarding 这个指针时指向自己的一个 指针。

    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_age_0 *age; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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
    
                (age->__forwarding->age) = 20; // 真正修改 age 的地方
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__7ngz5gzx5sjgs4dlqrh7t58w0000gn_T_main_f41d15_mi_0, (age->__forwarding->age));
            }
    

    二、__block 修饰符 原理

    • __block 可以用于解决 block 内部无法修改 auto 变量值得问题
    • __block 不能修饰全局变量、静态变量
    • 使用 ____block 修饰时,编译器会将 ____block 变量包装成一个对象。

    三、___block 的内存管理

    • 当 block 在栈上时,并不对 __block 变量产生强引用
    • 当 block 在堆上时
      • 会调用 block 内部的 copy 函数
      • copy 函数内部会调用 _Block_object_assign 函数
      • _Block_object_assign 函数会对 __block 变量形成强引用
    图片.png
    • 当block 从堆中移除时
      • 会调用 block 内部的dispose 函数
      • dispose 函数内部会调用 _Block_object_dispose 函数
      • _Block_object_dispose 函数会自动释放引用的 __block 变量
    图片.png

    四、__block 的 forwarding 指针

    [图片上传失败...(image-a5af69-1608559220453)]

    五、对象类型的 auto 变量、__block 变量

    • 当 block 在栈上时,对它们都不会产生强引用

    • 当 block 拷贝到堆上时,都会通过 copy 函数来处理它们

      • __block变量(假设变量名叫做a)
      • _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
    • 对象类型的 auto 变量(假设变量名叫做p)

      • _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
    • 当 block 从堆上移除时,都会通过 dispose 函数来释放它们

      • __block变量(假设变量名叫做a)
      • _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);
    • 对象类型的auto变量(假设变量名叫做p)

      • _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);
    • 对象 BLOCK_FIELD_IS_OBJECT
      __block 变量 BLOCK_FIELD_IS_BYREF

    六、被__block 修饰的对象类型

    • 当__block变量在栈上时,不会对指向的对象产生强引用
    • 当__block变量被copy到堆时
      • 会调用__block变量内部的copy函数
      • copy函数内部会调用_Block_object_assign函数
      • _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
    • 如果__block变量从堆上移除
      • 会调用__block变量内部的dispose函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose函数会自动释放指向的对象(release)

    六、循环引用

    一、什么是循环引用

    图片.png
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            MyPerson *p = [[MyPerson alloc] init];
            p.age = 18;
            p.block = ^{
                NSLog(@"age is %d", p.age);
            };
            
            NSLog(@"-------");
            
        }
        return 0;
    }
    
    // MyPerson
    
    typedef void (^MyBlock) (void);
    
    @interface MyPerson : NSObject
    
    @property (copy, nonatomic) MyBlock block;
    @property (assign, nonatomic) int age;
    
    @end
      
    

    上面的代码产生了循环引用,block 里面的代码 无法执行

    二、解决循环引用

    一、ARC 环境

    1、使用 __weak

    • 不会产生强引用,指向的对象销毁时,会自动让指针置为nil
    图片.png
    MyPerson *p = [[MyPerson alloc] init];
    __weak typeof(p) weakP = p;
    p.age = 18;
    p.block = ^{
        NSLog(@"age is %d", weakP.age);
    };
    

    2、使用 __unsafe_unretained

    • 不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
     MyPerson *p = [[MyPerson alloc] init];
    __unsafe_unretained typeof(p) weakP = p;
    p.age = 18;
    p.block = ^{
        NSLog(@"age is %d", weakP.age);
    };
    

    3、使用 __block

    • 使用 __block 必须 调用 block,并把对象置为 nil
    图片.png
    __block MyPerson *p = [[MyPerson alloc] init];
            
    p.age = 18;
    p.block = ^{
        NSLog(@"age is %d", p.age);
            p = nil;
        };
    p.block();
    

    二、MRC 环境

    由于 MRC 环境不支持 __weak,所以只有两种情况

    1、__unsafe_unretained

    • 和 ARC 一样

    2、__block

    • 不需要置为 nil 和 手动调用 block

    相关文章

      网友评论

        本文标题:iOS 开发 Block 原理分析

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