Blocks 是带有自动变量(局部变量)的匿名函数,

2019-09-11 20:51栏目:大奖888官网登录
TAG:

consumed这个单词我并不能给出很准确的翻译,在这篇文章中,我把Consumed parameters称为耗用参数,它在OC中有着独特的应用场景。

1 Blocks 概要

基础部分


在 parameters这一个小模块有很大的疑问,因此在网上查了一些资料,虽然有了一个大概的了解,但是还是有一些不太清楚的地方。

1.1 什么是 Blocks

Blocks是 C 语言的扩充功能,Blocks 是带有自动变量(局部变量)的匿名函数。

带有自动变量值的匿名函数  分为 “ 匿名函数” 和 “带有自动变量值”

什么是 “带有自动变量值”: 

www.88pt88.com 1

带有自动变量i值的匿名函数。Blocks保持自动变量的值。

一 重要概念:

我们先来看一个例子,这个例子来源于上边的那份文档:

2 Blocks 模式

1 闭包

可以理解成一个函数,可以读取其他函数内部变量的函数。
而block就是object-c对闭包的实现。
void foo(__attribute((ns_consumed)) id x);-  foo:  __attribute((ns_consumed)) x;

2.1 Block 语法

C 语言函数定义相比,有两点不同 :1:没有函数名。 2:带有 ^

第一种:

www.88pt88.com 2

^ int (int count) { return count + 1;};

第二种: 

www.88pt88.com 3

省略返回值类型时:Block 语法将按照 return 语句的类型,返回返回值。

第三种: 

www.88pt88.com 4

2 block

block可以把他看做是一个函数,也可以看做是一个变量,只不过他是存储的是代码段。

我们可以用__attribute((ns_consumed))来修饰某个函数或者方法的参数,但这只是表面上的看法,实际上,它并不是只作用于它修饰的某个参数,而是作用于整个函数或方法。

2.2 Block 类型变量

“Block” 既指源代码中的 Block 语法,也指由 Block 语法所生成的值。

Block变量声明:

int (^ blk) (int);

Block 类型变量的用途:

(1)自动变量(局部变量)

(2)函数参数

(3)静态变量

(4)静态全局变量

(5)全局变量

使用 Block 语法将 Block 赋值为 Block 类型变量

int (^blk) (int)  = ^(int count) {return count +1 };

在函数参数中使用 Block 类型变量向函数传递 Block:

一种:

void func (int (^blk) (int)) {…}

另一种:

typedef  int (^blk_t) (int);

void func (blk_t blk) {…}

在函数返回值中指定 Block 类型,可以将 Block 作为函数的返回值返回

一种:

- (int (^) (int))func             //注:函数返回值为 Block时,返回类型没有 block 变量名

{

        return ^(int count) {return count + 1;};

}

另一种:

typedef  int (^blk_t) (int);

- (blk_t) func

{

                return ^(int count) {return count + 1;};

}

用 typedef 给 block 重命名 

www.88pt88.com 5

 用 Block 类型变量调用 Block,与通常的 C 语言变量一样使用:

- (int) methodUsingBlock: (blk_t) blk  rate:(int) rate

{

        return blk(rate);

}

二 block的用法

它的一个限制是,只能修饰可retain的对象指针类型,比如id, Class等等,不能修饰int *这样的类型。

2.3 截获自动变量值

“带有自动变量值的匿名函数” “带有自动变量值”在 Block 中表现为 “截获自动亦是值”。 

www.88pt88.com 6

Block 截获自动变量

Blocks 中,Block 表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为 Block 表达式保存了自动变量的值,所以在执行 Block 语法后,即使改写 Block 中使用的自动变量的值也不会影响 Block 执行时自动变量的值。

1 block的声明,定义,调用:

  • 声明
int (^myBlock)(int);
  • 定义
 myBlock = ^(int a){
         .....
         return a;
};
  • 调用
myBlock(10);
  • 声明和定义可以放一起
int (^myBlock)(int) = ^(int a){
         .....
         return a;
};
  • 和typedef使用

如果当有多个返回值和参数都相同的block,那我们就要按照上面的写法重复声明,这样很麻烦,这个时候只要使用typedef事情就变简单了。

typedef int (^MyBlock)(int);
MyBlock myBlock1 = ^(int a){
    .....
    return a;
};
MyBlock myBlock2 = ^(int a){
    .....
    return a;
};

