Block小知识点

Block小知识点

前言

Block是开发过程中常用的便捷回调方式,下面我们就做下总结。

Block介绍

什么是Block

  • Block是将函数及其执行上下文封装起来的对象

源码解析

  • 使用【clang -rewrite-objc file.m】查看编译之后的文件内容

什么是Block调用

  • Block调用即是函数的调用。

以下面代码为例:

- (void)show
{
    int num = 10;
    int(^Block)(int) = ^int(int factor) {
        return num * factor;
    };
    Block(2);
}

查看源码:

// Block对应的定义
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __DCBlock__show_block_impl_0 {
  struct __block_impl impl;
  struct __DCBlock__show_block_desc_0* Desc;
  int num;
  __DCBlock__show_block_impl_0(void *fp, struct __DCBlock__show_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 封装了block执行逻辑的函数
static int __DCBlock__show_block_func_0(struct __DCBlock__show_block_impl_0 *__cself, int factor) {
  int num = __cself->num; // bound by copy

        return num * factor;
    }

// block的相关描述
static struct __DCBlock__show_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __DCBlock__show_block_desc_0_DATA = { 0, sizeof(struct __DCBlock__show_block_impl_0)};

static void _I_DCBlock_show(DCBlock * self, SEL _cmd) {
    int num = 10;
    
    /*
    int(^Block)(int) = ^int(int factor) {
        return num * factor;
    };
    */
    int(*Block)(int) = ((int (*)(int))&__DCBlock__show_block_impl_0((void *)__DCBlock__show_block_func_0, &__DCBlock__show_block_desc_0_DATA, num));
    
    /*
    Block(2);
    */
    ((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);
}
  • isa:isa指针,Block是对象的标志
  • FuncPtr:函数指针,指向调用函数的地址

截获变量

根据数据类型,分为以下几种:

  • 对于基本数据类型的局部变量截获其值
  • 对于对象类型的局部变量连同所有权修饰符一起截获
  • 指针形式截获局部静态变量
  • 不截获全局变量、静态全局变量

  • 作用域:局部
变量类型 是否捕获到block内部 访问方式
基本数据类型 值传递
对象类型 值传递
静态变量 指针传递
  • 作用域:全局
变量类型 是否捕获到block内部 访问方式
变量、静态变量 直接访问

  • 为什么局部变量需要捕获? 考虑作用域的问题,需要跨函数访问,就需要捕获

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

  • block里访问self是否会捕获? 会,self是当调用block函数的参数,参数是局部变量,self指向调用者

  • block里访问成员变量是否会捕获 会,成员变量的访问其实是self->xx,先捕获self,再通过self访问里面的成员变量

__block修饰符

  • 一般情况下,对被截获变量进行赋值操作需添加__block修饰符
  • 作用:__block修饰的变量会变成对象
__block int num = 4;

num = 6; // num.__forwarding->num=6

Block的内存管理

对于Block本身有三种类型:

  • _NSConcreteGlobalBlock:全局
  • _NSConcreteStackBlock:栈
  • _NSConcreteMallocBlock:堆

Block的Copy操作

Block类别 Copy结果
_NSConcreteGlobalBlock 数据区 什么也不做
_NSConcreteStackBlock
_NSConcreteMallocBlock 增加引用计数
  • 变量作用域结束后
    • 栈上的创建的Block和__block变量会被销毁
    • 当栈上的Block销毁后,堆上的Block和__block变量依然存在
  • __forwarding指针是用来干什么的
  • 栈上__block变量的Copy

只是在栈的Block,__block变量的__forwarding指向的是栈上__block变量本身

对栈的Block进行copy操作后,栈上__block变量的__forwarding指向的是堆上__block变量;堆上__block变量的__forwarding指向的是堆上__block变量本身

  • __forwarding存在的意义 不论在任何内存位置,都可以顺利的访问同一个__block变量。

Block的循环引用

循环引用的实质:多个对象相互之间有强引用,不能释放让系统回收。

  • 在ARC下:
方法 比较
__weak 不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained 不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__block 必须把引用对象置位nil,并且要调用该block
  • 在MRC下:
方法 比较
__unsafe_unretained 不会产生强引用
__block 不会产生强引用

总结

  • 什么是Block?(定义)
  • 为什么Block会产生循环引用?
    • 自循环引用:如当前Block对当前对象的某一成员变量进行截获,则会对成员变量有一个强引用;而当前对象对Block也有一个强引用,这样就会产生一个自循环引用方式的循环引用问题;我们可以声明对象为__weak变量,来避免循环引用
    • 大环循环引用:如果定义了一个__block说明符,在ARC会产生循环引用,可以通过断环的方式来解决循环引用,不过有一个弊端,如果block一直无法得到调用的话,这个循环问题是无法得到解决的
  • 怎样理解Block截获变量的特性?(从变量作用域和类型考虑)
  • 你都遇到过哪些循环引用?你又是怎样解决的?
    • Block
    • NSTimer

参考文章

最后

如果对大家有帮助,请github上follow和star,本文发布在戴超的技术博客,转载请注明出处

Loading Disqus comments...
Table of Contents