探求系列已发布文章列表,有兴趣的同学能够翻阅一下:

第一篇 | iOS 特点 @property 详细探求

第二篇 | iOS 深入了解 Block 运用及原理

第三篇 | iOS 类别 Category 和扩展 Extension 及相关目标详解

第四篇 | iOS 常用锁 NSLock ,@synchronized 等的底层完成详解

第五篇 | Equality 详细探求

——- 正文开端 ——-

引言

在 iOS 日常开发中,Block 的运用频率是比较多的,咱们不会每天都做启动优化,也不会每天都做性能优化,但有可能每天都会用到 Block 。本文就侧重介绍一下 Block 在日常开发中值得咱们重视的技能点,大家一起学习。


代码标准

// 定义一个 Block
typedef returnType (^BlockName)(parameterA, parameterB, ...);
eg: typedef void (^RequestResult)(BOOL result);
// 实例
^{
    NSLog(@"This is a block");
 }

实质

Block 实质上是一个 Objective-C 的目标,它内部也有一个 isa 指针,它是一个封装了函数及函数调用环境的 Objective-C 目标,能够增加到 NSArrayNSDictionary 等调集中,它是根据 C 语言及运行时特性,有点类似标准的 C 函数。但除了可履行代码以外,另外包括了变量同堆或栈的主动绑定。


常用介绍

  • Block 的类型:
  1. NSGlobalBlock
void (^exampleBlock)(void) = ^{
    // block
};
NSLog(@"exampleBlock is: %@",[exampleBlock class]); 

打印日志:exampleBlock is: __NSGlobalBlock__

假如一个 block 没有拜访外部局部变量,或许拜访的是大局变量,或许静态局部变量,此刻的 block 便是一个大局 block ,而且数据存储在大局区。

  1. NSStackBlock
int temp = 100;
void (^exampleBlock)(void) = ^{
    // block
    NSLog(@"exampleBlock is: %d", temp);
};
NSLog(@"exampleBlock is: %@",[exampleBlock class]);

打印日志:exampleBlock is: __NSMallocBlock__??? 不是说好的 __NSStackBlock__ 的吗?为什么打印的是__NSMallocBlock__ 呢?这里是由于咱们运用了 ARC ,Xcode 默认帮咱们做了很多事情。

咱们能够去 Build Settings 里边,找到 Objective-C Automatic Reference Counting ,并将其设置为 No ,然后再 Run 一次代码。你会看到打印日志是:exampleBlock is: __NSStackBlock__

假如 block 拜访了外部局部变量,此刻的 block 便是一个栈 block ,而且存储在栈区。由于栈区的开释是由系统控制,因而栈中的代码在效果域完毕之后内存就会销毁,假如此刻再调用 block 就会发生问题,( 注: 此代码运行在 MRC 下)如:

void (^simpleBlock)(void);
void callFunc() {
    int age = 10;
    simpleBlock = ^{
        NSLog(@"simpleBlock-----%d", age);
    };
}
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        callFunc();
        simpleBlock();
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return 0;
}

打印日志:simpleBlock--------41044160

  1. NSMallocBlock

当一个 __NSStackBlock__ 类型 Block 做 copy 操作后就会将这个 Block 从栈上复制到堆上,而堆上的这个 Block 类型便是 __NSMallocBlock__ 类型。在 ARC 环境下,编译器会根据状况,主动将 Block 从栈上 copy 到堆上。具领会进行 copy 的状况有如下 4 种:

  • block 作为函数的返回值时;
  • block 赋值给 __strong 指针,或许赋值给 block 类型的成员变量时;
  • block 作为 Cocoa API 中办法名含有 usingBlock 的办法参数时;
  • block 作为 GCD API 的办法参数时;

  • __block 的效果

简略来说,__block 效果是答应 block 内部拜访和修正外部变量,在 ARC 环境下还能够用来避免循环引证;

__block int age = 10;
void (^exampleBlock)(void) = ^{
    // block
    NSLog(@"1.age is: %d", age);
    age = 16;
    NSLog(@"2.age is: %d", age);
};
exampleBlock();
NSLog(@"3.age is: %d", age);

__block 首要用来解决 block 内部无法修正 auto 变量值的问题,为什么加上 __block 润饰之后,auto 变量值就能修正了呢?

这是由于,加上 __block 润饰之后,编译器会将 __block 变量包装成一个结构体 __Block_byref_age_0 ,结构体内部 *__forwarding 是指向自身的指针,而且结构体内部还存储着外部 auto 变量。

struct __Block_byref_val_0 {
    void *__isa; // isa指针
    __Block_byref_val_0 *__forwarding; 
    int __flags;
    int __size; // Block结构体大小
    int age; // 捕获到的变量
}

iOS 探究 | 第二篇 深入理解 Block 使用及原理