这时MyBlcok就不再是一个具体的block了,而是一个block类型,表示返回值为int,参数Wie一个int型的block。

  • 作为方法的参数
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    void (^testBlock)(NSInteger, NSString *);
    testBlock = ^(NSInteger count, NSString *str) {
        NSLog(@"%@ %ld", str, count);
    };
    testBlock(100, @"test");

    NSInteger (^canshuBlock)(NSInteger) = ^NSInteger(NSInteger count) {
        NSLog(@"testMethod_count:%ld", count);
        return count + 100;
    };

    void (^b)(NSString *) = [self testMethod:canshuBlock];

    b(@"7890");
}

- (void (^)(NSString *))testMethod:(NSInteger(^)(NSInteger count))block {
    void (^myblock)(NSString *) = ^(NSString *str) {
        NSInteger n = block(999);
        NSLog(@"%ld", n);
    };

    return myblock;
}

总结:声明的时候block的参数名不用写,实现的时候需要写,以便使用。当block作为方法的返回值时,不用写block名,表示匹配这一类的block类型。

上边的两行代码表示foo被标记为consumed。意味着该函数的被调用者希望得到一个+1 retain count的对象。声明了这个属性后,当传入一个参数时,在函数调用前,ARC会把对该参数做一次retain操作,在该函数结束后再对该参数做一次release操作,这一过程很像函数对局部变量的操作。

2.4 __block 说明符

自动变量值截获只能保存执行 Block 语法瞬间的值。保存后就不能改写该值。尝试改写截获的自动变量值:会生产编译错误 

若想在 Block 语法的表达式中将值赋给在 Block 语法外声明的自动变量,需要在该自动变量上附加 __block 说明符。

www.88pt88.com 7

使用附有 __block 说明符的自动变量可在 Block 中赋值,该变量称为 __block 变量。

2 block实现简单的传值:

比如现在有个场景是这样的:页面A上有个label,页面B上有个输入框和一个按钮,当点击页面B上的按钮时,把页面B上输入框的内容用页面A上的label显示出来。

  • 当然我们可以使用代理来实现,大致实现如下:

分析可知,我们的目的是把页面B上的内容传给页面A,那么页面A就应该是代理方,页面B是委托方,点击页面B上的按钮时,把输入框中的内容作为页面A中代理方法的参数,并回调。完成参数的传递。
总结:委托方传值;代理方接收值。

  • block实现:

A中应该定义block,B中声明,回调。
B中实现:

@property(nonatomic,copy)void (^aBlock)(NSString *text);

点击按钮调用的方法回调:

self.aBlock(self.inputTF.text);  //self.inputTF为输入框控件

A中实现(b为B实例化的对象):

b.aBlock = ^(NSString *text){
     self.label.text = text;
};

总结:传值的地方声明,并回调;接收值的地方定义block。

到这里就产生了第一个疑问?

2.5 截获的自动变量

截获 OC 对象,调用变更该对象的方法:

www.88pt88.com 8

结论:赋值给截获的自动变量 arrayMwww.88pt88.com, 的操作会产生编译错误,但使用截获的值却不会有问题。

Blokc 截获 C 语言数组: 

www.88pt88.com 9

结论:在 Blocks 中,截获自动变量的方法并没有实现对 C 语言数组的截获。这时,使用指针可以解决该问题。 

三 block的内存管理

block不管是retain,copy,release都不会改变引用计数retainCount,retainCount始终是1

为什么要对传入的参数做retain,在结束时又release掉?

3 Blocks 的实现

1 block的分类(存储位置)

  • ** 全局区(blockNSGlobalBlock)**
    位于内存全局区,没有引用外部局部变量的block,retain、release,copy操作都无效。
//create a NSGlobalBlock
float (^sum)(float, float) = ^(float a, float b){
return a + b;
};
NSLog(@"block is %@", sum);  //block is <__NSGlobalBlock__: 0x47d0>
  • 栈区block(NSStackBlock)
    位于内存栈区,引用了局部变量的block。
    retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain野没有用。容易放错的是[mutableArray addObject:stackBlock],在函数出栈后,从mutableArray中取到的stackBlock已经被回收,变成野指针。(在ARC中不用担心此问题,因为ARC中会默认将实例化的block拷贝到堆区)
void (^TestBlock)(void) = ^{
NSLog(@"testArr :%@", testArr);
};
NSLog(@"block is %@", ^{
NSLog(@"test Arr :%@", testArr);
});
//block is <__NSStackBlock__: 0xbfffdac0>
//打印可看出block是一个 NSStackBlock, 即在栈上, 当函数返回时block将无效
NSLog(@"block is %@", TestBlock);
//block is <__NSMallocBlock__: 0x75425a0>
//上面这句在非arc中打印是 NSStackBlock, 但是在arc中就是NSMallocBlock,即在arc中默认会将block从栈复制到堆上,而在非arc中,则需要手动copy.
  • 堆区block(NSMallocBlock):位于内存堆区
    支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的block,只是增加一次引用计数,类似retain。
    在非ARC下,我们一般不会手动创建堆区block,我们把从栈区拷贝(copy)过来的block称为堆区block。

