Adopting Modern Objective-C

轉載自:http://codingobjc.com/blog/2014/03/16/yi-adopting-modern-objective-c/

蘋果2014年03月10日發佈了一個新文檔,介紹了Objective-C的幾個新技巧,包括:

文檔名字叫《Adopting Modern Objective-C》,將它翻譯成中文了,以下是正文。

Adopting Modern Objective-C

歷經多年,Objective-C語言已經得到了許多增長和演變。雖然核心概念和做法保持一致,但這個語言的部分已經發生顯著的變化和改進。這些現代化的改進增強了Objective-C的類型安全、內存管理、性能和一些其他方面,使你可以更輕鬆地編寫正確的代碼。在你現有的和將來的代碼中採用這些改進可以使你的代碼變得更一致,可讀性更強,更靈活。

XCode提供了一個工具來幫你完成這些結構上的更改。但是在你開始使用這個工具之前,你應該想了解一下它會給你的代碼帶來什麼樣的改變,以及爲什麼會帶來這樣的改變。本文檔重點介紹了一些你可以在你的代碼中應用的最顯著的和最有用的新特性。

instancetype

在返回類實例對象的方法中用instancetype關鍵詞作爲方法的返回值類型。這包括在allocinit和類工廠方法等方法中。

在適當的地方用instancetype代替id,可以提高你的Objective-C代碼的類型安全。例如,考慮下面的代碼:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
<span class="line-number" style="margin: 0px; padding: 0px;">7</span>
<span class="line-number" style="margin: 0px; padding: 0px;">8</span>
<span class="line-number" style="margin: 0px; padding: 0px;">9</span>
<span class="line-number" style="margin: 0px; padding: 0px;">10</span>
<span class="line-number" style="margin: 0px; padding: 0px;">11</span>
<span class="line-number" style="margin: 0px; padding: 0px;">12</span>
<span class="line-number" style="margin: 0px; padding: 0px;">13</span>
<span class="line-number" style="margin: 0px; padding: 0px;">14</span>
<span class="line-number" style="margin: 0px; padding: 0px;">15</span>
@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;
    x = [[MyObject factoryMethodA] count]; // Return type of +factoryMethodA is taken to be "MyObject *"
    y = [[MyObject factoryMethodB] count]; // Return type of +factoryMethodB is "id"
}

因爲+factoryMethodA的返回值類型爲instancetype,該消息表達式的類型爲MyObject*。由於MyObject沒有count方法,編譯器給出了一個關於x行的警告:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
main.m: MyObject may not respond to count

然而,因爲+factoryMethodB的返回值類型爲id,編譯器無法給出關於y行的警告。由於id類型的對象可以是任何類,並且由於一個叫count的方法可能存在於某個類的某個地方,對於編譯器來說,它就認爲+factoryMethodB方法返回的對象可能實現了該方法。

爲了確保instancetype工廠方法有正確的子類化行爲,在alloc類內存的時候一定要用[self class],而不要直接引用類名。遵循這一約定可以確保編譯器能夠正確的推斷出子類類型。例如,考慮MyObject子類的情形:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
<span class="line-number" style="margin: 0px; padding: 0px;">7</span>
@interface MyObjectSubclass : MyObject

@end

void doSomethingElse() {
    NSString *aString = [MyObjectSubclass factoryMethodA];
}

編譯器給出以下警告:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
main.m: Incompatible pointer types initializing NSString * with an expression of type MyObjectSubclass *

這個示例中,+factoryMethodA消息返回一個MyObjectSubclass類型的對象,這是消息接收者(receiver)類型的對象。編譯器會相應地推斷出+factoryMethodA的返回值類型應該是子類MyObjectSubclass,而不是定義了該工廠方法的父類。

如何應用

在你的代碼中,在適當的地方用instancetype做返回值,替換掉id。通常是在init方法和類工廠方法的情形中。儘管編譯器會自動將返回值類型爲id的並且開頭爲”alloc”、”init”或”new”的方法的返回值類型轉換成instancetype類型,但它不會去轉換其他的方法。Objective-C的約定是,要爲所有有需要的方法明確地寫上instancetype。

要注意的是,只有在id作爲返回值類型的地方纔能用instancetype代替它,其他地方不行。與id不同的是,instancetype關鍵字只能在方法聲明中被用作返回值類型。

例如:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
@interface MyObject
- (id)myFactoryMethod;
@end

應該變成:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
@interface MyObject
- (instancetype)myFactoryMethod;
@end

或者,你也可以用Xcode中的modern Objective-C轉換器來自動轉換你的代碼。欲瞭解更多信息,請參閱“使用Xcode重構你的代碼”。

Properties

Objective-C的屬性(property)是指用@property語法定義的公有的或私有的方法。

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
@property (readonly, getter=isBlue) BOOL blue;

屬性保存了對象的狀態。它們反映了對象的固有屬性和對象與其他對象之間的關係。屬性提供了一種安全的、便捷的方式來與這些屬性進行交互,而無需編寫一套自定義的訪問(accessor)方法(雖然,如果有需要的話,屬性確實允許自定義getter和setter方法)。

儘量在儘可能多的地方使用屬性來代替實例變量,會有許多好處:

  • 自動生成getter和setter方法。 當你定義了一個屬性,默認會自動爲你創建對應的getter和setter方法。

  • 更好的定義一組方法。 因爲訪問方法的命名約定,它會讓getter和setter方法的用途更明確。

  • 屬性關鍵詞可以表達出對應行爲的額外信息。 屬性提供了assign(相對於 copy)weakatomic(相對於 nonatomic)等等特性。

屬性方法遵循一個簡單的命名約定。getter方法的名字和屬性名字相同(例如,date),setter方法的名字是屬性名字帶一個”set”前綴並採用駝峯命名規則(例如,setDate)。布爾類型的屬性還可以定義一個以”is”開頭的getter方法:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
@property (readonly, getter=isBlue) BOOL blue;

其結果是,以下都是有效的:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
if (color.blue) { }
if (color.isBlue) { }
if ([color isBlue]) { }

當決定哪些可以作爲一個屬性的時候,請記住,以下這些不屬於屬性:

  • init方法
  • copymutableCopy方法
  • 類工廠方法
  • 開啓某項操作並返回一個BOOL結果的方法
  • 明確的改變了一個getter的內部狀態的副作用方法。

另外,在你的代碼中標示屬性特性的時候請考慮以下規則:

  • 一個可讀寫(read/write)的屬性有兩個訪問方法。setter方法接受一個參數並且沒有返回值,getter方法不接受任何參數並返回一個值。如果將這組方法轉換成一個屬性,就可以用readwrite關鍵字來標記它。
  • 一個只讀(read-only)的屬性只有一個訪問方法。即getter方法,它不接受任何參數,並且返回一個值。如果將這個方法轉換成一個屬性,就可以用readonly關鍵字標記它。
  • getter方法應當是冪等的(idempotent,如果一個getter方法被調用兩次,那麼第二次調用時返回的結果應該和第一調用時返回的結果相同)。然而,如果一個getter方法每次調用時,是被用於計算結果,這是可以接受的。

如何應用

識別出一組可以被轉換成一個屬性的方法,如這些方法:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
- (NSColor *)backgroundColor;
- (void)setBackgroundColor:(NSColor *)color;

@property語法和適當的關鍵字將它們定義成一個屬性:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
@property (copy) NSColor *backgroundColor;

有關屬性關鍵詞和其他注意事項,請參閱《Encapsulating Data》。

或者,你也可以用Xcode中的modern Objective-C轉換器來自動轉換你的代碼。欲瞭解更多信息,請參閱“使用Xcode重構你的代碼”。

枚舉宏

NS_ENUMNS_OPTIONS宏提供了一種在基於C的語言中定義枚舉的簡潔而又簡單的方法。這兩個宏增強了XCode中相關的代碼補全功能,並且明確地指定了枚舉和可選項(options)的類型和大小。此外,該語法定義的枚舉既可以使得舊的編譯器能夠正確地計算枚舉值,也可以使新的編譯器能夠解析出枚舉值的類型信息。

使用NS_ENUM宏來定義枚舉,即一組互斥的值:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

NS_ENUM宏同時定義了枚舉的名稱和類型,這個示例中名稱是UITableViewCellStyle,類型是NSInteger。枚舉的類型應該儘量是NSInteger的。

使用NS_OPTIONS宏來定義可選項(options),即一組可以相互結合的按位掩碼(bitmasked)的值:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
<span class="line-number" style="margin: 0px; padding: 0px;">7</span>
<span class="line-number" style="margin: 0px; padding: 0px;">8</span>
<span class="line-number" style="margin: 0px; padding: 0px;">9</span>
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

