Objective-C之block详细介绍
2016-03-09 14:06
603 查看
1.定义
闭包是一个允许访问自由变量(局部变量)的匿名函数。自由变量:跟block声明在同一个作用域内的局部变量。2.语法结构
返回类型 (^块名称)(参数列表)int (^myblock)(int a, int b) ; //声明一个块类型和块名,myblock为块名,块类型为int (^)(int a, int b);
myblock= ^(int a, int b) { return a+b;}; //定义块
int c = myblock(1,2); //c = 3; //调用块
从上面代码,块跟一个变量一样,先声明,定义,访问。
块变量与一般的C语言变量完全相同,可以作为以下用途。
局部变量
函数参数
静态变量
静态全局变量
全局变量
例1.块作为属性变量
@interfaceEOCClass :NSObject
@property(nonatomic,strong)int(^variableName)(inta,intb
);
@end
@implementationEOCClass
- (id)init {
if((self=
[superinit])) {
_variableName= ^(inta,intb)
{ //定义一个块,但块里面的代码还没被调用
returna
+ b;
};
}
return
self;
}
- (void)fun {
int c =
_variableName(1,2); //block被调用,即执行上面定义的代码a+b
}
@end
声明定义好一个块之后,就可以像传递一个变量一样,把block里面的代码传递到其他地方运行。这是块其中一个强大之处。
3.捕获自由变量
块另外一个强大之处是:在声明它的范围里,所有变量都可以为其捕获。例2@implementationEOCClass
- (id)init {
if ((self= [superinit]))
{
int additional =
5;
_variableName = ^(inta,intb)
{
//定义一个块
return a + b + additional; //additional被块捕获
};
}
return
self;
}
- (void)fun {
int c =
_variableName(1,2);
}
在fun方法调用块变量_variableName,这个块变量却能访问了声明在init方法里面的additional,这是因为块在声明时捕获了additional。需要注意的是,如果实例变量被块捕获了,那么也会自动把self一并捕获了,因为在对象内直接访问实例变量和通过self来访问是等效的:
self->anInstanceVariable = @“something”;
__block修饰符
默认情况下,为块所捕获的变量,是不可以在块里修改的。在上例中,假如块内的代码改动了additional的值,那么编译器就会报错。在声明变量时加上__block修饰符,这个变量就可以在块内修改了。__blockintadditional
=5;
_variableName= ^(inta,intb)
{
//定义一个块
additional =
3;
return a + b + additional; //additional被块捕获
};
注意:以下情况变量不需加修饰符__block也能在块修改。
1.静态变量
2.全局变量
3.实例变量
另外,块所捕获的变量的值,是块定义时变量的值,如果块定义完之后再修改变量,块内变量值仍是修改前的。但若变量是静态变量、全局变量、实例变量或__block修饰的变量,在块的值是其最新的值。(来源:http://www.cnblogs.com/kesalin/archive/2013/04/30/ios_block.html)
- (void)testAccessVariable
{
NSIntegeroutsideVariable
= 10;
//__block NSInteger outsideVariable = 10;
NSMutableArray*
outsideArray = [[NSMutableArrayalloc]init];
void (^blockObject)(void) = ^(void){
NSInteger insideVariable =20;
NSLog(@" > member variable = %lu", (unsignedlong)self.memberVarible);
NSLog(@" > outside variable = %ld", (long)outsideVariable);
NSLog(@" > inside variable = %ld", (long)insideVariable);
[outsideArray
addObject:@"AddedInsideBlock"];
};
outsideVariable =
30;
self.memberVarible=30;
blockObject();
NSLog(@" > %lu items in outsideArray", (unsignedlong)[outsideArraycount]);
}
输出结果为:
> member variable = 30
> outside variable = 10
> inside variable = 20
> 1 items in outsideArray
在块内outsideArray添加了一个字符串,因为并不是改变outsideArray本身。
4.块的内部结构
块本身也是对象:
isa:指向Class对象的指针,即指向块所属的类。
invoke:是一个函数指针,指向块的实现代码。
descriptor:是指向结构体的指针,其中声明了块对象的大小,还声明了copy与dispose这两个函数所对应的函数指针。函数会在拷贝及丢弃块对象时运行。
块还会把它所捕获的所有变量都拷贝一份,拷贝的并不是对象本身,而是指向这些对象的指针变量。这是块为什么能访问自由变量的原因。
5.全局块、栈块及堆块
5.1栈块NSConcreteStackBlock
定义块的时候,其所占得内存区域是分配在栈中,这就是说,块只在定义它的那个范围内有效。例如,下面这段代码就有危险:void (^block)( );
if(/* some condition */) {
block = ^{
NSLog(@“block A”);
};
} else {
block = ^{
NSLog(@“block B”);
}
block( );
编译器会给每个块分配好栈内存,然而等离开了相应了的范围之后,编译器有可能把分配给块的内存覆写掉。于是,这两个块只能保证在对应的if或else语句范围内有效。
5.2堆块NSConcreteMallocBlock
为了在块的作用域之外使用块,需要把块从栈复制到堆。而且,一旦复制到堆上块就成了带引用计数的对象了。如果不再使用这个块,那就应将其释放,在ARC环境下会自动释放,而手动管理引用计数时需要调用release方法。当引用计数将为0后,“分配在堆上的块”会像其他对象,为系统所回收。上述改进:
void (^block)( );
if(/* some condition */) {
block = [^{
NSLog(@“block A”) copy];
};
5.3全局块NSConcreteGlobalBlock
这种块不会捕获任何变量,运行时也无须任何变量参与。块所使用的整个内存区域,在编译期已经确定了。这种块实际上相当于单例。void (^block)( ) = ^{
NSLog(@"This is a block”);
}
6.保留环
在例1中的代码稍作修改,使得块捕获self,就会出现保留环。- (id)init {
if ((self= [superinit]))
{
int additional =
5;
_variableName = ^(inta,intb)
{
//定义一个块
NSLog(@"%@",self);
return a + b + additional; //additional被块捕获
};
}
return
self;
}
self被块所捕获,即self被保留了一次。同时块作为self的属性,也被self保留了一次。所以它们互相保留,产生了保留环。使用week声明块属性就可以破除保留环了。
另外一个典型的保留环例子:使用网络获取器EOCNetworkFetcher获取数据,当任务完成时,调用EOCNetworkFetcherCompletionHandler。
#import<Foundation/Foundation.h>
typedefvoid(^EOCNetworkFetcherCompletionHandler)(NSData*data);
@interfaceEOCNetworkFetcher :NSObject
@property(nonatomic,strong,readonly)NSURL*url;
- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end
#import"EOCNetworkFetcher.h"
@interfaceEOCNetworkFetcher()
@property(nonatomic,copy)EOCNetworkFetcherCompletionHandlercompletionHandler;
@property(nonatomic,strong)NSData*downloadedData;
@property(nonatomic,strong,readwrite)NSURL*url;
@end
@implementationEOCNetworkFetcher
- (id)initWithURL:(NSURL*)url
{
if ((self= [superinit]))
{
_url = url;
}
return
self;
}
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion
{
self.completionHandler= completion;
//请求执行完后调用p_requestCompleted
}
- (void)p_requestCompleted {
if (_completionHandler) {
_completionHandler(_downloadedData);
}
}
@end
某个类会创建这种网络数据获取器对象,并用其从URL中下载数据:
@implementationEOCClass {
EOCNetworkFetcher *_networkFetcher;
NSData *_fetchedData;
}
- (void)downloadData {
NSURL *url = [[NSURLalloc]initWithString:@"www.example.com/something.dat"];
_networkFetcher = [[EOCNetworkFetcheralloc]initWithURL:url];
[_networkFetcherstartWithCompletionHandler:^(NSData*data)
{
NSLog(@"Request URL %@ finished",_networkFetcher.url);
_fetchedData = data;
}];
}
@end
因为handler要设置_fetchedData实例变量,所以它必须捕获self变量,handler块保留了创建网络获取器的那个EOCClass实例。而EOCClass实例则通过strong实例变量保留了获取器,最后获取器对象又保留了handler块。
打破保留环:要么令_networkFetcher实例变量不再引用获取器,要么令获取器的completionHandler属性不再持有handler块。
- (void)downloadData {
NSURL *url = [[NSURLalloc]initWithString:@"www.example.com/something.dat"];
_networkFetcher = [[EOCNetworkFetcheralloc]initWithURL:url];
[_networkFetcherstartWithCompletionHandler:^(NSData*data)
{
NSLog(@"Request URL %@ finished",_networkFetcher.url);
_fetchedData = data;
_networkFetcher =
nil; //不推荐使用这种方法,它责任推给API调用者了
}];
}
或
- (void)p_requestCompleted {
if (_completionHandler) {
_completionHandler(_downloadedData);
}
self.completionHandler=nil;
}
总结:
1.块是一个可以访问自由变量的匿名函数。
2.它的一个强大之处是,声明定义好一个变量之后,可以作为参数传递块,那么块内的代码就可以传递到其他地方运行了。
3.块另外一个强大之处是,它可以捕获在它声明范围内的所有变量,需要注意的是,如果在块内捕获实例变量也会把self一并捕获,因为在对象内直接访问实例变量和通过self访问是等效的,这容易导致保留环。
4.默认情况下,所捕获的变量是只读的,如果想在块内修改所捕获的变量,需要在声明变量时加上__block修饰符。但静态变量、全局变量、实例变量不需要加__block修饰符,也能在块修改(跟作用域有关)。
5.块是一个轻量级的结构体,在块的内部第一个变量是isa,指向块所属的类。还有一个函数指针,指向块的实现代码。这是块为什么能被当做变量传递的原因。这个结构还保存着块所捕获的变量,这是块为什么能访问自由变量的原因。
6.块有栈块、堆块及全局块。在定义块时,块所占内存是分配在栈内的,也就是说,离开了定义块所在范围后,块就被释放了。若要想在定义块的范围之外使用块,需要把块从栈中拷贝到堆中,这时,块就变成了带有引用计数的对象了。不使用块时,需要释放块。在ARC环境下会自动释放。
7.使用块需要注意的是,保留环,比如把块声明为属性,self又被块所捕获,这时self跟块就互相保留了,导致内存泄露。
相关文章推荐
- 深入理解PHP之匿名函数
- 最后一次说说闭包
- Ruby中使用Block、Proc、lambda实现闭包
- LUA中的闭包(closure)浅析
- Lua中的闭包学习笔记
- C#中函数的创建和闭包的理解
- 深入理解javascript作用域和闭包
- javascript作用域和闭包使用详解
- 谈谈JavaScript中的函数与闭包
- 细品javascript 寻址,闭包,对象模型和相关问题
- JavaScript中的闭包原理分析
- 浅谈javascript中的闭包
- 学习javascript的闭包,原型,和匿名函数之旅
- javascript 闭包详解
- JavaScript 匿名函数和闭包介绍
- JavaScript 闭包深入理解(closure)
- 深入理解JavaScript 闭包究竟是什么
- 谈谈我对JavaScript原型和闭包系列理解(随手笔记8)
- 解决js函数闭包内存泄露问题的办法
- JavaScript中的闭包(Closure)详细介绍