这个跟参数的生命周期有关,我们在函数中使用了参数,当然希望能够得到这个参数的所有权,并且希望该参数一直存活着。这个内容我会在下边的内容中给出一定的解释。在上边的文档中有这样一段话:

3.1 Block 的实质

Block的实质即为 Objective-C 的对象(结构体)。

结构体包括:

    isa类结构指针 (三大类型:_NSConcrete[Stack | Malloc | Global ]Block)

    FuncPtr  函数指针

    flags , reserved和 Block 截获的自动变量

clang -rewrite-objc源代码文件名

2 block对外部变量的内存管理

基本数据类型

  • 局部自动变量

在Block中只读;Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。

  • STATIC修饰符的全局变量

因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量

  • __BLOCK修饰的变量

Block变量,被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。

oc对象

  • 默认情况下block的内存是在栈中(不需要手动去管理block内存),它不会对所引用的对象进行任何操作
  • 如果对block进行了copy操作, block的内存会搬到堆里面,它会对所引用的对象做一次retain操作

非ARC: 如果所引用的对象用了__block修饰,就不会做retain操作。
ARC: 如果所引用的对象用了__unsafe_unretained__weak修饰,就不会做retain操作。

RationaleThis formalizes direct transfers of ownership from a caller to a callee.The most common scenario here is passing the self parameter to init, but it isuseful to generalize. Typically, local optimization will remove any extra retains and releases: on the caller side the retain will be merged with a +1 source, and onthe callee side the release will be rolled into the initialization of the parameter.

3.2 截获自动变量值 (只对 Block 中使用的自动变量)

截获自动变量值:在执行 Block 语法时,Block 语法表达式所使用的自动变量值被保存到 Block 的结构体实例中。

这段话指出,上边的操作直接从调用者到被调用者转移了所有权,最常见的一个场景就是传递self参数到init方法之中,这个内容将是本文最重要的内容。一般来说,局部的优化会移除任何额外的retain和release操作,这句话的意思是说,在函数中,某些局部变量不一定都会十分严格的按照retain/release原则来进行操作。调用端将会进行一些必要的合并操作,而被调用端也会对参数做一些额外的操作。

3.3 __block 说明符

Block 中修改自动变量的两种方法:

第一种:用 静态局部变量、静态全局变量、全局变量。

注:静态局部变量:Block 结构体中存放静态局部变量的指针。

第二种:用 __block 说明符 (__block 存储域类型说明符) 类似于 static 、auto 、register 说明符,用于指定变量值的存储到哪个存储域中

__block变量clang后转换为结构体, Block 的结构体实例持有指向 __block 变量的结构体实例的指针。

2.3.4 Block 存储域

Block 转换为 Block 的结构体类型的自动变量,__block 变量转换为 __block 变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。

Block 的类:

_NSConcreteGlobalBlock -存储域:程序的数据区域(.data 区)

1)记述全局变量的地方使用 Block 语法时

(2)Block 语法的表达式中不使用截获的自动变量时

_NSConcreteStackBlock -存储域:栈 ;复制效果:到堆

除 Global 之外的 Block 生成的 Block 都是栈Block

_NSConcreteMallocBlock -存储域:堆;复制效果:引用计数增加

Blocks提供了将 Block 和 _block 变量从栈上复制到堆上的方法。

这样即使 Block 语法记述的变量作用域结束,堆上的 Block 还可以继续存在。

实际上当 ARC 有效时,大多数情形下编译器会恰当地进行判断,自动生成将 Block 从栈上复制到堆上的代码。

什么时候栈上的 Block 会被复制到堆上:

(1)调用 Block 的 copy 实例方法时

(2)Block 作为函数的返回值时

(2)将 Block 赋值给附有 __strong 修饰符 id 类型的类或Block 类型成员变量时

(3)方法名中含有 usingBlock 的 Cocoa 框架的方法 和 GCD 的 API

需要手动复制的情况:

(1)NSArray 类的initWithObjects

www.88pt88.com 10

(2)向方法或函数的参数中传递 Block 时(可以不用手动复制) 

www.88pt88.com 11

不管 Block 配置在何处,用 copy 方法复制都不会引起任何问题。在不确定时调用 copy 方法即可。

到这里,有了第二个疑问?

3.5 __block 变量存储域

