簡介:
如果你閱讀這本書,你可能已經牢牢掌握iOS開發的基礎,但這裡有一些小特點和實踐是許多開發人員並不熟悉的,甚至有數年經驗的開發人員也是。在這一章裡,你會學到一些很重要的開發技巧,但這仍遠遠不夠,你還需要積累更多的實踐來讓你的代碼更強力。
/*
本文翻譯自《iOS 7 Programming Pushing the Limits》一書的第三章“You May Not Know”,想體會原文精髓的朋友請支援原書正版。
——————(部落格園、新浪微博)葛布林大帝
*/
目錄:
一. 最好的命名實踐
二. Property和執行個體變數(Ivar)的最佳實務
三. 分類(Categories)
四. 關聯引用(Associative References)
五. Weak Collections
六. NSCache
七. NSURLComponents
八. CFStringTransform
九. instancetype
十. Base64 和 Percent編碼
十一. -[NSArray firstObject]
十二. 總結
十三. 更多閱讀
一、最好的命名實踐
在iOS開發裡,命名規範極其重要。在下面的部分,我們將學習如何正確命名各種條目,以及為什麼這樣命名。
1. 自動變數
Cocoa是動態類型的語言,你很容易對所使用的類型感到困惑。集合(數組、字典等等)沒有關聯它們的類型,所以這樣的意外很容易發生:
1 NSArray *dates = @[@”1/1/2000”];2 NSDate *firstDate = [dates firstObject];
編譯器沒有警告,但當你使用firstDate時,它很可能會報錯(an unknown selector exception)。錯誤是調用一個string dates數組。這個數組應該調用dateStrings,或者應該包含NSDate對象。這樣小心的命名將會避免很多令人頭痛的錯誤。
2. 方法
1)方法名應該清楚表明接收和返回的類型
例如,這個方法名是令人困惑的:
1 - (void)add; // 令人困惑
看起來add應該帶一些參數,但它沒有。難道它是增加一些預設對象?
這樣命名就清楚多了:
1 - (void)addEmptyRecord;2 - (void)addRecord:(Record *)record;
現在addRecord:接收一個Record參數,看起來清楚多了。
2)對象的類型應符合名稱,如果類型和名稱不匹配,則容易弄混
這個例子展示了一個常見錯誤:
1 - (void)setURL:(NSString *)URL; // 錯誤的
這裡錯誤是因為調用setURL時,應該接收一個NSURL,而不是一個NSString。如果你需要string,你需要增加一些指示讓它更明朗:
1 - (void)setURLString:(NSString *)string;2 - (void)setURL:(NSURL *)URL;
這個規則不應過度使用。如果類型很明顯,別添加類型資訊到變數上。一個叫做name的屬性就比叫做nameString的屬性更好。
3)方法名也有與記憶體管理和KVC相關的特定原則
雖然ARC使得其中的一些規則不再重要,但在ARC與非ARC進行互動時(包括Apple架構的非ARC代碼),不正確的命名規則仍會導致非常具有挑戰性的錯誤。
方法名應該永遠是小寫字母開頭,駝峰結構。
如果一個方法名以alloc、new、copy或者nutableCopy開頭,調用者擁有返回的對象。如果你的property的名字像newRecord這樣,這個規則可能會導致問題,請換一個名字。
get方法的開頭應該返回一個參照值,例如:
1 - (void)getPerson:(Person **)person;
不要使用get首碼作為property accessor的一部分,property name的getter應該為-name。
二、Property和執行個體變數(Ivar)的最佳實務
Property應該代表一個對象的狀態,Getter應該沒有外部影響(它們可以具有內部影響,例如caching,但那些應該是調用者不可見的)。
避免直接存取執行個體變數,使用accessor來代替。
在早期的ARC裡,引起bug最常見的原因就是直接存取執行個體變數。開發人員沒有正確的retain和release執行個體變數,它們的應用就會崩潰或者記憶體泄露。由於ARC自動管理retain和release,一些開發人員認為這個規則已經不再重要,但仍還有其他使用accessors的原因:
- KVO
- 也許使用accessor的最關鍵原因是,property可以被觀察到。如果你不使用accessor,你需要在每次修改property裡的執行個體變數時調用willChangeValueForKey: 和 didChangeValueForKey: ,而accessor會在需要時自動調用這些方法。
- Side effects
- 在setter裡,你或者你的子類可能包含side effects。通知可能被傳送、事件可能被註冊到NSUndoManager裡,你不應該繞過這些side effects,除非它是必要的。
- Lazy instantiation
- 如果一個property被lazily instantiated,必須使用accessor來確保它的正確初始化。
- Locking
- 如果引進locking到一個property裡來管理多線程代碼,直接存取執行個體變數將違背你的lock,並可能導致程式崩潰。
- Consistency
- 在看到前面的內容後,有人可能會說應該只使用accessor,但這使得代碼很難維護。懷疑和解釋每一個直接存取的執行個體變數,而不是記住哪些需要accessor哪些不需要,這樣使得代碼更容易審核、審閱和維護。Accessor,特別是synthesized accessors,已經在OC裡被高度最佳化,它們值得使用。
這就是說,你不應該在這幾個地方使用accessor:
- Accessor內部
- 顯然,你不能在accessor內部使用自身。通常,你也不想在getter和setter內部使用它們自己(這可能建立無限迴圈),一個accessor應該訪問其自身的執行個體變數。
- Dealloc
- ARC極大地減少了dealloc,但它有時仍會出現。最好調用dealloc裡的外部對象,該對象可能處於不一致的狀態,並很可能造成混淆。
- Initialization
- 類似dealloc,對象可能在初始化過程中處於不一致狀態,你不應該在此時銷毀通知或者其他的side effects。
三、 分類(Categories)
分類允許你在運行中的類裡添加方法。任何類(甚至是由Apple提供的Cocoa類)都可以通過分類來拓展,這些新方法對類的所有執行個體都是可用的,分類聲明如下:
1 @interface NSMutableString (PTLCapitalize)2 - (void)ptl_capitalize;3 @end
PTLCapitalize是分類的名稱,注意這裡沒有聲明任何執行個體變數。
分類不能聲明執行個體變數,也不能synthesize properties。
分類可以聲明properties,因為它只是聲明方法的另一種方式。
分類不能synthesize properties,因為這會建立一個執行個體變數。
1. +load
分類在運行時附加到類,這可能定義分類為動態載入,所以分類可以很晚添加(雖然你不能在iOS裡編寫自己的動態庫,但系統架構是動態載入的,並且包括分類)。OC提供了一個名為 +load 的東西,在分類首次附加時運行。隨著 +initialize,你可以使用它來實現指定分類的設定,例如初始化靜態變數。你不能安全的在分類裡使用+initialize,因為類可能已經實現它。如果有多個分類實現+initialize,那麼運行一個沒有意義。
我希望你已經準備好要問一個顯而易見的問題:“如果分類不能使用+initialize,因為他們可能與其他分類衝突,那麼多個分類實現+load呢?”這正是OC runtime神奇的地方之一, +load方法是runtime的特例,是每一個分類能實現它,並且所有的實現都運行。當然,你不應該嘗試手動調用+load。
四、關聯引用(Associative References)
關聯引用允許你附加key-value資料到任何對象。這個能力有多種用途,但最常用的是允許你的分類添加資料的property。
考慮一個Person類的情況,你想使用分類來添加一個叫做emailAddress的新property。也許你在其他程式裡使用Person類,並且有時使用email address而有時不用,因此使用分類是可以避免開銷的很好解決方案。或者,你沒有自己的Person類,並且維護者不會為你添加property,你該如何解決這個問題?首先來看一下基礎的Person類:
1 @interface Person : NSObject2 @property (nonatomic, readwrite, copy) NSString *name;3 @end4 5 @implementation Person6 @end
現在你可以添加新的property了,在分類裡使用關聯引用:
1 #import <objc/runtime.h> 2 @interface Person (EmailAddress) 3 @property (nonatomic, readwrite, copy) NSString *emailAddress; 4 @end 5 6 @implementation Person (EmailAddress) 7 static char emailAddressKey; 8 9 - (NSString *)emailAddress {10 return objc_getAssociatedObject(self, &emailAddressKey);11 }12 13 - (void)setEmailAddress:(NSString *)emailAddress {14 objc_setAssociatedObject(self, &emailAddressKey, emailAddress, OBJC_ASSOCIATION_COPY);16 } 17 @end
注意關聯引用是基於key的記憶體位址,而不是它的值。emailAddressKey裡儲存什麼並不重要,它只需要有一個唯一、不變的地址,這就是為什麼它通常使用未分配的static char作為key。
關聯引用有很好的記憶體管理,用以參照objc_setAssociatedObject的參數傳遞正確處理copy、assign或者retain。當相關的對象被deallocated,它們會released。這實際上意味著在另一個對象被銷毀時,你可以使用相關的對象進行追蹤,例如:
1 const char kWatcherKey; 2 3 @interface Watcher : NSObject 4 @end 5 6 #import <objc/runtime.h> 7 8 @implementation Watcher 9 - (void)dealloc {10 NSLog(@"HEY! The thing I was watching is going away!");11 }12 @end13 ...14 NSObject *something = [NSObject new];
15 objc_setAssociatedObject(something, &kWatcherKey, [Watcher new], OBJC_ASSOCIATION_RETAIN);
這種技術對於調試非常有用,同時也可用於非調試任務,例如執行清理。
使用關聯引用是附加相關對象到alert panel或者control的好方法,例如你可以附加一個“represented object”到alert panel,代碼如下:
1 ViewController.m (AssocRef) 2 id interestingObject = ...; 3 UIAlertView *alert = [[UIAlertView alloc] 4 initWithTitle:@"Alert" message:nil 5 delegate:self 6 cancelButtonTitle:@"OK" 7 otherButtonTitles:nil]; 8 objc_setAssociatedObject(alert, &kRepresentedObject, 9 interestingObject,10 [alert show];
許多程式在調用裡使用執行個體變數處理這個任務,但關聯引用更簡潔。對於那些熟悉Mac的開發人員,這些代碼類似於representedObject
,但卻更靈活。
聯想引用的一個限制是,它們沒有與encodeWithCoder:整合,因此它們很難通過一個分類來序列化。
五、Weak Collections
大多數Cocoa的集合例如NSArray、NSSet和NSDictionary都具有強大功能,但它們不適合某些情況。NSArray與NSSet會保留你儲存進去的對象,NSDictionary會儲存value和key,這些行為通常是你想要的,但對於某些工作它們並不適合。幸運的是,自從iOS6開始,一些其他的集合開始出現:NSPointerArray、NSHashTable與NSMapTable。它們統稱為Apple文檔的指標集合類(pointer collection classes),並且有時使用NSPointerFunctions類來進行配置。
NSPointerArray類似NSArray, NSHashTable類似NSSet,而NSMapTable類似NSDictionary。每個新的集合類都可以配置為保持弱引用,指向Null 物件或者其他異常情況。NSPointerArray的一個額外好處是它還可以儲存NULL值。
指標集合類可以使用NSPointerFunctions來廣泛的配置,但大多數情況下,它只是簡單的傳送一個NSPointerFunctionsOptions flag到–initWithOptions:。最常見的情況,例如+weakObjectsPointerArray,有自己的建構函式。
六、 NSCache
NSCache有幾個被低估的功能,比如事實上它是安全執行緒的,你可能在任何無鎖的線程裡改變一個NSCache。NSCache也被設計來融合對象遵從<NSDiscardableContent>,其中最常見的類型是NSPurgeableData,通過調用beginContentAccess 與 endContentAccess,你可以控制何時安全放棄這個對象。這不僅在你的應用運行時提供自動緩衝管理,它甚至有助於你的應用被暫停。通常情況下,當記憶體緊張時,記憶體警告沒有釋放出足夠的記憶體,iOS會開始殺死暫停在背景應用。在這種情形下,你的應用沒有得到delegate資訊,就這樣被殺死。不過如果你使用NSPurgeableData,iOS會釋放這塊記憶體給你,即使你的應用被暫停。
想得到