C++轉Objective-c的糾結惆悵 —— objective-c的怪異特性

前言

應公司要求,最近開始做IOS應用了,這意味着 什麼?全新的語法,全新的技術,全新得框架都要等着我去熟悉呢。。 對於我一個傳統的C++程序員來說,理論上要熟悉Objective-C的語法當然用時不多了,只是接觸之後才發現,這語法讓人糾結到頭皮發麻,全身發癢 (說的貌似有點過了)。其實幾天前就想寫這篇關於OBJECT-C語法方面的博文了,只是一直苦於上班不能寫博客,下班又沒MAC機用的惡劣境況,才推遲 到現在。好了,言歸正傳了。

 

令人糾結到髮指的Foundation Kit

先來看看有關Foundation中幾個簡單class的實例:

  1. int main(int argc, const char * argv[]) 
  2.     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
  3.     
  4.     NSArray *array_static = [[NSArray alloc] initWithObjects:@"one",@"two",@"three", nil]; 
  5.     for (int i = 0; i < [array_static count]; i++) { 
  6.         NSLog(@"%@",[array_static objectAtIndex:i]); 
  7.     } 
  8.     
  9.     NSMutableArray *array_dynamic = [[NSMutableArray alloc] init]; 
  10.     for (int i = 0; i < 3; i++) { 
  11.         NSNumber *number1 = [NSNumber numberWithInt:i+1]; 
  12.         [array_dynamic addObject:number1]; 
  13.     } 
  14.      
  15.     for(int i = 0; i < [array_dynamic count]; i++) 
  16.     { 
  17.         NSLog(@"%i",[[array_dynamic objectAtIndex:i] intValue]); 
  18.     } 
  19.     [array_dynamic removeAllObjects]; 
  20.      
  21.     NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithCapacity:3]; 
  22.      
  23.     [map setValue:@"one" forKey:@"1"]; 
  24.     [map setValue:@"two" forKey:@"2"]; 
  25.     [map setValue:@"three" forKey:@"3"]; 
  26.      
  27.     for(int i = 0; i < [map count]; i++) 
  28.     { 
  29.         NSLog(@"%@",[map objectForKey:[NSString stringWithFormat:@"%i",i+1]]); 
  30.     } 
  31.     [array_dynamic release]; 
  32.     [array_static release]; 
  33.     [map release]; 
  34.     [pool drain]; 
  35.     return 0; 

自從我接觸到Obj-c中的NSArray,我便一直想不通一個問題:既然NSArray不能動態改變其大小,而且 Foundation kit中還存在一個NSMutableArray可以彌補NSArray的這種缺陷,甚至能提供比NSArray更多的功能,那麼NSArray還有什麼 存在的必要呢? 有人說NSArray的效率比NSMutableArray的要高一些,我便更不能理解了——連C++這種直接操作內存的高效語言都沒搞一個不能改變大小 的Array Class出來,OBJ-C緣何還要來畫蛇添個足呢?用NSMutableArray來完全代替NSArray不好嗎?這問題一直困擾着我 。。  直到剛剛纔猛然間的豁然開朗了一下:C++完全沒必要實現的這麼複雜,有個跟NSArray功能相近的數組就行了。 如果從效率上面來理解的話,NSArray絕對是一次性分配固定內存的,而NSMutableArray則是動態分配內存的,倘若事先知道一個數組的大 小,這時NSArray就能展現出比NSMutableArray更高的效率了。。。 而對於CCArray和CCMutableArray不能存儲基本內置數據類型而只能存儲OBJ-C對象時,又讓我糾結不已。。。 STL中的所有容器可不帶這樣的。。。  能想到的是CCArray內部存儲對象實際是在存儲對象的地址(指針)而非對象的拷貝,否則便不會有此限制。All in all, NSArray的存在絕對是有必要的。