像枚舉一樣,NS_OPTIONS宏也同時定義了名稱和類型。但是,可選項的類型通常應該是NSUInteger的。

如何應用

更換你的枚舉聲明,比如這個:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
<span class="line-number" style="margin: 0px; padding: 0px;">7</span>
enum {
    UITableViewCellStyleDefault,
	UITableViewCellStyleValue1,
	UITableViewCellStyleValue2,
	UITableViewCellStyleSubtitle
};
typedef NSInteger UITableViewCellStyle;

更換成NS_ENUM語法:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

但當你用enum來定義一個位掩碼的時候,比如這裏:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
<span class="line-number" style="margin: 0px; padding: 0px;">7</span>
<span class="line-number" style="margin: 0px; padding: 0px;">8</span>
<span class="line-number" style="margin: 0px; padding: 0px;">9</span>
<span class="line-number" style="margin: 0px; padding: 0px;">10</span>
enum {
    UIViewAutoresizingNone                  = 0,
    UIViewAutoresizingFlexibleLeftMargin    = 1 << 0,
    UIViewAutoresizingFlexibleWidth         = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin   = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin     = 1 << 3,
    UIViewAutoresizingFlexibleHeight        = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin  = 1 << 5
};
typedef NSUInteger UIViewAutoresizing;

要換成使用NS_OPTIONS宏:

<span class="line-number" style="margin: 0px; padding: 0px;">1</span>
<span class="line-number" style="margin: 0px; padding: 0px;">2</span>
<span class="line-number" style="margin: 0px; padding: 0px;">3</span>
<span class="line-number" style="margin: 0px; padding: 0px;">4</span>
<span class="line-number" style="margin: 0px; padding: 0px;">5</span>
<span class="line-number" style="margin: 0px; padding: 0px;">6</span>
<span class="line-number" style="margin: 0px; padding: 0px;">7</span>
<span class="line-number" style="margin: 0px; padding: 0px;">8</span>
<span class="line-number" style="margin: 0px; padding: 0px;">9</span>
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

或者,你也可以用Xcode中的modern Objective-C轉換器來自動轉換你的代碼。欲瞭解更多信息,請參閱“使用Xcode重構你的代碼”。

自動引用計數(ARC)

自動引用計數(Automatic Reference Counting)是一個編譯器特性,它提供了Objective-C對象的自動內存管理功能。而你不必再需要記住什麼時候使用retainreleaseautorelease了,ARC會評估你的對象的生命週期,並在編譯期自動幫你插入適當的內存管理調用。編譯器也會爲你生成相應的dealloc方法。

如何應用

XCode提供了一個工具,可以自動幫你完成ARC轉換過程中的手動操作部分(比如移除對retainrelease的調用),也可以幫助你解決遷移工具不能自動處理的問題。要使用ARC遷移工具,在XCode菜單中選擇“Edit > Refactor > Convert to Objective-C ARC”,遷移工具會將項目中的所有文件轉換至使用ARC。

欲瞭解更多信息,請參閱《Transitioning to ARC Release Notes》。

使用XCode重構你的代碼

XCode提供了一個現代的Objective-C轉換工具,可以協助你完成轉換工作。雖然該轉換工具可以在某些潛在的地方幫助你識別和應用這些現代的特性,但它不解釋你代碼的語義。例如,它不能檢測出你的-toggle方法是一個可以影響你的對象的狀態的方法,它會錯誤地提議將這個方法轉換成一個更現代化的屬性。請務必手動檢查和確認轉換工具給你的代碼提供的任何修改。

綜上所述的現代化的特性,這個轉換工具可以提供以下這些轉換功能:

  • 在適當的地方用instancetype替換id
  • 將enum轉換至NS_ENUM或NS_OPTIONS
  • 將部分方法更新成@property語法

除了這些,這個轉換工具還會建議你對代碼做一些其他的修改,包括:

  • 轉換至字面值語法(literals),因此像[NSNumber numberWithInt:3]這樣的語句會變成@3
  • 使用下標語法,因此像[dictionary setObject:@3 forKey:key]這樣的語句會變成dictionary[key] = @3

要使用這個現代的Objective-C轉換工具,在Xcode菜單中選擇“Edit > Refactor > Convert to Modern Objective-C Syntax”。


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