浅谈iOS Block

4,490 阅读4分钟

概述

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

定义

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

全局 Block:全局 Block 是指它的作用域在整个应用程序中都可以访问,因为它存储在全局数据区,并且系统会在应用程序启动时自动创建。

Block:栈 Block 是指它的作用域只在当前函数内部,当它超出作用域时,它会自动被销毁。栈 Block 只能访问当前函数的变量,不能访问全局变量和静态变量。

Block:堆 Block 是指它的作用域在整个应用程序中都可以访问,因为它被存储在堆内存中。当堆 Block 被赋值给一个变量时,该变量会对其进行引用计数,并且在没有任何引用时,堆 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有个变量捕获机制

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是同一地址

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);
}