概述

在 iOS 开发中,Block 是一种非常重要的编程概念,它能够将代码块作为参数传递和回来,从而便利实现回调、异步任务和代码封装等功能。在本文中,我们将深入探讨 Block 的技术细节,包括 Block 的界说、分类、内存管理、循环引证及其处理方案等

界说

Block 是一种由苹果引入的 Objective-C 扩展,是一种代码块目标,能够像普通目标相同存储到变量中,也能够作为参数传递和回来。其本质是一个封装函数调用以及函数调用环境的OC目标,其内部也有个isa指针在 Objective-C 中,Block 有三种类型:大局 Block、栈 Block 和堆 Block

大局 Block:大局 Block 是指它的效果域在整个应用程序中都能够拜访,因为它存储在大局数据区,而且体系会在应用程序启动时主动创立。

Block:栈 Block 是指它的效果域只在当时函数内部,当它超出效果域时,它会主动被毁掉。栈 Block 只能拜访当时函数的变量,不能拜访大局变量和静态变量

Block:堆 Block 是指它的效果域在整个应用程序中都能够拜访,因为它被存储在堆内存中。当堆 Block 被赋值给一个变量时,该变量会对其进行引证计数,而且在没有任何引证时,堆 Block 会主动被毁掉。

浅谈iOS Block

源码分析

浅谈iOS Block

struct __block_impl {
  void *isa;     // isa指针,指向一个类目标,有三种类型:
  int Flags;     // block 的负载信息(引证计数和类型信息),按位存储
  int Reserved;  // 保存变量
  void *FuncPtr; // 一个指针,指向Block执行时调用的函数,也便是Block需求执行的代码块
};
struct block_descriptor {
   size_t reserved;   // Block版别升级所需的预留区空间,在这里为0。
  size_t Block_size; // Block巨细(sizeof(struct __blockTest_block_impl_0))
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct block_descriptor* descriptor;
  ...
  // 构造函数
  __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;
  }
};

变量捕获(capture)

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

浅谈iOS Block

block内部拜访目标类型的auto变量

假如block是在栈上,将不会对auto变量发生强引证block拷贝到堆上的进程

  1. 调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数

  2. _Block_object_assign函数会依据auto变量的润饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,构成强引证(retain)或者弱引证

    // __block变量 变量名为a _Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);

    // __block目标类型的auto变量 变量名为p _Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);

block从堆上移除的进程

  1. 调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数
  2. _Block_object_dispose函数会主动开释引证的auto变量(release)

定论如下

  1. 假如block在栈空间,不论外部变量是强引证还是弱引证,都不会对变量发生强引证
  2. 假如block在堆空间,假如外部强引证,block内部也是强引证;假如外部弱引证,block内部也是弱引证

__block润饰符

界说:__block会将变量包装成一个OC目标

效果:用于处理block内部无法修正auto变量值的问题

__block int age = 10
struct __Block_byref_age_0 {
  void *__isa;
  __Block_byref_age_0 *__forwarding;//age的地址
 int __flags;
 int __size;
 int age;//age 的值
};         

注意:

  1. __block不能润饰大局变量、静态变量
  2. __Block_byref_age_0结构体内部地址和外部变量age是同一地址

浅谈iOS Block

block内部拜访__block润饰的目标类型

假如block是在栈上,将不会对指向的目标发生强引证当__block变量被copy到堆时

  1. 会调用__block变量内部的copy函数
  2. copy函数内部会调用_Block_object_assign函数
  3. _Block_object_assign函数会依据所指向目标的润饰符(__strong__weak__unsafe_unretained)做出相应的操作,构成强引证(retain)或者弱引证(注意:这里仅限于ARC时会retainMRC时不会retain

假如__block变量从堆上移除

  1. 会调用__block变量内部的dispose函数
  2. dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会主动开释指向的目标(release

处理循环引证问题 – ARC

1、用__weak__unsafe_unretained处理

__weak typeof(self) weakSelf = self;
// __unsafe_unretained id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
}

2、用__block处理(必需要调用block,而且置目标为nil)

__block id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
    weakSelf = nil;
}
self.block();       

处理循环引证问题 – MRC

1、用__weak、__unsafe_unretained处理

// 因为MRC环境下,不支持__weak
__unsafe_unretained id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
}          

2、用__block处理

__block id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
}