Effective Objective-C 2.0編寫高質量iOS與OS X代碼的52個有效方法

2017/12/31


第一章 熟悉Objective-C

第1條 了解Objective-C語言的起源

  • Objective-C為C語言添加了面向對象的特性,是其超急。Objective-C使用動態(tài)綁定的消息結構,也就是說,在運行時才會檢查對象類型。接收一條消息后,究竟應執(zhí)行何種代碼,由運行環(huán)境而非編譯器來決定。
  • 理解C語言的核心概念有助于寫好Objective-C程序。尤其要掌握內存模型。

第2條 在類的頭文件中盡量少引入其他頭文件

  • 除非確有必要,否則不要進入頭文件。一般來說,應在某個類的頭文件中使用向前聲明來提及別的類,并在實現(xiàn)文件中引入那些類的頭文件。這樣做可以降低類之間的耦合。
  • 有時無法使用向前聲明,比如要聲明某個類要遵守某個協(xié)議。這種情況下,盡量把“該類遵守某協(xié)議”的這條聲明移至“分類”中。如果不行的話,就把協(xié)議單獨放在一個頭文件中,然后將其引入。

第3條 多用字面量語法

  • 應該使用字面量語法, 以使代碼更簡明扼要。
    • 字符串 @“str” ??
    • 數(shù)值 @10 ??
    • 數(shù)組 @[] ??
    • 字典 @{} ??
  • 應使用下標來訪問數(shù)組,鍵訪問字典。
    • arr[10] ??
    • dict[@"name"] ??
  • 字面量創(chuàng)建數(shù)組或字典時,若值中有nil,會拋出異常。請確保值不含nil
    • @[nil] ?
    • @{nil} ?

第4條 多用類型常量,少用#define預處理指令

  • 不要用預處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只會在編譯時做替換操作。即使有人重新定義了常量值,編譯器也不會發(fā)出警告。還有個弊端是會產(chǎn)生N多份臨時變量。
  • 在實現(xiàn)文件中使用static const來定義只在當前文件可見的常量,例如static NSString *const kAnimationName = @“greatWave”
  • 在頭文件中使用extern來聲明全局常量,并在相關實現(xiàn)文件中定義值。這種常量會出現(xiàn)在全局符號中,所以其名稱應以類名做前綴,如UIAplicationDidBecomeActiveNotification

第5條 用枚舉表示狀態(tài)、選項、狀態(tài)碼

  • 應該用枚舉來表示狀態(tài)機的狀態(tài)、傳遞給方法的選項以及狀態(tài)碼等值,給這些值起個易懂的名字。
  • 如果值為可選項類型(可多選),那就定義值為2^N,以便可以使用按位或操作。
  • NS_ENUMNS_OPTIONS宏來定義枚舉類型,并指明底層數(shù)據(jù)類型例如NS_ENUM(NSUInteger, VLCPlayerState)。
  • 在用switch處理枚舉時不要有default分支,這樣在加入新枚舉后編譯器會提示有未處理的枚舉。

第二章 對象、消息、運行期

第6條 理解“屬性”這一概念

  • @property來定義對象的屬性。
  • 通過“特質”來指定存儲數(shù)據(jù)所需的正確語義。
  • iOS開發(fā)應該用nonatomic,因為atomic會嚴重影響性能。

第7條 在對象內部盡量直接訪問實例變量

  • 在對象內部讀取數(shù)據(jù)時,應該直接通過追李變量來讀,而寫入數(shù)據(jù)時,則應該通過屬性來寫。
  • 在初始化方法及dealloc方法總,總是應該直接通過實例變量來讀寫數(shù)據(jù)。
  • 懶加載時,通過屬性來讀取數(shù)據(jù)。

第8條 理解“對象等同性”這一概念

  • 檢測對象等同性應提供isEqualhash方法。
  • ==只會對比兩個指針所指對象的地址。
  • 相同對象必須具有相同hash,但hash相同未必對象相同。
  • 編寫hash方法時,應使用計算速度快而且hash碰撞率低的算法。

第9條 以“類族模式”隱藏實現(xiàn)細節(jié)

  • 類族模式可以把實現(xiàn)細節(jié)隱藏在一套簡單的公共接口后面
  • 系統(tǒng)框架里的大部分collection類都是類族,例如NSArray、NSMutableArray。

第10條 在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)

  • 只有在其他做法不可行時采用,否則可能引入難以查找的bug。
  • 使用方法:
void setAssociatedObject(id object, void\*key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, void\*key);
void objc_removeAssociatedObjects(id object)

第11條 理解objc_msgSend的作用

  • void objc_msgSend(id self, SEL cmd, ...)
  • 舉例
    id return = [git commit:parameter];
    上面的Objective-C方法在運行時會轉換成如下函數(shù):
    id return = objc_msgSend(git, @selector(commit), parameter);