Block中使用 __block 变量,当 Block 从栈复制到堆时,使用的所有 __block 变量也全部被从栈复制到堆。

在多个 Block 中使用 __block 变量   

www.88pt88.com 12

Block 超出变量作用域可存在的原因:将 Block 和 __block 变量从栈上复制到堆上

__block 变量用结构体成员变量 __forwarding 存在的原因:实现无论 __block变量配置在栈上还是堆上都能正确地进行访问 

www.88pt88.com 13

在ARC中,为什么selfinit方法中是一个consumed parameter?

3.6 截获对象

__main_block_copy_0 函数使用 _Block_object_assign 函数将对象类型对象赋值给 Block 结构体的成员变量 array 中并持有该对象。

_Block_object_assign 函数调用相当于 retain 实例方法的函数,将对象赋值在对象类型的结构体成员变量中。

__main_block_dispose_0 函数使用 _Block_object_dispose 函数,释放赋值在 Block 用结构体成员变量 array 中的对象。

_Block_object_dispose 函数相当于 release 实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。

__main_block_copy_0 __main_block_dispose_0 函数在 Block 从栈复制到堆时以及堆上的 Block 被废弃时调用

这个问题我之前是不知道的,它来源于这个提问。init方法被标记为ns_consumes_selfns_consumes_self说明在方法中遵循上边讲的原则,在方法调用之前先把self做retain操作,结束时做release操作。

3.7 __block 变量和对象

__block 说明符可指定任何类型的自动变量。

User *user = [[User alloc] init];

3.8 Block 循环引用

Block 中使用附有 __strong 修饰符的对象类型自动变量,那么当 Block 从栈复制到堆时,该对象为 Block 所持有。这样容易引起循环引用。

解决方法:

  1. ARC :通过 __weak 或 __unsafe_unretained 修饰符(iOS4) 来替代__strong 类型的被截获的自动变量。

  2. MRC:通过 __block 说明符指定变量不被 Block 所 retain ; ARC下 __block说明符的作用仅限于使其能在 Block 中被赋值。

原理:

如果对 Block 做一次 copy 操作,Block 的内存就会在堆中

它会将所引用的对象做一次 retain 操作

非 ARC : 如果所引用的对象用了 __block 修饰,就不会做 retain 操作

ARC :如果所引用的对象用了 __unsafe_unretained 或 __weak 修饰,就不会做 retain 操作。

这是一行非常简单的代码,在调用了alloc后就创建了一个User对象,这个可以在这篇回答中获得证据。返回的对象的retain count等于1,大家应该记得,凡是通过alloc/new/copy.etc生成的对象,retain count都会+1,那么在这里的init方法中:

3.9 copy/release

ARC无效时,一般需要手动将 Block 从栈复制到堆。也要释放复制的 Block。

copy 方法复制 ,release 方法释放。


注:静态局部变量,静态全局变量,全局变量 的相同与不同点:

相同点:存储区都在全局区

不同点: 作用域不同

全局变量: 其它文件需要用extern关键字再次声明这个全局变量。

    静态全局变量:仅所在的文件才可以访问

    静态局部变量:仅对其所在的函数体作用域可见。

截获 OC 对象,调用变更该对象的方法:

self = [super init];if  { ... } return self;

self首先被init的调用者做了一次retain操作,此时它的retain count为1,执行完self = [super init];后,它的retain count为2,直到init返回后,self做了一次release操作,此时它的retain count为1。**这就完美保证了self在方法中是一直存活的,也保证了能够返回一个retain count为1的对象。

有兴趣可以翻看这个提问中的回答的部分,那哥们说的很详细,再说一点,在以前的MRC时代,代码可以这样写:

- view { //explicit retain-autorelease of +1 variable is +2 -> +1, guaranteed access or nil. return [[_view retain]autorelease];}

为了正确返回某个对象,先retain再release。

因此在使用consumed的时候,需要注意一下几点:

  • 保证方法的接收者不能为null,因为在方法被调用之前,参会会做retain操作,这样就带来了内存泄漏的问题
  • 传递的参数的个数不能大于方法能够动态处理的个数,否则可能引起未知的后果
  • 谨慎处理静态类型的问题

何为静态类型,何为动态类型?

A *a = [A new];B *b = a;

那么b的静态类型就是B,这个类型是由编译器决定的,而A则是它的动态类型,由运行时决定。

我发现ASDisplayKit的源码极其复杂,估计要花相当多的时间来解读了。不能放弃,加油。

版权声明:本文由大奖888-www.88pt88.com-大奖888官网登录发布于大奖888官网登录,转载请注明出处:Blocks 是带有自动变量(局部变量)的匿名函数,