您的位置:首页 > 移动开发 > IOS开发

[iOS]block

2016-03-30 21:01 441 查看
本文参考以下博客
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/ http://blog.csdn.net/hherima/article/details/38586101 http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/
一、声明block

使用^操作符声明block,^表示了一个block的开始,{ }中是block的内容。可以有返回值和参数列表。 

此处是声明了一个block变量,多数情况下不需要声明block变量。直接在需要参数的地方使用block块即可。注意在声明block变量的时候,^在括号里面,而在实现block时,^在括号外面。

如果不显式声明blcok的返回值,它可以从该block的内容中自动推断。如果返回值被推断,参数列表是void,你也可以忽略(void)参数列表。如果当多个return语句都存在,它们必须完全匹配(有必要时使用cast)其他情况下,没有参数的block必须指定void在参数列表,可以使用可变参数。

宏定义一个block
typedef int (^MyBlock)(int, int); 

block被设计为全类型安全。开发者可以cast block引用为任意的指针类型,反过来也一样。然而,不能将指针转化成block,因此,block的大小不能在编译时期计算。

二、调用block

如果定义一个block变量,你可以将它作为一个函数使用

int (^oneFrom)(int) = ^(int anInt) {

    return anInt - 1;

};

 

printf("1 from 10 is %d", oneFrom(10));

// Prints "1 from 10 is 9”

传递一个block作为函数参数,就像其他参数一样。多数情况,开发者不用声明一个block。相反你只需要在需要的地方实现inline的block作为参数。

size_t count = 10;

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 

dispatch_apply(count, queue, ^(size_t i) {

    printf("%u\n", i);

});

三、block中的变量和对象

捕获自动变量值

首先看一个经典block面试题:

int val = 10;

void (^blk)(void) = ^{printf("val=%d\n",val);};

val = 2;

blk();

面这段代码,输出值是:val = 10.而不是2. 

    block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝;由于block捕获了自动变量的瞬时值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。

__block

block中变量的使用,和在函数中很像。注意,如果涉及到block外部的变量,可以访问,但是不能修改不然会有编译错误。但是可以改变全局变量、静态变量、全局静态变量。

其实这两个特点不难理解:不能修改自动变量的值是因为:block捕获的是自动变量的const值,名字一样,不能修改。可以修改静态变量的值:静态变量属于类的,不是某一个变量。由于block内部不用调用self指针。所以block可以调用。

如果想要修改,该外部变量要使用__block修饰符(此处有两个下横线)。

__block int x = 123; //  x lives in block storage

 

void (^printXAndY)(int) = ^(int y) {

 

    x = x + y;

    printf("%d %d\n", x, y);

};

printXAndY(456); // prints: 579 456

// x is now 579

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

__block变量还有两个限制,他们不能是变长数组,不能是结构体。

block的存储区域

block分为三种:_NSConcretStackBlock、_NSConcretGlobalBlock、 _NSConcretMallocBlock

正如它们名字显示得一样,表明了block的三种存储方式:栈、全局、堆。

1、当block在函数内部,且定义的时候就使用了函数内部的变量,那么这个 block是被拷贝到栈上(mrc),堆(arc)。

2、当block定义在函数体外面,或者定义在函数体内部且block体中并没有需要使用函数内部的局部变量时,那么block将会被编译器存储为全局block。

3、全局block存储在堆中,对全局block使用copy操作会返回原函数指针;而对栈中的block使用copy操作,会产生两个不同的block地址,也就是两个匿名函数的入口地址。

Block的copy,retain,release操作

对block retain操作并不会改变引用计数器,retainCount ,始终为1

NSGlobalBlock:retain、copy、release操作都无效;

Block_copy与copy等效,Block_release与release等效

NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的 错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry
addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。

NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain

尽量不要对Block使用retain操作

在 ARC 下,有时需要手动拷贝和释放 block。在 MRC 下更是如此,可以直接用 copy 和 release 来拷贝和释放

void (^bl
4000
k_on_heap)(void) = [blk_on_stack copy]; 

[blk_on_heap release];

拷贝到堆后,就可以 用 retain 持有 block

[blk_on_heap retain];

然而如果 block 在栈上,使用 retain 是毫无效果的,因此推荐使用 copy 方法来持有 block。

循环引用

- (id)init

{

 self = [super init];

blk_ = ^{NSLog(@"self = %@", self);}; 

return self;

}

由于 self 是 __strong 修饰,在 ARC 下,当编译器自动将代码中的 block 从栈拷贝到堆时,block 会强引用和持有 self,而 self 恰好也强引用和持有了 block,就造成了传说中的循环引用。

为了避免这种情况发生,可以在变量声明时用 __weak 修饰符修饰变量 self,让 block 不强引用 self,从而破除循环。iOS4 和 Snow Leopard 由于对 weak 的支持不够完全,可以用 __unsafe_unretained 代替。

- (id)init

{

self = [super init];

id __weak tmp = self;

blk_ = ^{NSLog(@"self = %@", tmp);}; 

return self;

}

再看一个例子

@interface MyObject : NSObject

{

blk_t blk_;

id obj_; 

}

@end

@implementation MyObject 

- (id)init

{

self = [super init];

blk_ = ^{ NSLog(@"obj_ = %@", obj_); }; 

return self;

}

...

@end

上面的例子中,虽然没有直接使用 self,却也存在循环引用的问题。因为对于编译器来说,obj_ 就相当于 self->obj_,所以上面的代码就会变成

blk_ = ^{ NSLog(@"obj_ = %@", self->obj_); };

所以这个例子只要用 __weak,在 init 方法里面加一行即可

id __weak obj = obj_;

在 MRC 下,使用 __block 说明符也可以避免循环引用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: