详解Objective-c Block应用(转载,讲的很好)

详解Objective-c block应用是本文要介绍的内容,主要介绍的是Objective-c block应用,不多说,先来看详细内容。

AppleCObjective-CC++加上Block这个延申用法。目前只有Mac 10.6 和iOS 4有支援。Block是由一堆可执行的程式组成,也可以称做没有名字的Function (Anonymous function)。如果是Mac 10.6 或 iOS 4.0 之前的平台可以利用 http://code.google.com/p/plblocks/ 这个project得以支援Block语法。

Apple有一个叫做GCD(Grand Central Dispach)的新功能,用在同步处理(concurrency)的环境下有更好的效率。Block语法产生的动机就是来自于GCD,用Block包好一个工作量交给GCD,GCD有一个宏观的视野可以来分配CPU,GPU,Memory的来下最好的决定。

Block 简介

Block其实行为和Function很像,最大的差别是在可以存取同一个Scope的变数值。

Block 实体会长成这样

^(传入参数列) {行为主体};

Block实体开头是"^",接著是由小括号所包起来的参数列(比如 int a, int b, float c),行为的主体由大括号包起来,专有名词叫做block literal。行为主体可以用return回传值,型别会被compiler自动办识出来。如果没有参数列要这样写(void)。
看个列子

  1. ^(int a) {return a*a;}; 

这是代表Block会回传输入值的平方值(int a 就是参数列,return a*a; 就是行为主体)。记得主体里最后要加";"因为是叙述,而整个{}最后也要要加";"因为Block是个物件实体。

用法就是

  1. int result = ^(int a) {return a*a;} (5); 

很怪吧。后面小括号里的5 会被当成a的输入值然后经由Block输出5*5 = 25指定给result这个变数。

有没有简单一点的方法不然每次都要写这么长?有。接下来要介绍一个叫Block Pointer的东西来简化我们的写法。

Block Pointer是这样宣告的

回传值 (^名字) (参数列);

直接来看一个列子

  1. int (^square) (int);   
  2. // 有一个叫square的Block Pointer,其所指向的Block是有一个int 输入和 int 输出  
  3. square = ^(int a ) {return a*a ;}; // 将刚刚Block 实体指定给 square 

使用Block Pointer的例子

int result = square(5); // 感觉上不就是funtion的用法吗?也可以把Block Pointer当成参数传给一个function,比如说

  1. void myFuction( int (^mySquare) (int) ); // function 的宣告

传入一个有一个int输入和int输出的Block 型别的参数

呼叫这个myFunction的时候就是这样呼叫

  1. int (^mySqaure) (int) = ^(int a) {return a*a;};  
  2. // 先给好一个有实体的block pointer叫mySquare  
  3. myFunction( mySqaure ) ; //把mySquare这个block pointer给myFunction这个function 

或是不用block pointer 直接给一个block 实体,就这样写

  1. myFunction(  ^(int a) {return a*a} ) ; 

当成Objective-C method 的传入值的话都是要把型别写在变数前面然后加上小括号,因些应该就要这样写

  1. -(void) objcMethod:( int (^) (int) ) square; // square 变数的型别是 int (^) (int) 

读文至此是不是对Block有基本的认识? 接下来我们要谈谈Block相关的行为和特色

首先是来看一下在Block里面存取外部变数的方法

存取变数