从上图能够看到,假如 block 是在栈上,那么这个 __forwarding 指针便是指向它自己,当这个 block 从栈上复制到堆上后,栈上的 __forwarding 指针指向的是复制到堆上的 __block 结构体。堆上的 __block 结构体中的 __forwarding 指向的仍是它自己,即 age->__forwarding 获取到堆上的 __block 结构体,age->__forwarding->age 会把堆上的 age 赋值为 16 。因而不管是栈上仍是堆上的 __block 结构体,终究运用到的都是堆上的 __block 结构体里边的数据。


  • __weak 的效果

简略来说是为了避免循环引证。

iOS 探究 | 第二篇 深入理解 Block 使用及原理

self 自身会对 block 进行强引证,block 也会对 self 构成强引证,这样就会形成循环引证的问题。咱们能够通过运用 __weak 打破循环,使 block 目标对 self 弱引证。

此刻咱们留意,由于 blockself 的引证为 weak 引证,因而有可能在履行 block 时,self 目标自身已经开释,那么咱们怎么确保 self 目标不在 block 内部开释呢?这就引出了下面__strong 的效果。


  • __strong 的效果 简略来说,是避免 Block 内部引证的外部 weak 变量被提前开释,进而在 Block 内部无法获取 weak 变量以持续运用的状况;
__weak __typeof(self) weakSelf = self;
void (^exampleBlock)(void) = ^{
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    [strongSelf exampleFunc];
};

这样就确保了在 block 效果域完毕之前,block 内部都持有一个 strongSelf 目标可供运用。

但是,即便如此,仍然有一个场景,便是履行 __strong __typeof(weakSelf) strongSelf = weakSelf; 之前,weakSelf 目标已经开释,这时假如给 self 目标发送音讯,这没有问题,Objective-C 的音讯发送机制答应咱们给一个 nil 目标发送音讯,这不会出现问题。

但假如有额外的一些操作,比如说将 self 增加到数组,这时由于 selfnil,程序就会 Crash。

咱们能够增加一层安全维护来解决这个问题,如:

__weak __typeof(self) weakSelf = self;
void (^exampleBlock)(void) = ^{
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        // Add operation here
    }
};

拓宽知识

  • 思考题

Block 内修正外部 NSMutableStringNSMutableArrayNSMutableDictionary 目标,是否需求增加 __block 润饰?

NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
[mutableArray addObject:@"1"];
void (^exampleBlock)(void) = ^{
    // block
    [mutableArray addObject:@"2"];
};
exampleBlock();
NSLog(@"mutableArray: %@", mutableArray);

打印日志:

mutableArray: ( 1, 2 )

答案是:不需求。由于在 block 内部,咱们仅仅运用了目标 mutableArray 的内存地址,往其中增加内容。并没有修正其内存地址,因而不需求运用 __block 也能够正确履行。当咱们仅仅运用局部变量的内存地址,而不是对其内存地址进行修正时,咱们无需对其增加 __block ,假如增加了 __block 系统会主动创建相应的结构体,这种状况冗余且低效。

  • Block 数据结构

Block 内部数据结构图如下:

iOS 探究 | 第二篇 深入理解 Block 使用及原理

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

Block_layout 结构体成员含义如下:

isa: 指向所属类的指针,也便是 block 的类型

flags: 按 bit 位表明一些 block 的附加信息,比如判别 block 类型、判别 block 引证计数、判别 block 是否需求履行辅佐函数等;

reserved: 保存变量;

invoke: block 函数指针,指向详细的 block 完成的函数调用地址,block 内部的履行代码都在这个函数中;

descriptor: 结构体 Block_descriptor,block 的附加描述信息,包括 copy/dispose 函数,block 的大小,保存变量;

variables: 由于 block 有闭包性,所以能够拜访 block 外部的局部变量。这些 variables 便是复制到结构体中的外部局部变量或变量的地址;

Block_descriptor 结构体成员含义如下:

reserved: 保存变量;

size: block 的大小;

copy: 函数用于捕获变量并持有引证;

dispose: 析构函数,用来开释捕获的资源;


总结

运用 Block 过程中需求咱们重视的重点有 4 个:

  1. block 的三种类型;
  2. block 避免引起循环引证;
  3. block 对 auto 变量的 copy 操作;
  4. __block、__weak、__strong 的效果;

以上便是本文对 Block 的相关知识点的介绍,感谢阅览。


参考资料:

Working with Blocks


关于技能组

iOS 技能组首要用来学习、共享日常开发中运用到的技能,一起坚持学习,坚持前进。文章仓库在这里:github.com/minhechen/i… 微信公众号:iOS技能组,欢迎联系进群学习沟通,感谢阅览。

iOS 探究 | 第二篇 深入理解 Block 使用及原理