多年來,Objective-C語言已經有了革命性的發展。雖然核心理念和實踐保持不變,但語言中的部分內容經歷了重大的變化和改進。現代化的Objective-C在類型安全、內存管理、性能、和其他方面都得到了增強。使你更容易編寫正確的代碼。在你現有和未來的代碼中使用這些改進是很重要的,會使你的代碼一致、可讀、靈活。
Xcode提供了一個工具來幫助做這些結構性的變化。但在使用這個工具之前,你想了解工具爲你的代碼做了什麼改變以及爲什麼。本文強調了一些最重要的和有用的現代化方式可以用在你的代碼中。
instancetype
使用instancetype關鍵字作爲返回類型的方法,該方法返回一個類的實例(或該類的子類)這些方法包括alloc,init,和類工廠方法。
使用instancetype代替id在適當的地方可以改善Objective-C代碼類型安全。例如:考慮下面的代碼:
- @interface MyObject : NSObject
- + (instancetype)factoryMethodA;
- + (id)factoryMethodB;
- @end
- @implementation MyObject
- + (instancetype)factoryMethodA
- {
- return [[[self class] alloc] init];
- }
- + (id)factoryMethodB
- {
- return [[[self class] alloc] init];
- }
- @end
- void doSomething()
- {
- NSUinteger x, y;
- // Return type of +factoryMethodA is taken to be "MyObject *"
- x = [[MyObject factoryMethodA] count];
- // Return type fo +factoryMethodB is "id"
- y = [[MyObject factoryMethodB] count];
- }
因爲+factoryMethodA使用了instancetype作爲返回類型,該消息的類型表達式爲MyObject *.當MyObject沒有-count方法的時候,編譯器會發出警告的在x行:
- main.m: 'MyObject' may not respond to 'count'
然而,由於+factoryMethodB返回類型爲id,編譯器不可以給出警告。因爲一個id可以是任何類型的對象類,由於存在一個名爲-count的方法在一些類中,編譯器可能返回一個+factoryMehtodB的實現的方法。
確保instancetype工廠方法有權利子類化行爲,在初始化的時候一定要使用[self class]而不是直接引用的類名。遵循這個慣例確保編譯器將正確判斷出子類的類型。例如:考慮嘗試這樣做一個MyObject的子類從前面的示例:
- @interface MyObjectSubclass : MyObject
- @end
- void doSomethingElse()
- {
- NSString *aString = [MyObjectSubclass factoryMethodA];
- }
關於這個代碼編譯器將給出下面的警告:
- main.m: Incompatible pointer types initializing 'NSString *'
- with an expression of type 'MyObjectSubclass *'
在這個例子中,+factoryMethodA MyObjectSubclass類型的消息發送返回一個對象,這是接收者的類型。編譯器確定適當的返回類型+factoryMethodA應該是MyObjectSubclass子類,而不是超類的工廠方法被調用。
怎樣適配
在你的代碼中,出現id作爲返回值替換爲instancetype在適當的地方。這通常是init方法和類的工廠方法。甚至編譯器會自動轉以“alloc”、“init”、“new”開頭的方法,而不轉換其他的方法。objective-c對instancetype的轉換是顯式的方式。
請注意,您僅應該用instancetype替換id作爲返回值,而不是在你的代碼的任何地方都這麼做。不像id,instancetype在聲明方法時僅僅只能作爲返回值類型使用。
例如:
- @interface MyObject
- - (id)myFactoryMethod;
- @end
應該變爲:
- @interface MyObject
- - (instancetype)myFactoryMethod;
- @end
或者,您可以在Xcode使用現代objective-c變換器自動進行轉換您的代碼。更多信息請看使用Xcode重構你的代碼。
採用現代Objective-C (2)
Properties
一個public或private的Objective-C屬性使用@property語法聲明的。
- @property (readonly, getter=isBlue) BOOL blue;
屬性持有着一個對象的狀態。他們反映對象的本質屬性和其他對象的關係。Properties提供一個安全、方便的方式來定義這些屬性,而無需編寫一組自定義訪問器方法(雖然屬性允許定製的getter和setter,如果需要的話)。
使用屬性而不是實例變量在儘可能多的地方提供了許多好處:
1.自動合成getters和setters。當你聲明一個屬性,默認情況下爲你創建getter和setter方法。
2.更好的意圖聲明一組方法。因爲訪問器方法的命名約定方便,很明顯getter和setter方法是做什麼的。
3.property關鍵字表示關於行爲的額外信息。屬性提供潛在的聲明的屬性像assign(vs copu),weak,atomic(vs nonatomic),等等。
屬性方法遵循一個簡單的命名約定。getter屬性的名稱(例如,date),setter屬性在名稱前加前綴,按駝峯式命名書寫(例如,setDate)。Boolean屬性的命名約定是聲明他們的getter方法以'is'開頭。
- @property (readonly, getter=isBlue) BOOL blue;
因此,以下所有調用方式都可以正常工作:
- if(color.blue){}
- if(color.isBlue){}
- if([color isBlue]){}
在決定什麼可能是一個屬性時,記住,如下不是屬性:
1.init方法
2.copy方法,mutableCopy方法
3.一個類的工廠方法
4.一個初始化一個動作並返回BOOL值的方法
5.一個明確改變內部狀態對getter有副作用的方法
此外,考慮以下的規則集當在你的代碼中定義屬性時:
1.一個讀/寫屬性有兩個訪問器方法。setter接受一個參數什麼也不返回,getter不接受參數並返回一個值。可以用readwrite關鍵字設置這個屬性。
2.一個只讀屬性有一個訪問器方法,getter不接受參數並返回一個值。可以使用readonly關鍵字設置。
3.getter應該冪等的(如果一個getter方法調用了兩次,那麼第二次結果應該和第一次是相同的)。
但是,每次geeter被調用返回結果是可接受的。
怎樣適配
定義一組方法,有資格被轉換成屬性,諸如此類的:
- - (NSColor *)backgroundColor;
- - (void)setBackgroundColor:(NSColor *)color;
用@property語法和其他合適的關鍵字定義他們:
- @property (copy) NSColor *backgroundColor;
更多關於property關鍵字和其他的信息,請看“Encapsulating Data”
採用現代Objective-C (3)
Enumeration Macros
NS_ENUM和NS_OPTIONS宏提供一個簡潔、簡單的定義枚舉的方法和基於c語言的選項。這些宏在Xcode中實現可以顯式地指定枚舉類型和選項的大小。此外,這種由舊的編譯器語法聲明枚舉的方式,可以被新的編譯器正確評估和解釋潛在的類型信息。
使用NS_ENUM宏定義枚舉,互斥的一組值:
- typedef NS_ENUM(NSInteger, UITableViewCellStyle){
- UITableViewCellStyleDefault,
- UITableViewCellStyleValue1,
- UITableViewCellStyleValue2,
- UITableViewCellStyleSubtitle
- }
NS_ENUM宏幫助定義枚舉的名稱和類型,在本例中名稱爲UITableViewCellStyle類型爲NSInteger。枚舉類型應該是NSInteger。
使用NS_OPTIONS宏來定義選項,一組位掩碼值,可以組合在一起:
- typedef NS_OPTIONS(NSUInteger, UIViewAutoresizeing){
- UIViewAutoresizeingNone = 0,
- UIViewAutoresizeingFlexibleLeftMargin = 1 << 0,
- UIViewAutoresizeingFlexibleWidth = 1 << 1,
- UIViewAutoresizeingFlexibleRightMargin = 1 << 2,
- UIViewAutoresizeingFlexibleTopMargin = 1 << 3,
- UIViewAutoresizeingFlexibleHeight = 1 << 4,
- UIViewAutoresizeingFlexibleBottomMargin = 1 << 5
- }
像這樣的枚舉,NS_OPTIONS宏定義一個名稱和一個類型。然而,通常類型應該是NSUInteger。
怎樣適配
代替你的枚舉聲明,如:
- enum{
- UITableViewCellStyleDefault,
- UITableViewCellStyleValue1,
- UITableViewCellStyleValue2,
- UITableViewCellStyleSubtitle
- };
- typedef NSInteger UITableViewCellStyle;
用NS_ENUM語法:
- typedef NS_ENUM(NSInteger, UITableViewCellStyle){
- UITableViewCellStyleDefault,
- UITableViewCellStyleValue1,
- UITableViewCellStyleValue2,
- UITableViewCellStyleSubtitle
- }
但是,當你使用enum去定義一個位掩碼,像這樣:
- enum {
- UIViewAutoresizeingNone = 0,
- UIViewAutoresizeingFlexibleLeftMargin = 1 << 0,
- UIViewAutoresizeingFlexibleWidth = 1 << 1,
- UIViewAutoresizeingFlexibleRightMargin = 1 << 2,
- UIViewAutoresizeingFlexibleTopMargin = 1 << 3,
- UIViewAutoresizeingFlexibleHeight = 1 << 4,
- UIViewAutoresizeingFlexibleBottomMargin = 1 << 5
- };
- typedef NSUInteger UIViewAutoresizing;
用NS_OPTIONS宏:
- typedef NS_OPTIONS(NSUInteger, UIViewAutoresizeing){
- UIViewAutoresizeingNone = 0,
- UIViewAutoresizeingFlexibleLeftMargin = 1 << 0,
- UIViewAutoresizeingFlexibleWidth = 1 << 1,
- UIViewAutoresizeingFlexibleRightMargin = 1 << 2,
- UIViewAutoresizeingFlexibleTopMargin = 1 << 3,
- UIViewAutoresizeingFlexibleHeight = 1 << 4,
- UIViewAutoresizeingFlexibleBottomMargin = 1 << 5
- }
或者,您可以在Xcode使用現代objective-c變換器自動進行轉換您的代碼。更多信息請看使用Xcode重構你的代碼。
Automatic Reference Counting (ARC)
自動引用計數(ARC)是一個編譯器特性,它提供了Objective-C對象的自動內存管理。代替你不必記得使用retain,release和autorelease。ARC評估對象的生命週期需求並自動插入適當的內存管理要求在編譯時間。編譯器也會爲你產生適當的dealloc方法。
怎樣適配
Xcode提供了一個工具,自動化轉換的(如刪除retain和release調用)幫助你解決不能自動修復的問題。使用ARC工具:選擇Edit > Refactor > Convert to Objective-C ARC。這個工具轉換項目中所有的文件使用ARC。
更多的信息,看Transitioning to ARC Release Notes.
Refactoring Your Code Using Xcode
Xcode提供了一個現代objective - c變換器,在轉向現代化過程中可以幫助你。雖然轉換器有助於識別和潛在應用現代化的機制,但它沒有解釋代碼的語義。例如,它不會發現-toggle方法是一種動作,影響你的對象的狀態,並將錯誤地提供現代化這一行動是一個屬性。確保手動審查和確認任何轉換器提供的使您的代碼的更改。
前面描述的現代化,轉換器提供了:
1.改變id到instancetype在合適的地方
2.改變enum到NS_ENUM或NS_OPTIONS
3.更新到@property語法
除了這些現代化,這個轉換器推薦額外的代碼變更,包括:
1.轉換到字面意思,像[NSNumber numberWithInt:3]變成@3.
2.用下標,像[dictionary setObject:@3 forKey:key]變成dictionary[key] = @3.
使用modern Objective-C converter,Edit > Refactor > Convert to Modern Objective-C Syntax.
來源:簡書