1. 可以读取和Block pointer同一个scope的变数值:

  1. {  
  2. int outA = 8;  
  3. int (^myPtr) (int) = ^(int a) {return outA+a;};  
  4. // block 里面可以读同一个scope的outA的值  
  5. int result = myPtr(3); // result is 11  

我们再来看一个很有趣的例子

  1. {  
  2. int outA = 8;  
  3. int (^myPtr) (int) = ^(int a) {return outA+a;};  
  4. // block 里面可以读同一个scope的outA的值  
  5. outA = 5; // 在呼叫myPtr之前改变outA的值  
  6. int result = myPtr(3); // result 的值还是 11并不是 8  

事实上呢,myPtr在其主体用到outA这个变数值的时候是做了一个copy的动作把outA的值copy下来。所以之后outA即使换了新的值对于myPtr里copy的值是没有影响到的。
 
要注意的是,这个指的值是变数的值,如果这个变数的值是一个记忆体的位置,换句话说,这个变数是个pointer的话,它指到的值是可以在block里被改变的。

  1. {  
  2.         NSMutableArray * mutableArray = [NSMutableArray arrayWithObjects:@"one",@"two",@"three",nil];  
  3.         int result = ^(int a) { [mutableArray removeLastObject];  return a*a;} (5);  
  4.         NSLog(@"test array %@", mutableArray);  

原本mutableArray的值是{@"one",@"two",@"three"}在block里被更改mutableArray所指向的物件后,mutableArray的值就会被成{@"one",@"two"}

2. 直接存取static 的变数

  1. {  
  2. static int outA = 8;  
  3. int (^myPtr) (int) = ^(int a) {return outA+a;};  
  4. // block 里面可以读同一个scope的outA的值  
  5. outA = 5; // 在呼叫myPtr之前改变outA的值  
  6. int result = myPtr(3); // result 的值是 8,因为outA是个static 变数会直接反应其值  

甚至可以在block里面直接改变outA的值比如这样写

  1. {  
  2. static int outA = 8;  
  3. int (^myPtr) (int) = ^(int a) { outA5; return outA+a;};  
  4. // block 里面改变outA的值  
  5. int result = myPtr(3); // result 的值是 8,因为outA是个static 变数会直接反应其值  

3. Block Variable

在某个变数前面如果加上修饰字__block 的话(注意block前有两个下底线),这个变数又称为block variable。那么在block里就可以任意修改此变数值,变数值的改变也可以知道。

  1. {  
  2.     __block int num = 5;  
  3.  
  4.     int (^myPtr) (int) = ^(int a) { return num++;};  
  5.     int (^myPtr2) (int) = ^(int a) { return num++;};  
  6.     int result = myPtr(0);  
  7.     result = myPtr2(0);  

因为myPtr和myPtr2都有用到num这个block variable,最后result的值就会是7

生命周期和记忆体管理

因为block也是继承自NSObject,所以其生命周期和记忆体的管理也就非常之重要。

block一开始都是被放到stack里,换句话说其生命周期随著method或function结束就会被回收,和一般变数的生命周期一样。

关于记忆体的管理请遵循这几个要点

1. block pointer的实体会在method或function结束后就会被清掉

2. 如果要保存block pointer的实体要用-copy指令,这样block pointer就会被放到heap里

(1)block 主体里用到的block variable 也会被搬到heap 而有新的记忆体位置,且一并更新有用到这个block variable 的block都指到新的位置
    
(2)一般的variable值会被copy 
    
(3)如果主体里用到的variable是object的话,此object会被retain, block release时也会被release
    
(4)block variable 里用到的object是不会被retain的

首先来看一下这个例子

  1. typedef int (^MyBlock)(int);  
  2.  
  3. MyBlock genBlock();  
  4.  
  5. int main(){  
  6.         MyBlock outBlock = genBlock();  
  7.         int result = outBlock(5);  
  8.  
  9.         NSLog(@"result is %d",[outBlock retainCount] ); // segmentation fault  
  10.         NSLog(@"result is %d",result  );  
  11.  
  12.         return 0 ;  
  13. }  
  14. MyBlock genBlock() {  
  15.         int a = 3;  
  16.         MyBlock inBlock = ^(int n) {  
  17.                 return n*a;  
  18.         };  
  19.         return inBlock ;  

此程式由genBlock里产生的block再指定给main function的outBlock变数,执行这个程式会得到

  1. Segmentation fault 

(注:有时候把 genBlock里的a 去掉就可以跑出结果的情形,这是系统cache住记忆体,并不是inBlock真得一直存在,久了还是会被回收,千万不要以为是对的写法)
表示我们用到了不该用的记忆体,在这个例子的情况下是在genBlock里的inBlock变数在return的时候就被回收了,outBlock无法有一个合法的记忆体位置-retainCount就没意义了。

如果这个时候需要保留inBlock的值就要用-copy指令,将genBlock改成

  1.  MyBlock genBlock() {  
  2.         int a = 3;  
  3.         MyBlock inBlock = ^(int n) {  
  4.                 return n*a;  
  5.         };  
  6.         return [inBlock copy]  ;  

这样[inBlock copy]的回传值就会被放到heap,就可以一直使用(记得要release)

执行结果是

  1. result is 1  
  2. result is 15 

再次提醒要记得release outBlock。

如果一回传[inBlock copy]的值就不再需要的时候可以这样写

  1.  MyBlock genBlock() {  
  2.         int a = 3;  
  3.         MyBlock inBlock = ^(int n) {  
  4.                 return n*a;  
  5.         };  
  6.         return [[inBlock copy] autorelease] ;  

-copy指令是为了要把block 从stack搬到heap,autorelease是为了平冲retainCount加到autorelease oop ,回传之后等到事件结束就清掉。

接下来是block存取到的local variable是个物件的型别,然后做copy 指令时

  1. MyBlock genBlock() {  
  2.         int a = 3;  
  3.         NSMutableString * myString = [NSMutableString string];  
  4.         MyBlock inBlock = ^(int n) {  
  5.                 NSLog(@"retain count of string %d",[myString retainCount]);  
  6.                 return n*a;  
  7.         };  
  8.         return [inBlock copy] ;  

结果会印出

  1. retain count of string 2 

这个结果和上面2.3提到的一样,local variable被retain了

那再来试试2.4,在local variable前面加上__block

  1. MyBlock genBlock() {  
  2.         int a = 3;  
  3.         __block NSMutableString * myString = [NSMutableString string];  
  4.         MyBlock inBlock = ^(int n) {  
  5.                 NSLog(@"retain count of string %d",[myString retainCount]);  
  6.                 return n*a;  
  7.         };  
  8.         return [inBlock copy] ;  

执行的结果就是会

  1. 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

  1. @interface MyObject : NSObject {  
  2.         NSString * title;  
  3.         void (^myLog) (NSString * deco);  
  4. }  
  5.  
  6. -(void) logName;  
  7. @end  
  8. @implementation MyObject  
  9. -(id) initWithTitle:(NSString * ) newTitle{  
  10.         if(self = [super init]){  
  11.                 title = newTitle;  
  12.                 myLog = [^(NSString * deco) { NSLog(@"%@%@%@",deco, title, deco );} copy];  
  13.         }  
  14.         return self;  
  15. }  
  16.  
  17. -(void) logName{  
  18.  myLog(@"==");  
  19. }  
  20.  
  21. -(void ) dealloc{  
  22.  
  23.         [myLog release];  
  24.         [title release];  
  25.         [super dealloc];  
  26. }  
  27. @end 

在main 里使用如下

  1. MyObject * mObj = [[MyObject alloc] initWithTitle:@"Car"];  
  2. NSLog(@"retainCount of MyObject is  %d",[mObj retainCount]  );  
  3. [mObj logName]; 

其执行的结果为

  1. retainCount of MyObject is  2 
  2. ==Car== 

因为在MyObject的建构子里myLog这个block pointer用了title这个instance variable然后就会retain self也就是MyObject的物件。

尽量不要这样写,会造成retain cycle,改善的方法是把建构子改成这样

  1. -(id) initWithTitle:(NSString * ) newTitle{  
  2.         if(self = [super init]){  
  3.                 title = newTitle;  
  4.                 myLog = [^(NSString * deco) { NSLog(@"%@%@%@",deco, newTitle, deco );} copy];  
  5.         }  
  6.         return self;  

在Block主体里用newTitle这个变数而不是title。这样self就不会被retain了。

最后谈一个小陷井

  1. void (^myLog) (void);   
  2. BOOL result ;  
  3. if(result)  
  4.     myLog = ^ {NSLog(@"YES");};  
  5.  
  6. else  
  7.     myLog = ^ {NSLog(@"NO");};  
  8.  
  9. myLog(); 

这样很可能就会当掉了,因为myLog 实体在if 或是else结束后就被清掉了。要记得。

要用copy来解决这个问题,但要记得release

小结:详解Objective-c block应用的内容介绍完了,希望本文对你有所帮助!更多相关内容请参考编辑推荐。

原文地址:http://mobile.51cto.com/iphone-279757.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章