iOS:底层原理之 Block

1. Block 本质

block 本质上也是一个 OC 对象,它内部也有个 isa 指针
block 是封装了函数调用以及函数调用环境的 OC 对象
block 的底层结构如下图所示
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 copy
        NSLog((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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值