###1.什么是block?
####带有自动变量(局部变量)的匿名函数。 #####带有自动变量的理解: 在oc中由类生成的实例或对象保持该成员变量的值。block提供了类似的行为
TestBlock2 *testblock = [TestBlock2 shareManage];
[testblock outgoing:^(NSString *string) {
GLog(@"-----%@", string);.//将变量string设定为回调
}];
复制代码
Blocks 通常代表一个很小、自包的代码片段。因此它们作为封装的工作单元在并 发执行,或在一个集合项上,或当其他操作完成时的回调的时候非常实用。 Blocks 作为传统回调函数的一个实用的替代办法,有以下两个原因:
- 它们可以让你在调用的地方编写代码实现后面将要执行的操作。 因此 Blocks 通常作为框架方法的参数。
- 它们允许你访问局部变量。
而不是需要使用一个你想要执行操作时集成所有上下文的信息的数据结构来
进行回调,你可以直接简单的访问局部变量。
###2.block的语法
###3.自动变量值的截获(保存)
int a = 10;
void(^block)(void) = ^{
NSLog(@"a == %d", a);
};
a = 20;
block();
复制代码
__block int a = 10;
void(^block)(void) = ^{
a = 30;
NSLog(@"a == %d", a);
};
a = 20;
block();
复制代码
第3点和第4点分别是保存和修改变量值,保存(截获)时,不会产生编译错误,但赋值如果不用__block修饰则会产生编译错误。
#####实际上,在由block语法生成的值block上,可以存有超过其变量作用域的被截获对象的自动变量(即保存)。变量作用域结束同时,原来的自动变量被废弃,因此block中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量(即不能改变值)。
__block存储域说明符:指定将变量值设置到哪个存储区域中。
这里讲述一下isa指针,具体看这里http://www.jianshu.com/p/41735c66dccb在oc中,由类生成对象
。
//block被创建于栈上,通过copy将其引用到堆区。
//通过block复制,__block变量从栈复制到堆。此时,可同时访问栈上的__block变量和堆上的__block变量
__block int val = 0;
void(^blk)() = [^{
++val;
} copy];
++val;
blk();
GLog(@"%d", val);
//__block说明符可指定任何类型的自动变量
//block中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量
//__block在这里用于将变量值设置到哪个存储区域中。
__block NSString *string = @"s";//所指向变量的地址,是随着block而变化的,即block在栈则,__block变量也配置在栈;block被copy到堆区,则__block变量也被从栈复制到堆区。
复制代码
block从栈复制到堆时对__block变量产生的影响。
__block变量的配置存储区域 | block从栈复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆并被block持有 |
堆 | 被block持有 |
通过block复制,__blcok变量也从栈复制到了堆。此时,可同时访问栈上的__block变量和堆上的__block变量。 ###5.block的存储区域
作为一种优化,block存储在栈上面,就像blocks本身一样。如果使用Block_copy 拷贝了 block 的一个副本(或者在 Objective-C 里面给 block 发送了一条 copy 消息), 变量会被拷贝到堆上面。所以一个__block 变量的地址可以随时间推移而被更改。 block转换为block的机构提类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。
block与__block变量的实质
名称 | 实质 |
---|---|
block | 栈上block的结构体实例 |
__block | 栈上__block变量的结构体实例 |
block的类
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域(.data区) |
_NSConcreteMallocBlock | 堆 |
配置在全局变量上的block,从变量作用域外可以通过指针安全地使用。但设置在栈上的block,如果其所属的变量作用域结束,该block就会被废弃。由于__block也配置在栈上,同样,如果其所属的变量作用域结束,则该__block变量也会被废弃。 可通过copy到堆来解决此问题 #####当ARC有效时,大多数情况下编译器会恰当地进行判断,自动生成将block从栈复制到堆上的代码。但在以下情况下需要手动生成代码,将block从栈上复制到堆上。此时,我们使用"copy实例方法"
- 向方法或函数的参数中传递block
#####但是如果在方法或函数中恰当地复制了传递过来的参数,那么就不必在调用该方法或函数钱手动复制了。
- cocoa框架的方法且方法名中含有usingBlock等时
- GCD的api #####虽然以上有区分但是,不管block配置在何处,用copy方法复制都不会产生任何问题。
void(^blk)(void);
blk = [[[blk copy] copy] copy];
//该代码可解释如下
{
/**
将配置在栈上的block赋值给blk中
*/
void(^tmp)(void) = [blk copy];
/**
将配置在堆上的block赋值给变量tmp中,变量tmp持有强引用的block。
*/
blk = tmp;
/**
将变量tmp的block赋值为变量blk,变量blk持有强引用的block。
因为原先赋值的block配置在栈上,所以不受此赋值影响。
此时block的持有为变量blk和变量tmp。
*/
}
/**
由于变量作用域结束,所以变量tmp被废弃,其强引用失效并释放所持有的block。
由于block被变量blk持有,所以没有被废弃。
*/
{
/**
配置在堆上的block被赋值变量blk,同时变量blk持有强制引用的block。
*/
void(^tmp)(void) = [blk copy];
/**
配置在堆上的blcok被赋值到变量tmp中,变量tmp持有强引用的block。
*/
blk = tmp;
/**
由于变量blk进行了赋值,所以现在赋值的block的强引用失效,block被释放。
由于block被变量tmp所持有,所以没有被废弃。
变量blk中赋值了变量tmp的blokc,变量blk持有强引用的block。
此时block的持有为变量blk和变量tmp。
*/
}
/**
由于变量作用域结束,所以变量tmp被废弃,其强引用失效并释放所持有的block。
由于block被变量blk持有,所以没有被废弃。
*/
{
//重复此过程
void(^tmp)(void) = [blk copy];
blk = tmp;
}
复制代码
#####栈上的block在什么时候会复制到堆?
- 调用block的copy实例方法时
- block作为函数返回值返回时
- 将block赋值给赋有__strong修饰符id类型的类或者block类型成员变量时
__strong修饰符是id类型和对象类型默认的所有权修饰符
id obj = [[NSObject alloc] init];
等同于
id __strong obj = [[NSObject alloc] init];
复制代码
- 在方法名中含有usingBlock的cocoa框架方法或GCD的api中传递block 时
block中,使用的赋值给赋有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的block所持有,因而可超出其变量作用域而存在。
###6.block的循环引用
#import <Foundation/Foundation.h>
typedef void(^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk;
}
@end
#import "MyObject.h"
@implementation MyObject
- (instancetype)init
{
self = [super init];
if (self) {
//__weak id tmp = self;
blk = ^{
//GLog(@"self = %@", tmp);
GLog(@"self = %@", self);
};
}
return self;
}
- (void)dealloc
{
NSLog(@"MyObject-----dealloc");
}
//MyObject类对象的Block类型成员变量blk持有赋值为Block的强引用。及MyObject类对象持有Block。
//init实例方法中执行的Block语法使用附有__strong修饰符的id类型变量self;并且由于Block语法赋值在了成员变量blk中,因此通过Block语法生产在栈上的Block此时由栈复制到了堆并持有所使用的self。
//self持有Block,Block持有self。这正是循环引用。
//可通过__weak修饰来解决此问题
@end
#import "ViewController.h"
MyObject *object = [[MyObject alloc] init];
NSLog(@"object---- %@", object);
复制代码