第12條 理解消息轉發(fā)機制

第13條 用“方法調配技術”調試“黑盒方法”

第14條 理解“類對象”的用意

第三章 接口與API

第15條 用前綴避免命名沖突

第16條 提供“全能初始化方法”

  • 所有初始化方法最終都會調用到這個方法,便于統(tǒng)一管理

第17條 實現(xiàn)description方法

  • debugDescription方法是在開發(fā)者在調試器中以控制臺命令打印對象時調用的,如po

第18條 盡量使用不可變對象

  • 盡量把對外開放的屬性設為readonly,而且可能的話盡量不要對外開放(隱藏細節(jié))。
  • 不想被外部改變的屬性,對外聲明時應該使用readonly,而在內部分類中重新聲明為readwrite。
  • 不要把可變的collection作為屬性公開,而應該提供相關方法,以此修改對象中的可變collection。
  • 例如:
// Person.h
@interface Person:NSObject
@property (nonatomic, strong, readonly) NSSet *friends;
@end

// Person.m
@implementation Person {
    NSMutableSet *_internalFriends;
}

- (NSSet *)friends {
    return [_internalFriends copy];
}
- (void)addFriend:(Person *)person {
    [_internalFriends addObject:person];
}
@end

第19條 使用清晰而協(xié)調的命名方式

  • 盡量不要使用縮略詞。
  • 如果方法返回值是新創(chuàng)建的,那么方法名的首個詞應是返回值的類型(如-intValue;+string),除非另有修飾詞(如:- lowercaseString;)。
  • Boolean屬性應選用has、is當前綴。
  • 將get這個前綴留給借由“輸出參數(shù)”來保存返回值的方法,如
    -(void)getBytes:(uchar \*)buffer range:(Range)aRange;

第20條 為私有方法名加前綴

  • 給私有方法名加上前綴,建議使用p_,便于與公開方法區(qū)分。
  • 不要使用單個下劃線_作為私有方法名的前綴,因為這是預留給蘋果公司用的,這可能會覆蓋蘋果的私有方法。

第21條 理解Objective-C錯誤模型

  • NSError封裝了三個屬性 {domain code info}
  • 只有發(fā)生了嚴重錯誤時才使用異常NSException
  • 小錯誤可以返回NSError給調用者處理

第22條 理解NSCopying協(xié)議

  • 要實現(xiàn)copy就要重寫- (id)copyWithZone:(NSZone \*)zone;
  • 要實現(xiàn)mutableCopy就要重寫- (id)mutableCopyWithZone:(NSZone \*)zone;
    (以前會把內存分成不同的zone,現(xiàn)在只有一個zone,所以不必擔心這個參數(shù))
  • 復制對象時需決定深拷貝還是淺拷貝,盡量用淺拷貝。

第四章 協(xié)議與分類

第23條 通過代理與數(shù)據(jù)源協(xié)議進行對象間通信

  • delegate屬性一定要是weak,否則會引入“保留環(huán)”
// 如果頻繁調用如下代碼:
if ([_delegate respondsToSelecter:@selecter(someClassDidSomething:)]) {
    [_delegate someClassDidSomething];
}
// 可考慮將檢查加過緩存到本地標記:
_delegateFlags.didUpdateProgressTo = [_delegate respondsToSelecter:@selecter(someClassDidSomething:)]
...
if (_delegateFlags.didUpdateProgressTo) {
    [_delegate didUpdateProgressTo:progress]
}

第24條 將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中

  • 使用分類機制把類的實現(xiàn)代碼劃分成易于管理的小塊。
  • 將應該視為“私有”的方法歸入名為Private的分類中,以隱藏實現(xiàn)細節(jié)。

第25條 總是為第三方類的分類名稱加入前綴

  • 添加分類的時候要注意是否會覆寫已有方法,因加載時機不確定很可能發(fā)生覆蓋或被覆蓋,這種bug很難查。
  • 向第三方類中添加分類時,應該給其名稱及方法加上你的專用前綴,以減少覆寫情況。

第26條 勿在分類中聲明屬性

  • 為分類添加屬性技術上可行(見第6條),但盡量避免,因為這種方法無法把實現(xiàn)屬性所需的實例變量合成出來。
  • 把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里。
  • 在分類中可以定義存取方法,但盡量不要定義屬性。

第27條 使用匿名擴展隱藏實現(xiàn)細節(jié)

  • 通過擴展向類中增加實例變量。
  • 如果某屬性在主接口中聲明為readonly,那可以在擴展中重新聲明為readwrite。
    若想使類遵循的下一不為人知,可在擴展中聲明。

