关于Block的一些基础

一直C++学的也不怎么好,一直在分析一些Demo的时候发现Block被用到的很多,但是都是迷迷糊糊,搞不懂怎么用,有什么好处。看到一篇还好的博文,但是是繁体。有些词也不是很通俗,试着参照改写一下。

Block

Apple 在C, Objective-C, C++加上Block用法。目前只有Mac 10.6 和iOS 4有支持。Block是由一堆可执行代码段组成,也可以称作沒有名字的函数Function (Anonymous function)。如果是Mac 10.6 或 iOS 4.0 之前的平台可以利用 http://code.google.com/p/plblocks/ 這個project得以支持Block语法。 

Block 简述

Block其实际作用和Function很像,最大的差別是在可以存取同一個范围(Scope)的变量。 
Block 格式 
^(传入类型 参数   ) {   主体代码   } ;

Block开头是"^",接着是小括号内所包起來的  参数列 (比如 int a, int b, float c),  主体代码 由大括号包起來,专用名词叫做block literal。  主体代码 可以用return返回值,返回类型會被编译器(compiler)自动识别。无参数则为(void)。 
看個列子 
^(  int a) {  return a*a;};
这是代表Block会返回输入值的平方值(  int a  就是  参数 ,  return a*a;  就是 主体代码 )。記得主體裡最後要加";"因為是敘述,而整個{}最後也要要加";"因為Block是個物件實體。 
用法就是 
int result = ^(int a) {return a*a  ;} (5)  ;
很怪吧。小括号的5会被当做a的输入值然后由Block输出5*5 = 25指定給result這個变量。 
有沒有简单的方法不然每次都要写这么长?有。接下來要介紹一個叫  Block Pointer 來简化写法。 
Block Pointer 声明格式 
返回类型  (^名字  ) (参数列  );

直接來看一個列子 
int (^  square) (int); 
// 有一個叫  square 的  Block Pointer ,其所指向的Block是有一個int 輸入和 int 輸出 

square = ^(int a ) {return a*a ;}; // 将刚声明的block实现

使用Block Pointer的例子 
int result = square(5); // 感覺上不就是函数的用法吗?
也可以把Block Pointer當成参数傳給一個函数function,比如
void myFuction( int (^mySquare) (int) ); //函数的声明
传入一個有以int为参数类型和int为返回类型的Block的参数
调用myFunction的時候就是这样
int (^mySqaure) (int) = ^(int a) {return a*a;};
// 先給好一個已经声明实现的block pointer叫mySquare

myFunction( mySqaure ) ; //把mySquare這個block pointer传myFunction函数
或是不用block pointer 直接給一個block 实体,就這样
 myFunction(   ^(int a) {return a*a} ) ;
作为Objective-C 方法的传入参数的話都是要把类型写在变量前面然后加上小括号,因些应该就是这样写
-(void) objcMethod:(  int (^) (int) ) square; // square变量的类型是 int (^) (int)
至此對Block是基本的认识 接下來我們要说Block有关的用法和特点 
首先是來看一下在Block里存取外部变量的方法 

存取变量
1. 可以读取和Block pointer同一個范围的变量值: 

int outA = 8; 
int (^myPtr) (int) = ^(int a) {return outA+a;}; 
int result = myPtr(3); // result is 11 
}
再來看一個奇怪的例子 


int outA = 8; 
int (^myPtr) (int) = ^(int a) {return outA+a;}; 
// block 裡面可以读同一個作用范围的outA的值 
outA = 5; // 在调用myPtr之前变更outA的值 
int result = myPtr(3); // result 的值是  11并不是  8 
}
实际上,myPtr在其实现部分用到outA這個变量值的时候是做了一個copy的调用把outA的值copy下來。所以之后outA即使改变了对于myPtr里copy的值是沒有影响的。 
要注意的是,這個指的值是变量的值,如果這個变量的值是个pointer的話,它指到的值是可以在block里被改变。 

        NSMutableArray * mutableArray = [NSMutableArray arrayWithObjects:@"one",@"two",@"three",nil]; 
        int result = ^(int a) { [mutableArray removeLastObject];  return a*a;} (5); 

        NSLog(@"test array %@", mutableArray); 

}
原本mutableArray的值是{@"one",@"two",@"three"}在block里面被更改mutableArray所指向的元素后,mutableArray的值就是{@"one",@"two"} 
2. 直接存取static 的变量

static int outA = 8; 
int (^myPtr) (int) = ^(int a) {return outA+a;}; 

outA = 5; // 在呼叫myPtr之前改變outA的值 
int result = myPtr(3); // result 的值是  8,因為outA是個static 特性的变量
}
甚至可以在block裡面直接改变outA的值比如這样

static int outA = 8; 
int (^myPtr) (int) = ^(int a) { outA= 5; return outA+a;}; 
// block 裡面改變outA的值 
int result = myPtr(3); // result 的值是  8

}
3. Block Variable 
在某個变量前面如果加上修飾字  _   _ block 的話(注意block前有兩個下划线),這個变量又叫做block variable(变量)。那在block里就可以任意修改此变量值,变量值的变化也可以知道。 

    __block int num = 5; 

    int (^myPtr) (int) = ^(int a) { return num++;}; 
    int (^myPtr2) (int) = ^(int a) { return num++;}; 
    int result = myPtr(0); 
    result = myPtr2(0); 
}
myPtr和myPtr2都有用到num這個block variable,result的值就會是7 

生命周期和内存管理