另外對於NSDictionary,我先前以爲它是個和STL中MAP功能相似的類,只不過改了個名 字罷了,現在看來卻非如此。事實上,map中對於Key的要求是很隨意得,只要能比較大小即可(比如數字或字符串甚至任意重載了“<"運算符的 class),而NSDictonary的Key必須是NSString類型。。如此的限制再次讓我糾結到淚奔。。。

從2d開源引擎cocos2d-x看objective-c的內存管理機制

Object-C中對於內存管理不像C++那樣隨意創建和釋放,也不再像JAVA中那樣只管NEW不管釋放了。 所有的內存管理都是通過Reference Count機制來實現的。 關於Reference Count機制,在我之前一篇博文

《主流RAII class的存在價值——不存在能夠完全替代Dumb Pointer的RAII class 》

中 講解shared_ptr時詳細講解過其原理,而Object-c中所有Counting機制已經不再那麼透明瞭,蘋果規定給你怎麼用你就得怎麼用,要想 知道其內部實現機制只能透過表象來猜測了。。 但有一點我一直感到很慶幸,那便是:目前比較主流的2D遊戲引擎cocos2d-x作爲cocos2d的C++版本,簡直就是用C++把OBJ-C全然模 擬了一遍。 對於其Reference Count機制,尤其是AutoReleasePool的實現着實讓人津津樂道。以下附上cocos2d-x中AutoReleasePool頭文件部 分:

  1. namespace cocos2d { 
  2. class CC_DLL CCAutoreleasePool : public CCObject 
  3.     CCMutableArray<CCObject*>*  m_pManagedObjectArray;   
  4. public
  5.     CCAutoreleasePool(void); 
  6.     ~CCAutoreleasePool(void); 
  7.  
  8.     void addObject(CCObject *pObject); 
  9.     void removeObject(CCObject *pObject); 
  10.  
  11.     void clear(); 
  12. }; 
  13.  
  14. class CC_DLL CCPoolManager 
  15.     CCMutableArray<CCAutoreleasePool*>* m_pReleasePoolStack;     
  16.     CCAutoreleasePool*                  m_pCurReleasePool; 
  17.  
  18.     CCAutoreleasePool* getCurReleasePool(); 
  19. public
  20.     CCPoolManager(); 
  21.     ~CCPoolManager(); 
  22.     void finalize(); 
  23.     void push(); 
  24.     void pop(); 
  25.  
  26.     void removeObject(CCObject* pObject); 
  27.     void addObject(CCObject* pObject); 
  28.  
  29.     static CCPoolManager* getInstance(); 
  30.  
  31.     friend class CCAutoreleasePool; 
  32. }; 
  33.  

 

看的出其有一個對象池:CCMutableArray用來存儲所有加入到內存池中的對象。當對象使用autorelease時候, 究其源碼在把對象放入到內存池的同時不影響對象本身的Counting值。而用new的話會在基類CCObject的構造函數中中計數值設爲1。如果 counting機制的retain和release用的很混亂會如何呢?要麼對象早釋放,要麼晚釋放。。 對於晚釋放的,事實上算內存泄露,而對於早釋放的,在debug狀態下便會出異常。

在object-c中對於一些其它創建對象的方式如:initwithXXXX之類的,在cocos2d-x中用C++模擬時候實際是在內部調用node方法,此方法通過宏定義來實現,通常是這樣的:

  1. #define LAYER_NODE_FUNC(layer) \ 
  2. static layer* node() \ 
  3. { \ 
  4. layer *pRet = new layer(); \ 
  5. if (pRet && pRet->init()) \ 
  6. { \ 
  7. pRet->autorelease(); \ 
  8. return pRet; \ 
  9. } \ 
  10. else \ 
  11. { \ 
  12. delete pRet; \ 
  13. pRet = NULL; \ 
  14. return NULL; \ 
  15. } \ 
  16. };  

即先new後autorelease,如此明瞭不必再多做闡釋了。。 對於cocos2d-x的開源團隊能將object-c模擬的如此精緻,至此,不得不感嘆開源社區的強大。。。

使用category和protocol替換掉C++中的virtual function以達到多態的另類實現

對於Object-c的Category(類別),看起來是對C++中的 vitrual function的缺陷的改善,因爲形式上而言,子類(這樣說貌似有些不恰當,暫且這樣稱呼)是純粹對父類的方法進行擴充而不需要重新繼承父類很多信息, 以此便省出了virtual table的創建空間。。  也因爲如此,可以只對方法進行聲明而不進行實現,如此又有些像C++的特性,在object-c中又把這種在category的特性叫做“非正式協議”, 因爲子類可以拓展並覆蓋(同名情況)父類的方法。其另外一顯著作用便是可以將龐大的類分散到很多小塊中去實現,以使得條理更加清晰。。  以下是用category實現的一個proxy(代理)模式。這種模式在OBJ-C中被大量用到,現假設有個Cwnd(窗口)類,它接受點擊消息但其類內 部本身不去處理,將其委託給WindowMessageCategory去處理,代碼如下: 

  1. // 
  2. //  main.m 
  3. //  Language_Project 
  4. // 
  5. //  Created by wei yang on 12-6-27. 
  6. //  Copyright (c) 2012年 __MyCompanyName__. All rights reserved. 
  7. // 
  8.  
  9. #import <Foundation/Foundation.h> 
  10.  
  11. @interface CWnd : NSObject 
  12.     id delegate; 
  13. -(id) WindowOnClick; 
  14.  
  15. @end 
  16.  
  17. @implementation CWnd 
  18.  
  19. -(id) init 
  20.     if(self = [super init]) 
  21.     { 
  22.         delegate = self; 
  23.     } 
  24.     return self; 
  25. -(id) WindowOnClick 
  26.     NSLog(@"window on click"); 
  27.     if([delegate respondsToSelector:@selector(respondsClick)]) 
  28.     { 
  29.         [delegate performSelector:@selector(respondsClick)]; 
  30.     } 
  31.     NSLog(@"click finished!"); 
  32.     return nil; 
  33. @end 
  34.  
  35. @interface CWnd(WindowMessageCategory) 
  36. -(void) respondsClick; 
  37. @end 
  38.  
  39. @implementation CWnd(WindowMessageCategory) 
  40.  
  41. -(void) respondsClick 
  42.     NSLog(@"the click event has been responded"); 
  43.  
  44. @end 
  45.  
  46. int main(int argc, const char * argv[]) 
  47.     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
  48.     CWnd *window = [[CWnd alloc] init]; 
  49.     [window WindowOnClick]; 
  50.     [window release]; 
  51.     [pool drain]; 
  52.     return 0; 

