您的位置:首页 > 移动开发 > Objective-C

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跟块就互相保留了,导致内存泄露。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  闭包 Objective-C