美文网首页
block详解

block详解

作者: myzhing | 来源:发表于2016-12-21 22:06 被阅读0次

毫不夸张地说,block让objc这门语言变得更有魅力,它就是在其它语言中常见的闭包的概念。
在block之前,objc重度依赖delegate来完成一些用户行为,是block让我们开发者多了一个
更简单的选择,本文就《objc高级编程》来总结一下block的实现原理。

什么是block

用书中的话来概括block,就是:带自动变量的匿名函数。block可以有很多理解,它可以被
看作一个代码块,这个代码块即是匿名函数的函数体,它和普通的函数一样可以有参数,可
以有返回值,由此可见,block在使用上除了没有函数名之外和普通的函数没有区别,但是在
这里要注意区分函数和方法的区别。其带有自动变量,即是它可以保持block声明作用域内变
量的值,无论把这个block拿到哪里去执行,这些变量的值都为你保存着。这些都是显然的闭
包的概念,通过block来深入理解objc(甚至是其它语言)的闭包未尝不是一个好的方法。

虽然使用和理解block给接触objc不久的开发者带来了困扰,但是block的语法却很简单,通
过下面三个实例概括一下block的使用:

  1. 定义:
typedef void (^RFAudioBasicBlock) (void);
typedef void (^RFAudioSuccessBlock) (BOOL flag);
typedef void (^RFAudioSuccessDetailBlock) (BOOL flag, NSURL *url, NSTimeInterval duration);
typedef void (^RFAudioSuccessURLBlock) (BOOL flag, NSURL *url);
  1. 作为参数:
- (void)playWithURL:(NSURL *)url finishedBlock:(RFAudioSuccessDetailBlock)block;
  1. 使用:
[[RFAudioManager defaultManager] playWithURL:url finishedBlock:^(BOOL flag, NSURL *url) {
NSLog(@"播放结束:%@", url);
}];

block的本质与实现

直接通过代码来分析block的实现比较有说服力:

  • objc源码
int main {
void (^blk)(void) = ^{printf("Block/n");};
blk();
return 0;
}
  • 编译之后的代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself){
printf("Block/n");
}

static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};

int main() {
void (^blk)(void) = (void (*)(void))&__main_block_impl_0(
(void *)__main_block_func_0, &__main_block_desc_0_DATA
);

( (void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr )(
(struct __block_impl *)blk
);
}

这段代码是我照着PDF文档手打出来的,等我打出来之后也就明白了这段代码的意思,不信你
可以试试。由源代码我们可以清晰地看到:

  1. Block就是__main_block_impl_0结构体指针类型的变量blk,即栈上生成的__main_block_impl_0
    结构体实例(对象)。
  2. __block_impl即为Block的类信息,理解这里的类信息需要借助与objc对象与类之间的关系。
    里面的isa变量我们很熟悉,标识了__block_impl的元类型NSConcreteStackBlock(其实标识了
    该对象所处的位置在stack中)。
  3. Block如何截获自动变量,再简单不过了,将自动变量保存在__main_block_impl_0对象中即可。

由此,我们可以下结论:Block就是一个Objective-c对象。

__block说明符

Block能够轻松地截获自动变量的值,并在其调用内使用它,然而却不能更改它,如果需要在
Block内部更改外部的变量值,需要加上__block修饰符。咋一看,这个要求很简单,在Block
对象中保存变量的指针不就完事了吗?然而实际情况却不是,在Block调用的时候,自动变量
可能早已超出其作用域被销毁了,也就不能通过指针来访问自动变量了。看代码:

  • objc代码
__block int val = 10;

*编译之后的代码

struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int flags;
int __size;
int val;
};

int main() {
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
}
}

由上,我们不难看出所有被__block修饰的变量均会变成一个结构体(即对象),所以在block
中改变其值并不难,然而即便是对象也并没有解决超出作用域被收回的问题,下面我们来探讨
Block及__block变量超出作用域而存在的道理。

Block存储域

分解上述的isa指针,Block分为三种类型:

  • _NSConcreteGlobalBlock
    通过名称就可以判断出此Block为全局的,处于程序的数据区,生成此种类型的Block有
    两种情况:
  1. 记述全局变量的地方有Block语法
  2. Block语法的表达式中不使用应该截获的自动变量时
    此种类型的Block就如同全局变量一样,在全局环境下通过指针安全地使用。
  • _NSConcreteStackBlock
    当Block截获自动变量的时候,其所处的区域在于Stack中。设置在栈上的Block,如果其
    所属的作用域结束,该Block即会被废弃;同理__block类型的变量也配置在栈上,如果
    其所属的变量作用域结束,该__block变量也会被废弃。

  • _NSConcreteMallocBlock
    大部分Block的结构体实例均是在栈上,如果在实例作用域结束的时候,要保留这个Block
    就需要将其复制到堆上,如果堆上的Block引用也过期,就会被收回。下面探讨将Block
    从栈复制到堆的情形。