因为block也是继承自NSObject,所以其生命周期和内存的管理也就非常之重要。 
block一开始都是被放到堆里,换句话说其生命周期随着method或function結束就會被回收,和一般变量的生命周期一样。 
关于内存管理請遵循这几点 
1. block pointer的实现会在method或function結束后就会被清理 
2. 如果要保存block pointer要用-  copy 指令,这样block pointer就會被放到堆裡 
    2.1 block 实现用到的block variable 也会被移到到heap 而有新的内存地址,且一并更新有用到這個block variable 的block都指到新的位置 
    2.2 一般的variable值會被copy 
    2.3 如果主體裡用到的variable是object的話,此object會被retain, block release時也會被release 
    2.4 __block variable 裡用到的object是不會被retain的 

首先來看一下這個例子 

typedef int (^MyBlock)(int); 

MyBlock genBlock(); 

int main(){ 
        MyBlock  outBlock = genBlock(); 
        int result = outBlock(5); 

        NSLog(@"result is %d",[outBlock retainCount] ); // segmentation fault 
        NSLog(@"result is %d",result  ); 

        return 0 ; 

MyBlock genBlock() { 
        int a = 3; 
        MyBlock  inBlock = ^(int n) { 
                return n*a; 
        }; 
        return  inBlock ; 
}
代码由genBlock里生成的block再指定給main function的  outBlock变量 ,运行代码會得到 
Segmentation fault 
(註:有時候把 genBlock裡的a 去掉就可以跑出結果的情形,這是系統缓冲区内存,並不是inBlock真得一直存在,久了還是會被回收,千萬不要以為是對的寫法) 
表示我們用到了不該用的内存,在这个例子的情況下是在genBlock里的  inBlock 变量在return的時候就被回收了,  outBlock 无法调用一个合法的内存位置-retainCount就沒意义了。 
如果這個時候需要保留inBlock的值就要用-copy指令,將genBlock改成 
 MyBlock genBlock() { 
        int a = 3; 
        MyBlock inBlock = ^(int n) { 
                return n*a; 
        }; 
        return [inBlock  copy]  ; 
}
[inBlock copy]的返回值就會被放到栈,就可以一直使用(記得要release) 
結果是 
result is 1 
result is 15 

再次提醒要記得release outBlock。 
如果一返回[inBlock copy]的值就不再需要的時候可以這樣 
 MyBlock genBlock() { 
        int a = 3; 
        MyBlock inBlock = ^(int n) { 
                return n*a; 
        }; 
        return [[inBlock  copyautorelease] ; 
}
-copy指令是為了要把block 從stack搬到heap,autorelease是為了平衝retainCount加到autorelease oop ,回傳之後等到事件結束就清掉。 

接下來是block存取到的local variable是对象的情况,然後做copy 指令時 
MyBlock genBlock() { 
        int a = 3; 
        NSMutableString * myString = [NSMutableString string]; 
        MyBlock inBlock = ^(int n) { 
                NSLog(@"retain count of string %d",[myString retainCount]); 
                return n*a; 
        }; 
        return [inBlock copy] ; 
}
結果會印出 
retain count of string 2 
這個結果和上面2.3提到的一樣,local variable被retain了 
那再來試試2.4,在local variable前面加上__block 
MyBlock genBlock() { 
        int a = 3; 
         __block NSMutableString * myString = [NSMutableString string]; 
        MyBlock inBlock = ^(int n) { 
                NSLog(@"retain count of string %d",[myString retainCount]); 
                return n*a; 
        }; 
        return [inBlock copy] ; 
}
執行的結果就是會 
retain count of string 1 

Block Copying注意事项
如果在Class method里面做copying block的话 
1. 在Block里如果有直接存取到self,則self會被retain 
2. 在Block里如果取存到instance variable (无论直接或是从accessor),則self會被retain 
3. 取存到local variable所擁有的object時,這個object會被retain 

讓我們來看一個自訂的Class 
@interface MyObject : NSObject { 
        NSString * title; 
        void (^  myLog) (NSString * deco); 


-(void) logName; 
@end 

@implementation MyObject 
-(id) initWithTitle:(NSString * ) newTitle{ 
        if(self = [super init]){ 
                title = newTitle; 
                 myLog = [^(NSString * deco)  { NSLog(@"  %@%@%@",deco, title, deco );  } copy]; 
        } 
        return self; 


-(void) logName{ 

 myLog(@"=="); 


-(void ) dealloc{ 

        [myLog release];
        [title release]; 
        [super dealloc]; 

@end
在main 裡使用如下 

 MyObject * mObj = [[MyObject alloc]  initWithTitle :@"Car"]; 
 NSLog(@"retainCount of MyObject is  %d",[mObj retainCount]  ); 
 [mObj logName]; 
其執行的結果為 
retainCount of MyObject is   2  
==Car== 
因為在MyObject的建構子裡myLog這個block pointer用了title這個instance variable然後就會retain self也就是MyObject的物件。 
盡量不要這樣寫,會造成retain cycle,改善的方法是把建構子改成這樣 
-(id) initWithTitle:(NSString * ) newTitle{ 
        if(self = [super init]){ 
                title = newTitle; 
                 myLog = [^(NSString * deco)  { NSLog(@"  %@%@%@",deco,  newTitle, deco );  } copy]; 
        } 
        return self; 
}
在Block主體裡用newTitle這個變數而不是title。這樣self就不會被retain了。 
最後談一個小陷井 
void (^myLog) (void); 
BOOL result ; 
if(result) 
    myLog = ^ {NSLog(@"YES");}; 

else 
    myLog = ^ {NSLog(@"NO");}; 

myLog(); 

這樣很可能就會當掉了,因為myLog 實體在if 或是else結束後就被清掉了。要記得。 

要用copy來解決這個問題,但要記得release。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值