標籤:
2007年的時候,Objective-C在TIOBE程式設計語言排名裡還排在可憐的第45位,而隨著移動互連網的迅速發展和iPhone,iPad等iOS裝置的廣闊市場前景,Objective-C也迅速崛起,走進了開發人員的視野。在最近的TIOBE排名中,Objective-C達到了驚人的第4名,可以說已經成為當今世界上一門非常重要的程式設計語言。
而Objective-C現在主要是由Apple在負責維護了。一直以來Apple為了適應開發的發展需要,不斷在完善OC以及相應的cocoa庫,2.0中引入的property,隨著iOS4引入的block,以及去年引入的ARC,都受到了絕大部分開發人員的歡迎。幾乎每年都有重大特性的加入,這不是每種語言都能做到的,更況且這些特性都為大家帶來了眾多的便利。
今年WWDC也不例外,OC和LLVM將得到重大的改進。本文將對這些改進進行一個簡單整理和評述。
方法順序
如果有以下代碼:
@interface SongPlayer : NSObject - (void)playSong:(Song *)song; @[email protected] SongPlayer - (void)playSong:(Song *)song { NSError *error; [self startAudio:&error]; //... } - (void)startAudio:(NSError **)error { //... } @end
在早一些的編譯環境中,上面的代碼會在[self startAudio:&error]處出現一個執行個體方法未找到的警告。由於編譯順序,編譯器無法得知在-playSong:方法之後還有一個-startAudio:,因此給出警告。以前的解決方案有兩種:要麼將-startAudio:的實現移到-playSong:的上方,要麼在類別中聲明-startAudio:(順便說一句..把-startAudio:直接拿到.h檔案中是完全錯誤的做法,因為這個方法不應該是public的)。前者破壞.m檔案的結構打亂了方法排列的順序,導致以後維護麻煩;後者要寫額外的不必要代碼,使.m檔案變長。其實兩種方法都不是很好的解決方案。
現在不需要再頭疼這個問題了,LLVM中加入了新特性,現在直接使用上面的代碼,不需要做額外處理也可以避免警告了。新編譯器改變了以往順序編譯的行為,改為先對方法申明進行掃描,然後在對方法具體實現進行編譯。這樣,在同一實現檔案中,無論方法寫在哪裡,編譯器都可以在對方法實現進行編譯前知道所有方法的名稱,從而避免了警告。
枚舉改進
從Xcode4.4開始,有更好的枚舉的寫法了:
typedef enum NSNumberFormatterStyle : NSUInteger { NSNumberFormatterNoStyle, NSNumberFormatterDecimalStyle, NSNumberFormatterCurrencyStyle, NSNumberFormatterPercentStyle, NSNumberFormatterScientificStyle, NSNumberFormatterSpellOutStyle } NSNumberFormatterStyle;
在列出枚舉列表的同時綁定了枚舉類型為NSUInteger,相比起以前的直接枚舉和先枚舉再綁定類型好處是方便編譯器給出更準確的警告。個人覺得對於一般開發人員用處並不是特別大,因為往往並不會涉及到很複雜的枚舉,用以前的枚舉申明方法也不至於就搞混。所以習慣用哪種枚舉方式還是接著用就好了..不過如果有條件或者還沒有形成自己的習慣或者要開新工程的話,還是嘗試一下這種新方法比較好,因為相對來說要嚴格一些。
屬性自動綁定
人人都愛用property,這是毋庸置疑的。但是寫property的時候一般都要對應寫執行個體變數和相應的synthesis,這實在是一件讓人高興不起來的事情。Apple之前做了一些努力,至少把必須寫執行個體變數的要求去掉了。在synthesis中等號後面的值即為實力變數名。現在Apple更進一步,給我們帶來了非常好的訊息:以後不用寫synthesis了!Xcode 4.4之後,synthesis現在會對應property自動產生。
預設行為下,對於屬性foo,編譯器會自動在實現檔案中為開發人員補全synthesis,就好像你寫了@synthesis foo = _foo;一樣。預設的執行個體變數以底線開始,然後接屬性名稱。如果自己有寫synthesis的話,將以開發人員自己寫的synthesis為準,比如唯寫了@synthesis foo;那麼執行個體變數名就是foo。如果沒有synthesis,而自己又實現了-foo以及-setFoo:的話,該property將不會對應執行個體變數。而如果只實現了getter或者setter中的一個的話,另外的方法會自動協助產生(即使沒有寫synthesis,當然readonly的property另說)。
對於寫了@dynamic的實現,所有的對應的synthesis都將不生效(即使沒有寫synthesis,這是runtime的必然..),可以理解為寫了dynamic的話setter和getter就一定是運行時確定的。
總結一下,新的屬性綁定規則如下:
- 除非開發人員在實現檔案中提供getter或setter,否則將自動產生
- 除非開發人員同時提供getter和setter,否則將自動產生執行個體變數
- 只要寫了synthesis,無論有沒有跟執行個體變數名,都將產生執行個體變數
- dynamic優先順序高於synthesis
簡寫
OC的文法一直被認為比較麻煩,絕大多數的訊息發送都帶有很長的函數名。其實這是一把雙刃劍,好的方面,它使得代碼相當容易閱讀,因為幾乎所有的方法都是以完整的英語進行描述的,而且如果遵守命名規則的話,參數類型和方法作用也一清二楚,但是不好的方面,它使得coding的時候要多不少不必要的鍵盤敲擊,降低了開發效率。Apple意識到了這一點,在新的LLVM中引入了一系列列規則來簡化OC。經過簡化後,以降低部分可讀性為代價,換來了開發時候稍微快速一些,可以說比較符合現在短開發週期的需要。簡化後的OC代碼的樣子向Perl或者Python這樣的快速開發語言靠近了一步,至於實際用起來好不好使,就還是仁智各異了…至少我個人對於某些簡寫不是特別喜歡..大概是因為看到簡寫的代碼還沒有形成直覺,總要反應一會兒才能知道這是啥…
NSNumber
所有的[NSNumber numberWith…:]方法都可以簡寫了:
[NSNumber numberWithChar:‘X’]
簡寫為 @‘X’
;
[NSNumber numberWithInt:12345]
簡寫為 @12345
[NSNumber numberWithUnsignedLong:12345ul]
簡寫為 @12345ul
[NSNumber numberWithLongLong:12345ll]
簡寫為 @12345ll
[NSNumber numberWithFloat:123.45f]
簡寫為 @123.45f
[NSNumber numberWithDouble:123.45]
簡寫為 @123.45
[NSNumber numberWithBool:YES]
簡寫為 @YES
嗯…方便很多啊~以前最討厭的就是數字放Array裡還要封裝成NSNumber了…現在的話直接用@開頭接數字,可以簡化不少。
NSArray
部分NSArray方法得到了簡化:
[NSArray array]
簡寫為 @[]
[NSArray arrayWithObject:a]
簡寫為 @[ a ]
[NSArray arrayWithObjects:a, b, c, nil]
簡寫為 @[ a, b, c ]
可以理解為@符號就表示NS對象(和NSString的@號一樣),然後接了一個在很多其他語言中常見的方括弧[]來表示數組。實際上在我們使用簡寫時,編譯器會將其自動翻譯補全為我們常見的代碼。比如對於@[ a, b, c ],實際編譯時間的代碼是
// compiler generates:id objects[] = { a, b, c }; NSUInteger count = sizeof(objects)/ sizeof(id); array = [NSArrayarrayWithObjects:objects count:count];
需要特別注意,要是a,b,c中有nil的話,在產生NSArray時會拋出異常,而不是像[NSArray arrayWithObjects:a, b, c, nil]那樣形成一個不完整的NSArray。其實這是很好的特性,避免了難以尋找的bug的存在。
NSDictionary
既然數組都簡化了,字典也沒跑兒,還是和Perl啊Python啊Ruby啊很相似,意料之中的寫法:
[NSDictionary dictionary]
簡寫為 @{}
[NSDictionary dictionaryWithObject:o1 forKey:k1]
簡寫為 @{ k1 : o1 }
[NSDictionary dictionaryWithObjectsAndKeys:o1, k1, o2, k2, o3, k3, nil]
簡寫為 @{ k1 : o1, k2 : o2, k3 : o3 }
和數組類似,當寫下@{ k1 : o1, k2 : o2, k3 : o3 }時,實際的代碼會是
// compiler generates: id objects[] = { o1, o2, o3 }; id keys[] = { k1, k2, k3 }; NSUInteger count = sizeof(objects) / sizeof(id); dict = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:count];
Mutable版本和靜態版本
上面所產生的版本都是不可變的,想得到可變版本的話,可以對其發送-mutableCopy訊息以產生一份可變的拷貝。比如
NSMutableArray *mutablePlanets = [@[ @"Mercury", @"Venus", @"Earth", @"Mars", @"Jupiter", @"Saturn", @"Uranus", @"Neptune" ] mutableCopy];
另外,對於標記為static的數組(沒有static的字典..雜湊和排序是在編譯時間完成的而且cocoa架構的key也不是常數),不能使用簡寫為其賦值(其實原來的傳統寫法也不行)。解決方案是在類方法+ (void)initialize中對static進行賦值,比如:
static NSArray *thePlanets; + (void)initialize { if (self == [MyClass class]) { thePlanets = @[ @"Mercury", @"Venus", @"Earth", @"Mars", @"Jupiter", @"Saturn", @"Uranus", @"Neptune" ]; } }
下標
其實使用這些簡寫的一大目的是可以使用下標來訪問元素:
[_array objectAtIndex:idx]
簡寫為 _array[idx]
;
[_array replaceObjectAtIndex:idx withObject:newObj]
簡寫為 _array[idx] = newObj
[_dic objectForKey:key]
簡寫為 _dic[key]
[_dic setObject:object forKey:key]
簡寫為 _dic[key] = newObject
很方便,但是一定需要注意,對於字典用的也是方括弧[],而不是想象中的花括弧{}。估計是想避免和代碼塊的花括弧發生衝突吧…簡寫的實際工作原理其實真的就只是簡單的對應的方法的簡寫,沒有什麼驚喜。
但是還是有驚喜的..那就是使用類似的一套方法,可以做到對於我們自己的類,也可以使用下標來訪問。而為了達到這樣的目的,我們需要實現以下方法,
對於類似數組的結構:
- (elementType)objectAtIndexedSubscript:(indexType)idx; </pre>- (void)setObject:(elementType)object atIndexedSubscript:(indexType)idx;
對於類似字典的結構:
- (elementType)objectForKeyedSubscript:(keyType)key; </pre>- (void)setObject:(elementType)object forKeyedSubscript:(keyType)key;
固定橋接
對於ARC來說,最讓人迷惑和容易出錯的地方大概就是橋接的概念。由於曆史原因,CF對象和NSObject對象的轉換一直存在一些微妙的關係,而在引入ARC之後,這些關係變得複雜起來:主要是要明確到底應該是由CF還是由NSObject來負責記憶體管理的問題(關於ARC和更詳細的說明,可以參看我之前寫的一篇ARC入門教程)。
在Xcode4.4之後,之前區分到底誰擁有對象的工作可以模糊化了。在代碼塊區間加上CFIMPLICITBRIDGINGENABLED和CFIMPLICITBRIDGINGDISABLED,在之前的橋接轉換就都可以簡單地寫作CF和NS之間的強制轉換,而不再需要加上__bridging的關鍵字了。誰來管這塊記憶體呢?交給系統去頭疼吧~
Objective-C確實是一門正在高速變化的語言。一方面,它的動態特性和small talk的烙印深深不去,另一方面,它又正積極朝著各種簡單語言的文法方向靠近。各類的自動化處理雖然有些讓人不放心,但是事實證明了它們工作良好,而且也確實為開發人員節省了時間。儘快努力去擁抱新的變化吧~
objective-c 新文法特性