AI智能
改变未来

iOS:底层原理之 Block


1. Block 本质

block 本质上也是一个 OC 对象,它内部也有个 isa 指针
block 是封装了函数调用以及函数调用环境的 OC 对象
block 的底层结构如下图所示

源码解析:

struct __GSBlock__load_block_impl_0 {struct __block_impl impl;____①____struct __GSBlock__load_block_desc_0* Desc;____②____int age; // 外部的变量__GSBlock__load_block_impl_0(void *fp, struct __GSBlock__load_block_desc_0 *desc, int _age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};

========================================
① __block_impl:

struct __block_impl {void *isa;  // 内部有isa指针int Flags;int Reserved;void *FuncPtr; //存放代码块中内容的函数的地址};

========================================
② __GSBlock__load_block_desc_0:// 描述Block

static struct __GSBlock__load_block_desc_0 {size_t reserved;size_t Block_size; // 占多少内存}

========================================

__GSBlock__load_block_func_0: // Block代码块内的函数static void __GSBlock__load_block_func_0(struct __GSBlock__load_block_impl_0 *__cself, int a, int b) {int age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_w290bty14td49582fr3nz9hh0000gn_T_GSBlock_f179b1_mi_0,age);

block的变量捕获(capture) 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制。

auto:自动变量,离开作用域就销毁。
全局变量不会捕获,因为全局在任何地方都可以访问,不需要捕获。
static 类型的变量被捕捉进 block 是 *。

  • 例1:static int age = 10;
  • 在^{ nslog(@“%@”,age)};
  • 在 block 内部是 int *age。
  • 例2:
      Person *person = [Person alloc] init];
    • 捕获到 block 中是 Person **person。

    方法的默认参数:

    • -(void)test;-(void)test:(Person *self, SEL _cmd);
    • -(void)test:(id *self, SEL _cmd);
  • -(void)testWithName:(NSString *name);
      -(void)test:(Person *self, SEL _cmd, NSString *name);
    • -(void)test:(id *self, SEL _cmd, NSString *name);

    self 为局部变量,所有的参数都是局部变量。

    2. Block分类


    block 有 3种类型,可以通过调用 class 方法或者 isa 指针查看具体类型,最终都是继承自 NSBlock 类型
    NSGlobalBlock ( _NSConcreteGlobalBlock )全局 block 没有访问 auto变量
    NSStackBlock ( _NSConcreteStackBlock )栈 block 访问了 auto 变量
    NSMallocBlock ( _NSConcreteMallocBlock )堆 block __NSStackBlock__调用了copy

    堆:

    动态分配内存,需要申请内存,需要管理内存。

    • malloc(20) 分配内存,free( ) 释放内存。
    • [NSObject alloc]

    栈:

    存放局部变量,静态变量,特点是自动分配内存,自动销毁。

    • 程序数据段:全局变量。
    • 程序区域和数据区域由编译器控制。

    NSGlobalBlock:没有访问 auto 变量 ,调用 copy 依然是 NSGlobalBlock。
    NSStackBlock:访问了 auto 变量 ,调用 copy 是 NSMallocBlock类型。
    NSMallocBlock:NSStackBlock 调用 copy 就会成为 NSMallocBlock ,再调用 copy 依然是 NSMallocBlock,引用计数增加1。

    每一种 Block 调用 copy 后的结果如下:

    ARC环境下,stackBlock 会默认进行copy操作变为 mallocBlock。

    栈空间中的 block 是不会去持有外部对象的,如果是堆空间中的 block 是可以持有外部对象的。

    3. Block copy

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

    • block 作为函数返回值时。
    • 将 block 赋值给 __strong 指针时。
    • block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时。
    NSArray *array = @[];[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {}];
    • block 作为 GCD API 的方法参数时。
    static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{});dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});

    对象类型的 auto 变量

    栈空间中的 block 是不会去持有外部对象的,如果是堆空间中的 block 是可以持有外部对象的。

    __weak 问题解决
    在使用 clang 转换 OC 为 C++代码时,可能会遇到一下问题:
    cannot create __weak reference in file using manual reference

    解决方案:支持ARC、指定运行时系统版本,比如:
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

    • 当 block 内部访问了对象类型的 auto 变量时如果 block 是在栈上,将不会对 auto 变量产生强引用。
  • 如果 block 被拷贝到堆上
      会调用 block 内部的 copy 函数
    • copy 函数内部会调用 _Block_object_assign 函数
    • _Block_object_assign 函数会根据 auto 变量的修饰符( __strong、__weak、_unsafe_unretained )做出相应的操作,类似于retain ( 形成强引用、弱引用 )
  • 如果 block 从堆上移除
      会调用 block 内部的 dispose 函数。
    • dispose 函数内部会调用 _Block_object_dispose 函数。
    • _Block_object_dispose 函数会自动释放引用的 auto 变量,类似于release。

    copy 函数 与 dispose 函数的调用时机

    MRC 下 block 属性的建议写法

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

    ARC 下 block 属性的建议写法

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

    4. __Block修饰符

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

    __forwarding:指向自身的指针,存放的是__Block_byref_age_0 自己的内存地址。

    5. __Block 内存管理



    __block 修饰的变量是强引用。

    • 强指针:默认情况下,所有的指针都是强指针。我们也可以用__strong修饰。

    • 弱指针:用__weak修饰的指针,就是弱指针。

    • 当 block 在栈上时,并不会对 __block 变量产生强引用。

    • 当 block 被 copy 到堆时,会调用 block 内部的 copy 函数,copy 函数内部会调 _Block_object_assign 函数,_Block_object_assign 函数会对 __block 变量形成强引用(retain)。

    • 当 block 从堆中移除时,会调用 block 内部的 dispose 函数,dispose 函数内部会调用_Block_object_dispose 函数,_Block_object_dispose 函数会自动释放引用的 __block 变量(release)

    6. Block 循环引用

    ARC:用__weak、__unsafe_unretained解决。
    MRC:用__Block、__unsafe_unretained解决。

    • 用 __weak、__unsafe_unretained 解决__weak typeof(self)weakSelf = self; //__typeof(self)在Xcode 6已不再提示,提倡使用typeof(self)
    • typeof(*) 返回 * 的类型。
    • __weak:不会产生强引用。指向的对象销毁时,会自动让指针置为nil。
    • __unsafe_unretained:不会产生强引用,不安全。指向的对象销毁时,指针存储的地址值不变。
  • 用__block解决(必须要调用block)
      此方案的弊端是,必须要执行 block( ),block 体内 需要做吧 weakself 置为 nil 的打破循环的操作。
  • __strong typeof(weakSelf)strongSelf = weakSelf;
  • 7. 对象类型的 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/);

    8. 被 __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_objc_dispose 函数
    • _Block_objc_dispose 函数会自动释放引用的 __block 变量(release)
    • 点赞
    • 收藏
    • 分享
    • 文章举报

    GS-NICE发布了177 篇原创文章 · 获赞 0 · 访问量 2707私信关注

  • 赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » iOS:底层原理之 Block