typedef int (^blk_t)(int);

blk_t func(int rate) {
return ^(int count){return rate * count;};
}

该Block即是属于Stack上的实例变量,当函数返回的时候,Block也相继被废弃,但是此Block
作为函数的返回值,编译器会自动生成将Block复制到堆上的代码。一般情况下,编译器会自行
判断需要将Block复制到堆上的情形,但是也存在编译器不能判断的场景:

  • 向方法或者函数的参数中传递Block时需要手动复制Block。这就是很好地解释了@property
    后面的类型为Block时需要申明copy,当然申明strong/retain也不会出现问题,因为其对于
    Block的默认行为也是copy。

还有两个我们不需要copy的场景:

  • Cocoa框架的方法且方法名中含有usingBlock等时;
  • 使用GCD的API时

至此我们可以稍微总结一下Block被复制的情形:

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符的id类型的类或者Block类型的成员变量时
  • 方法名中含有usingBlock的cocoa框架方法或者使用GCD时

__block变量作用域

由上文可知,__block变量被转化为结构体实例,其实就是对象,其随着持有它的Block一起被
复制到堆中或者被废弃。当其被复制之后,不管是栈中还是堆中,均可以访问到该对象,并对其
值进行的更改均有效,这是如何做到的呢?

在上文__Block_byref_val_0结构体中有个_forwarding字段,其即为__block变量对象的
指针,栈中对象的_forwarding实际上指向了堆中对象的地址,所以不管是在栈中还是
在堆中访问这个变量均指向了同一个地方即堆中的对象。所以在Block内外均能够对其
进行赋值和访问。

截获对象

有一种情况,如果非__block变量指向的是对象,那么我们依然得克服变量作用域失效而导致
对象被释放的困境,以达到Block截获自动变量的目的。这个问题也似乎很好解决,只需要在
Block中带入该自动变量的修饰符(strong/weak),于是Block对象中保持了对自动变量指向
的对象的强引用,那么目标对象即不会被释放,这样就达到了Block持有这个对象的目的。
实际上苹果也是这么做的,只是这个修饰符在Block被copy的时候才会生效,也即要求Block
对象处于堆中,如果该Block并没有被copy,那么自动变量的修饰符会丢失,自动变量所指
向的对象也会因为超出作用域而被释放。

如果__block变量指向的是对象,情况与非block变量类似。

最后

提醒:使用block是循环引用的高发区,所以要小心杜绝循环引用引起的内存泄露。

相关文章

  • iOS Block实例

    iOS之Block详解:Block详解 ViewController.h(ARC) ViewController....

  • Block - block简单的使用

    参考文档 iOS Block详解 一、忘记block格式? 样例一.png 样例二.png 二、Block的定义 ...

  • SDWebImage4.0源码探究(二)具体代码拓展

    代码一 知识点:block参考:iOS中block的详解weakSelf、strongSelf <转自唐巧>Blo...

  • 关于block的理解

    block的类型详解 关于block的知识,在网络上的资料那是相当的多。不过这里还是想来谈谈自己对block的理解...

  • iOS 题目详解 部分三

    主要讲解Block 内部使用strongSelf的理由和用法 iOS 题目详解 部分一iOS 题目详解 部分二...

  • Block详解

    1.Block定义及使用 首先看下Block的定义和使用。 实际使用方法 Block看起来比较复杂,在OC中实际上...

  • Block详解

    1️⃣Block的修饰 ARC情况下( ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Ref...

  • block详解

    __weak typeof(self) weakSelf = self;self.handler = ^{type...

  • Block详解

    __block说明符 Block只能保存局部变量瞬间的值,所以当我们尝试修改截获的自动变量值,就会报错。例如: 该...

  • Block详解

    block的定义,调用等就不介绍了,自行去查资料。 本文介绍内容: 1.block的底层数据结构2.block的类...

网友评论

      本文标题:block详解

      本文链接:https://www.haomeiwen.com/subject/qxccvttx.html