說明:本篇文章是作者(Mitchell)參考蘋果的 Adopting Modern Objective-C 這篇文章加上自己的總結(jié)所翻譯,如需轉(zhuǎn)載請注明出處。
一、instancetype###
- 使用 instancetype 關(guān)鍵詞作為方法中返回類型,該方法返回一個它們所調(diào)用的類的實例(或者子類),這些方法包括 alloc,init 和類的工廠方法。
- 在適當(dāng)?shù)牡胤绞褂?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;
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 ,返回值的類型被表達(dá)為MyObject*,然而MyObject并沒有-count的方法,所以編譯器在x的行給出了一則警告。
main.m: ’MyObject’ may not respond to ‘count’
然而,factoryMethodB 返回的是一個id 類型的返回值,編譯器不能在 y 行給出警告。因為一個 id 類型的對象可能是任何的類,并且一個叫 -count 的方法可能存在于其中某些類中,對于編譯器來說這個返回值是有可能去響應(yīng)這個方法的。
為了確保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 *’
也就是說編譯器已經(jīng)能夠識別你的這個類型就是MyObjectSubclass*。但是如果將 instancetype 改成 id ,我們又會發(fā)現(xiàn)這個警告將會消失,也就是說編譯器不會知道你這個對象的準(zhǔn)確類型是什么。
- 在這個例子中,+ factoryMethodA 方法傳遞了一個類型是MyObjectSubclass 的返回值類型,它作為接受者的類型。編譯器將會適時的確定+ factoryMethodA方法的返回值就是子類MyObjectSubclass,而不是父類的工廠方法中所返回的父類。
- 如何運用?
在你的代碼中,在適當(dāng)?shù)牡胤接?instancetype 替換現(xiàn)有的返回值為 id 的類型。通常是在 init 初始化方法和工廠類方法中。即使編譯器會自動將 “alloc、init、new ”返回值是 id 的方法轉(zhuǎn)換為instancetype,它不會轉(zhuǎn)換其他方法。Objective-C 約定會為所有方法寫入instancetype。
注意:你只能在返回值的時候用instancetype替換id,而不是在你代碼的其他地方。instancetype 并不像 id,instancetype 關(guān)鍵詞只能被做聲明方法的返回值類型。
例如:
@interface MyObject
- (id)myFactoryMethod;
@end
應(yīng)該被改為:
```
@interface MyObject
- (instancetype) myFactoryMethod;
```
或者,您可以用現(xiàn)代Objective-C變化器在Xcode自動進(jìn)行更改您的代碼,具體請參考 [Refactoring Your Code Using Xcode](https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html#//apple_ref/doc/uid/TP40014150-CH1-SW13) 。
- 對于 instancetype 和 id 異同的小結(jié):
- 不同點:
1、instancetype可以返回和方法所在類相同類型的對象,而id只能返回未知類型的對象,簡言之:instancetype會有一個類型檢測,在初始化的時候它會檢測是哪個類在調(diào)用初始化方法,然后返回這個類,而這個是id所做不到的。
2、instancetype只能作為返回值,不能像id那樣作為參數(shù)。 - 相同點:都能作為返回值的類型。
- 不同點:
二、屬性###
一個Objective-C的屬性是由@property語法來定義是公有或者私有。
@property (readonly, getter=isBlue) BOOL blue;
屬性捕捉對象的狀態(tài)。他們反映了對象固有的屬性和其他對象們的關(guān)系。屬性提供了一個安全,方便的方式關(guān)聯(lián)這些屬性,而無需編寫一組自定義訪問器方法(雖然屬性允許定制getter、setter方法,如果需要的話)。
- 自動合成 getter和setter方法:當(dāng)你定義一個屬性的時候,默認(rèn)為你創(chuàng)建了getter和setter方法。
- 更好的辦法是聲明一系列的方法。因為訪問器方法有命名約定,可以很清楚的知道getter和setter中做了什么。
- 屬性的關(guān)鍵詞可以表達(dá)出關(guān)于自身行為的額外信息。屬性提供了一個潛在的聲明屬性的方式,assign(vs copy),weak,atomic(vs nonatomic),等等。
- 屬性方法遵守了一個簡單的命名約定。getter方法的是以屬性的名稱命名,setter方法是以set前綴命名,書寫的規(guī)范是駝峰式的大小寫。Boolean屬性的命名約定是在getter方法前加一個“is”:
@property(readonly,getter = isBlue)BOOL blue;
據(jù)此,所有的接下來的工作可以這樣:
if(color.blue){}
if(color.isBlue){}
if([color isBlue]){}
當(dāng)決定什么可以稱為一個屬性的時候,請記住以下的幾點不可以稱為屬性:
- init 方法
- copy 方法,mutableCopy方法
- 一個類的工廠方法
- 一個初始化方法并且返回一個BOOL值的方法
- 一個就像getter方法作用的一方面來明確內(nèi)部狀態(tài)變化的方法。
除此之外,考慮考慮以下的規(guī)則當(dāng)在你的代碼中定義一個潛在的屬性:
- readwrite的屬性有兩個訪問器方法,setter帶一個參數(shù)無返回值。getter沒有參數(shù)但是有一個返回值。如果你想把這組方法轉(zhuǎn)換成屬性,標(biāo)記它一個readwrite關(guān)鍵詞。
- read-only的屬性只有一個訪問器方法,getter。如果你想把這個方法轉(zhuǎn)換成屬性,標(biāo)記它一個readonly關(guān)鍵詞。
- getter方法應(yīng)該被冪等(如果一個getter被調(diào)用兩次,第二次的返回的結(jié)果應(yīng)該是和第一次相同的)。然而,這也是可以接受的就是getter每次被調(diào)用時都計算結(jié)果。
- 如何采用
定義一組方法,有資格被轉(zhuǎn)換為一個屬性,如下:
- (NSColor*)backgroundColor;
- (void)setBackgroundColor:(NSColor*)color;
然后用帶著適合的關(guān)鍵字的@property語法糖去聲明它們:
@property(copy)NSColor* backgroundColor;
關(guān)于屬性關(guān)鍵詞的信息和一些其他的注意事項,請看Encapsulating Data
三、枚舉、宏###
NS_ENUM和NS_OPTION的宏提供了一個簡潔的基于C語言的方式去定義枚舉和選項。這兩個宏在顯式地指定枚舉類型和大小的和選項方面提高了代碼的完成效率。除此之外,這個語法生命枚舉的方式是由老的編程式來準(zhǔn)確評估的,由新的編程式來解釋潛在的類型信息。
使用 NS_ENUM 宏來定義枚舉,一組相互獨立的值:
typedef NS_ENUM (NSInteger,UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITavleViewCellStyleValue1,
UITavleViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
NS_ENUM 宏幫助定義了枚舉的名字和類型兩個類型,在這種情況下給 UITableViewCellStyle 命名為 NSInteger 的類型。枚舉的類型應(yīng)該是 NSInteger。
使用 NS_OPTIONS 宏來定義選項,一組位掩碼值可以被組合在一起:
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宏定義了名稱和類型兩個值。然而,選項的類型應(yīng)該是NSUInteger。
- 如何采用
替換你的魅族聲明,就像下面這樣:
enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
typedef NSInteger UITableViewCellStyle;
使用 NS_ENUM 語法:
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
當(dāng)你使用 enum 去定義一組位掩碼的時候,可以像下面這樣:
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 宏
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
};
- 關(guān)于NS_ENUM 和NS_OPTIONS的小結(jié)
NS_ENUM:第一個參數(shù)用于存儲新類型的類型,在64位環(huán)境下,UITableViewCellStyle 和 NSInteger 一樣有8字節(jié)長。你要保證你給出的所有的值能被改類型容納,否則就會產(chǎn)生錯誤。第二個參數(shù)是新類型的名字。大括號里面和以前i,是你要定義的各種值。這種方法提取了之前各種不同實現(xiàn)的優(yōu)點,甚至有提示編譯器在進(jìn)行switch判斷時檢查類型匹配的功能。
NS_OPTIONS:位掩碼使用。語法和 NS_ENUM 完全相同,但這個宏提示編譯值是如何通過位掩碼|組合在一起的。同樣的,注意值的區(qū)間不要超過所使用類型的最大容納范圍。
參考至:NS_ENUM & NS_OPTIONS
四、對象的初始化###
在 Objective-C 中,對象的初始化是基于一個指定的初始化器的概念,一個初始化方法負(fù)責(zé)調(diào)用的是父類的初始化方法然后會調(diào)用自身的對象實例方法。不指定值的初始化被稱為便利的初始化。便利初始化通常委托給一個初始化器-最終的終端鏈在初始化器-而不是自己執(zhí)行的初始化。
指定的初始化器會幫助并確保繼承的初始化器正確初始化所有的實例變量。子類所執(zhí)行重要的初始化需要覆蓋其父類所有指定的初始化。但是它不必去覆蓋方便的初始化。更多關(guān)于初始化器的信息,看這里Object Initialization
-
為了澄清指定和指定初始化之間的區(qū)別,你應(yīng)該在
init家庭中 增加NS_DESIGNATED_INITIALIZER這個宏,表示它是指定的初始化器。介紹這個宏的幾個使用機制:- 指定的初始化器必須鏈接到一個父類的 init 方法中([super init]),這是為父類指定的初始化器。
- 方便的初始化器(一個類的初始化方法并沒有被標(biāo)記為是指定的初始化方法,一個類必須至少有一個初始化方法作為指定的初始化器)必須委托給另一個初始化器。
- 如果一個類提供了一個或者更多的指定的初始化方法,它必須實現(xiàn)所有的指定的父類的初始化方法。
- 如果違反了這些限制,你會收到來自編譯器的警告。
如果你在你的類中使用了NS_DESIGNATED_INITIALIZER宏,你需要在你所有指定的初始化方法中都標(biāo)記這個宏,所有的其他的初始化會被認(rèn)為是便利的初始化。
怎么使用
在你的類中確定你指定的初始化方法,然后用 NS_DESIGNATED_INITIALIZER 宏來標(biāo)記它們,舉個例子:
- (instancetype)init NS_DESIGNATED_INITIALIZER;
五、自動引用計數(shù)(ARC)###
- 自動引用計數(shù)是編譯器提供的一個自動對 Objective-C 對象進(jìn)行內(nèi)存管理的特色。可之前你不得不用 retain,releae 或者autorelease 不同,ARC 預(yù)估對象生命周期的需求然后在編譯的事后自動插入適當(dāng)?shù)膬?nèi)存管理需求。編譯器也會生成適當(dāng)?shù)?dealloc 方法。
- 如何運用
Xcode提供了一個ARC轉(zhuǎn)換的自動化工具(例如移除retain和release的調(diào)用),這會幫助你解決遷移的時候不能自動處理的問題。使用ARC遷移工具,選擇 Edit > Refactor > Convert to Objective-C ARC. 遷移器會將所有文件轉(zhuǎn)換成ARC。