第28條 通過協(xié)議提供匿名對象

第5章 內存管理

第29條 理解引用計數(shù)

第30條 以ARC簡化引用計數(shù)

  • ARC只負責管理OC對象的內存,CF開頭的對象要開發(fā)者管理。

第31條 在dealloc方法中只釋放引用并解除監(jiān)聽

  • dealloc里應該做的事是釋放指向其他對象的引用,并取消訂閱KVO或Notification等通知,不要做其他事。
  • 如果持有文件秒速符等系統(tǒng)資源,就應該編寫一個方法來釋放這種資源。
  • 不要在dealloc里做異步任務,因為此時對象已經(jīng)在回收狀態(tài)了。

第32條 編寫“異常安全代碼”時留意內存管理問題

  • OC中拋出異常的時候可能會引起內存泄漏
  • 在@try捕獲異常中清理干凈

第33條 以弱引用避免保留環(huán)

  • 以弱引用避免“保留環(huán)(Retain Cycle)”

第34條 以“自動釋放池”降低內存峰值

  • 自動釋放池排布在棧中。
  • 自動釋放池一般放在可能引起內存峰值比較高的地方,如在for循環(huán)創(chuàng)建大量臨時變量。
  • 寫法:
@autoreleasepool {
    // ...
}

第35條 用“僵尸對象”調試內存管理問題

  • 在Scheme->Run->Diagnostics->“Enable Zombie Objects”,勾選即可開啟此功能。
  • 開啟此功能后,系統(tǒng)回收對象時不是釋放對象,而是轉為僵尸對象,僵尸對象能響應所有選擇子,響應方式為打印相關信息然后終止程序。

第36條 不要使用retainCount

  • 對象的保留計數(shù)并不能絕對準確反映對象的生命期。
  • 在蘋果引入ARC之后retainCount已經(jīng)正式廢棄,任何時候都不要調用這個retainCount方法來查看引用計數(shù)了,因為這個值實際上已經(jīng)沒有準確性了。但是在MRC下還是可以正常使用

塊與大中樞派發(fā)

第37條 理解“塊”這一概念

  • 塊是閉包。
  • 塊可以分配在棧塊、堆塊或全局塊上,要注意塊的作用域
// 不安全的塊,因為A塊、B塊都是分配在棧上的,離開了if語句可能會被釋放
void (^block)();
if (condition_1) {
    block = ^{ print("A") };
} else {
    block = ^{ print("B") };
}
block();

// 若將塊拷貝copy就會搬到堆上,就會變成對象,就會安全
block = [^{ print("A") } copy];
block = [^{ print("B") } copy];

第38條 為常見的塊類型創(chuàng)建typedef

  • 主要是為了考慮代碼可讀性。

第39條 用handler塊降低代碼分散程度

  • 異步操作中使用block的方式設計,這樣業(yè)務相關的代碼會比較緊湊,不會顯得那么凌亂。

第40條 用塊引用其所屬對象時不要出現(xiàn)保留環(huán)

  • 不是一定得在block中使用weakself,block 不是被self所持有的,在block中就可以直接使用self

第41條 多用派發(fā)隊列,少用同步鎖

  • 這種寫法效率很低,也不能保證線程中絕對安全:
// 1. synchronized
@synchronized(self) {
    return _someString;
}
// 2. nslock
NSLock *_locker = [[NSLock alloc] init];
[_locker lock];
// safe code
[_locker unlock];
  • 應該用GCD來替換:
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//讀取字符串
- (NSString *)someString {
    __block NSString *localSomeString;
     dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
     return localSomeString;
}
- (void)setSomeString:(NSString*)someString {
     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}

第42條 多用GCD,少用performSelector系列方法

  • performSelector有很多缺點:
    • 內存管理問題:在ARC下我們經(jīng)常會看到編譯器發(fā)出如下警告:warning: performSelector may cause a leak because its selector is unknown [-Warc-performSelector-leaks];
    • 返回值只能是void或對象類型;
    • 無法處理帶有多個參數(shù)的選擇子,最多只能處理兩個參數(shù)
  • 用dispatch系列函數(shù)代替

第43條 掌握GCD以及操作隊列的使用時機

  • 需要的時候可以用NSOperation和NSOperationQueue代替GCD
  • 使用NSOperation和NSOperationQueue的優(yōu)點:
    • 支持取消某個操作
    • 支持指定操作間的依賴關系
    • 支持指定操作的優(yōu)先級
    • 重用NSOperation對象

第44條 通過Dispatch Group機制,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務

  • DispatchGroup可將任務分組,然后等待這組任務執(zhí)行完畢時會有通知,開發(fā)者可以拿到結果然后繼續(xù)下一步操作:
// A B C將會同步執(zhí)行
dispatch_group_async({...A})
dispatch_group_async({...B})
dispatch_group_async({...C})
// 以上都執(zhí)行完再執(zhí)行notify
dispatch_group_notify({...})
  • 通過dispatch group在并發(fā)隊列上同時執(zhí)行多項任務的時候,GCD會根據(jù)系統(tǒng)資源狀態(tài)來幫忙調度這些并發(fā)執(zhí)行的任務。

第45條 使用dispatch_once來執(zhí)行只需要運行一次的線程安全代碼

  • dispatch_once比較高效,沒有重量級的同步機制, 常用于單例模式:
+ (instancetype)sharedInstance {
    static id _instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone: NULL] init];
    });
    return _instance;
}

+ (id)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

- (id)copyWithZone:(struct _NSZone *)zone {
    return [[self class] sharedInstance];
}

- (instancetype)init {
    if (self = [super init]) {
        <#code#>
    }
    return self;
}

第46條 不要使用dispatch_get_current_queue

  • dispatch_get_current_queue 函數(shù)的行為常常與開發(fā)者所預期的不同,iOS 6.0起此函數(shù)已經(jīng)廢棄,只應做調試之用。

第7章 系統(tǒng)框架

第47條 熟悉系統(tǒng)框架

  • Foundation OC基礎框架,其中的類以NS為前綴
  • CoreFoundation C語言框架,CF前綴,可與Foundation無縫橋街
  • AVFoundation OC對象可用來回訪并錄制音頻及視頻。
  • CFNetwork C語言級別的網(wǎng)絡通信能力。
  • CoreAudio C語言API可以用來操作設備上的音頻硬件。
  • CoreData OC接口可以將對象放入數(shù)據(jù)庫,將數(shù)據(jù)持久化。
  • CoreText C語言接口可以高效執(zhí)行文字排版以及渲染操作。
  • SpriteKit 游戲框架
  • CoreLocation、MapKit 定位地圖相關框架
  • Social 社交網(wǎng)絡框架
  • AddressBook 需要使用通訊錄時才使用該框架
  • MusicLibraries 音樂庫相關框架
  • HealthKit 健康相關框架
  • HomeKit 為智能化硬件提供的框架
  • CloudKit iCloud相關的框架
  • NSLinguisticTagger 解析自然語言,如動詞,名詞等
  • UIKit 構建在基礎框架之上,包含UI元素和粘合機制
  • CoreAnimation 是QuartzCore框架的一部分,OC級別的核心動畫庫,用以圖形渲染
  • CoreGraphics C語言級別2D渲染所必備的數(shù)據(jù)結構與函數(shù),如CGRect、CGPoint

第48條 多用塊枚舉,少用for循環(huán)

  • 遍歷有4方法:for、NSEnumerator、for—in、塊枚舉
  • 塊枚舉法是通過GCD來并發(fā)執(zhí)行遍歷操作,當屬最高效方法。
  • 若提前知道待遍歷的collection含有何種對象,則應修改塊簽名,指出對象的具體類型。
  • 例如字典遍歷:
[dict enumeratKeysAndObjectsUsingBlock: ^(NSString \*key, NSString \*obj){
    // ...
}];

第49條 對自定義其內存管理語義的collecion使用無縫橋接

  • 通過無縫橋接技術,可以在定義于Foundation框架中的類和CoreFoundation框架中的C語言數(shù)據(jù)結構之間來回轉換。
    下面代碼展示了簡單的無縫橋接:
NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));
//Output: Size of array = 5

轉換操作中的__bridge告訴ARC如何處理轉換所涉及的OC對象,也就是ARC仍然具備這個OC對象的所有權。__bridge_retained則意味著ARC將交出所有權。

  • 使用CFRelease(aCFArray)釋放對象。

第50條 構建緩存時選用NSCache而非NSDictionary

  • NSCache會在系統(tǒng)資源緊急時自動釋放緩存。
  • NSCache是線程安全的。
  • NSCache會先行釋放最久未使用的對象。
  • 只有重新計算、獲取開銷很大的數(shù)據(jù)才值得緩存,如網(wǎng)絡獲取,磁盤讀取。

第51條 精簡initialize與load的實現(xiàn)代碼

  • oad與initialize 方法都應該實現(xiàn)的精簡一點,這樣有助于保持應用程序的響應能力,也可以減少引入依賴環(huán)的幾率

第52條 別忘了NSTimer會保留其目標對象

  • 在iOS開發(fā)中經(jīng)常會用到定時器:NSTimer,由于NSTimer會生成指向其使用者的引用,而其使用者如果也引用了NSTimer,那就形成了該死的循環(huán)引用。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容