在C++中,運行時的多態可通過繼承和虛函數來實現,而在Object-c中沒有虛函數一說,取而代之的是協議。聲明一個協議相當於聲明瞭一個 pure virtual class,讓子類實現其定義的接口。而多態的實現則是通過繼承,協議以及委託代理三者結合來實現的。對於上述中用category來實現的proxy模 式,用protocol的方式實現如下:

  1. // 
  2. //  main.m 
  3. //  Language_Project 
  4. // 
  5. //  Created by wei yang on 12-6-27. 
  6. //  Copyright (c) 2012年 __MyCompanyName__. All rights reserved. 
  7. // 
  8.  
  9. #import <Foundation/Foundation.h> 
  10.  
  11. @protocol ResPondsWindowMessage <NSObject> 
  12.  
  13. @optional         
  14. -(void) RespondsClick; 
  15.  
  16. @end 
  17.  
  18. @interface CWnd:NSObject<ResPondsWindowMessage> 
  19.     id <ResPondsWindowMessage> delegate;  
  20.  
  21. -(id) WindowOnClick; 
  22. @end 
  23.  
  24. @implementation CWnd 
  25. -(id) init 
  26.     if(self = [super init]) 
  27.     { 
  28.         delegate = self; 
  29.     } 
  30.     return self; 
  31. -(id) WindowOnClick 
  32.      
  33.     NSLog(@"window on click!"); 
  34.     if([delegate conformsToProtocol:@protocol(ResPondsWindowMessage)] 
  35.        && [delegate respondsToSelector:@selector(RespondsClick)] ) 
  36.         { 
  37.             [delegate performSelector:@selector(RespondsClick)]; 
  38.         } 
  39.     NSLog(@"click finished!"); 
  40.     return nil; 
  41. @end 
  42.  
  43. @interface MyWnd : CWnd 
  44. -(void) RespondsClick; 
  45. @end 
  46.  
  47. @implementation MyWnd 
  48.  
  49. -(id) init 
  50.     if(self = [super init]) 
  51.     { 
  52.         delegate = self; 
  53.     } 
  54.     return self; 
  55.  
  56. -(void) RespondsClick 
  57.     NSLog(@"click event has been responded!"); 
  58.  
  59. @end 
  60.  
  61. int main(int argc, const char * argv[]) 
  62.     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
  63.     CWnd *window = [[MyWnd alloc] init]; 
  64.     [window WindowOnClick]; 
  65.     [window release]; 
  66.     [pool drain]; 
  67.     return 0; 

對於category和protocol的闡述大概差不多了。 在這裏我並沒有很詳細的說明使用其時需要注意的地方,這些基本的東西可以去看《objective-c 2.0基礎教程》,其實旨在指出Objective-c與才C++不同和相似的地方,進行對比說明的同時,還以此達到減少自己對於這種“怪異”語言困惑和 糾結的地方。

 

結束語

這篇博文已經寫了兩天了,是在上班時間抽時間些的。主要是回家沒MAC玩,就沒辦法編代碼寫博客了。對於objective-c的語言類學習,到此也算畫上個句號了,以後的時間可能都要花在實際項目上了。對於之前寫C++的我來說,糾結和困惑始終伴隨着我學object-c的過程,但既然轉到這個平臺了,貌似只有慢慢習慣才行,畢竟,習慣成自然嘛。。。。 路漫漫其修遠兮,吾將上下而求索。

 

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