前言
《禪與Objective-C 編程藝術》雖然內容不是很多,但是卻又很多好用的Tips??梢詫⑵渥鳛镺C的編碼規(guī)范來使用。雖然OC逐漸在被Swift所替代,但是很多項目短時間是不會全部轉向Swift的,而且本文有很多內容也是獨立于編程語言的。總得來說是一本很不錯的書,除了這本書外還有一本書叫《Effective Objective-C 2.0: 52 Specific Ways to Improve Your iOS and OS X Programs》,這兩本書內容都差不多,后者比前者講的更詳細一些,對于初級程序猿來說會受益匪淺。
條件語句
語句體總是使用大括號來包圍避免錯誤。
推薦
if (!error) {
return success;
}
不推薦
if (!error)
return success;
或者
if (!error) return success;
不要使用尤達表達式(尤達表達式:使用常量去和變量比較)
推薦
if ([myValue isEqual:@42]) { ...
不推薦
if ([@42 isEqual:myValue]) { ...
nil和BOOL檢查使用感嘆號!來判斷
因為nil是解釋到NO所以沒必要在條件語句里面把它和其他值比較。同時,不要直接把它和YES比較,因為YES的定義是1而BOOL是8位的,實際上是char類型。
推薦
if (someObject) { ...
if (![someObject boolValue]) { ...
if (!someObject) { ...
```
不推薦
```
if (someObject == YES) { ... // Wrong
if (myRawValue == YES) { ... // Never do this.
if ([someObject boolValue] == NO) { ...
不要嵌套if語句,多個return是OK的
推薦
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
不推薦
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
當遇到復雜的if語句時,應該將他們提取出來賦給給一個Bool變量
推薦
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
三元運算符?:,應該只用在它能讓代碼更加清晰的地方
推薦
result = a > b ? x : y;
result = object ? : [self createObject]; // 第二個參數(shù)返回和條件語句中已經(jīng)檢查的對象一致時
不推薦
result = a > b ? x = c > d ? c : d : y;
result = object ? object : [self createObject]; // 第二個參數(shù)返回和條件語句中已經(jīng)檢查的對象一致時
當方法返回一個錯誤參數(shù)引用的時候,檢查返回值,而不是錯誤的變量
推薦
NSError *error = nil;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
Case語句
括號在Case語句中是非必要的,但當一個Case包含多行語句時,要加上括號
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
當switch語句里使用可枚舉的變量的時候,default是不必要的,沒有default有利于錯誤的排查
switch (menuType) {
case ZOCEnumNone:
// ...
break;
case ZOCEnumValue1:
// ...
break;
case ZOCEnumValue2:
// ...
break;
}
關于遍歷
多用快速遍歷(for in)或者block的方式遍歷,少用常規(guī)for循環(huán)
Enumerated Types枚舉類型推薦使用NS_ENUM()
用枚舉表示狀態(tài),選項,狀態(tài)碼,可讀性和可用性會大大增加
命名規(guī)則
盡量遵守Apple命名約定,推薦使用長的,描述性的方法和變量名
推薦
UIButton *settingsButton;
不推薦
UIButton *setBut; // 方法和變量盡量不要帶set get 前綴
Constants常量
多用類型常量,少用#define預處理指令,有利于錯誤檢測
常量命名:
常量如果局限于某個實現(xiàn)文件(.m)中時,使用前綴k。如果常量在類之外可見,則使用類名作為前綴。常量應該用static聲明,并且不要使用#define,除非他就是明確作為一個宏。
推薦
static const NSTimeInterval kFadeOutAnimationDuration = 0.4; // 只在.m 可見
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4; // 類外可見
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
不推薦
static const NSTimeInterval fadeOutTime = 0.4;
#define CompanyName @"Apple Inc.
類型常量如果在類外可見,則應該在.h文件中這樣聲明extern NSString *const ZOCCacheControllerDidClearCacheNotification;,在.m中這樣聲明NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
方法命名在方法類型(-/+符號)后應該有一個空格,方法段之間也該有一個空格,參數(shù)名稱之前總應有一個描述性關鍵詞,盡量不使用and等連接詞來表明多個參數(shù)
推薦
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推薦
- (void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
字面量/語法糖,多使用字面量來創(chuàng)建不可變的實例對象。
推薦
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
不推薦
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
美化代碼
縮進使用4個空格
-
方法的大括號和其他的大括號(if/else/switch/while 等) 總是在同一行開始,在新起一行結束。
推薦:
if (user.isHappy) { //Do something } else { //Do something else }不推薦:
if (user.isHappy) { //Do something } else { //Do something else } 方法之間應該要有一個空行來幫助代碼看起來清晰且有組織。 方法內的空格應該用來分離功能,但是通常不同的功能應該用新的方法來定義。
優(yōu)先使用 auto-synthesis。但是如果必要的話, @synthesize and @dynamic
在實現(xiàn)文件中的聲明應該新起一行。
-
應該總是讓冒號對齊。有一些方法簽名可能超過三個冒號,用冒號對齊可以讓代碼更具有可讀性。即使有代碼塊存在,也應該用冒號對齊方法。
推薦:
[UIView animateWithDuration:1.0 animations:^{ // something } completion:^(BOOL finished) { // something }]; -
長行代碼可以在第二行以一個間隔(2個空格)延續(xù)
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
代碼組織
利用代碼塊
代碼塊如果在閉合的圓括號內的話,會返回最后語句的值。
NSURL *url = ({
NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint];
[NSURL URLWithString:urlString];
});
Pragma
使用 #Pragma Mark進行代碼分組:
- 不通功能組的實現(xiàn)
- protocols的實現(xiàn)
- 對父類方法的重寫
- (void)dealloc { /* ... */ }
- (instancetype)init { /* ... */ }
#pragma mark - View Lifecycle (View 的生命周期)
- (void)viewDidLoad { /* ... */ }
- (void)viewWillAppear:(BOOL)animated { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
#pragma mark - Custom Accessors (自定義訪問器)
- (void)setCustomProperty:(id)value { /* ... */ }
- (id)customProperty { /* ... */ }
#pragma mark - IBActions
- (IBAction)submitData:(id)sender { /* ... */ }
#pragma mark - Public
- (void)publicMethod { /* ... */ }
#pragma mark - Private
- (void)zoc_privateMethod { /* ... */ }
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
#pragma mark - ZOCSuperclass
// ... 重載來自 ZOCSuperclass 的方法
#pragma mark - NSObject
- (NSString *)description { /* ... */ }
或者

忽略警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[myObj performSelector:mySelector withObject:name];
#pragma clang diagnostic pop
- (NSInteger)giveMeFive
{
NSString *foo;
#pragma unused (foo)
return 5;
}
明確警告與錯誤標識
- 手動置為錯誤
#error Whoa, buddy, you need to check for zero here! - 手動警告
#warning Dude, don't compare floating point numbers like this!
注釋
- .h 頭文件中聲明該類的作用
- 接口方法應該有注釋
- 如果變量或方法名不明確的應該加上注釋
接口與API設計
- 類加上前綴,避免命名空間沖突
- 提供designated initializer
- 盡量創(chuàng)建不可變對象,不要把可變對象屬性公開,而是提供相關方法。
- 屬性如果有讀寫權限設定,需要用
readonly聲明 - 方法命名要清晰,盡量不使用縮略詞
- 私有方法可以加上前綴,如
- (void)p_privateMethod,但不要用下劃線開頭;
類
類的頭文件(.h)中盡量少引入其他頭文件,避免互相引用,降低耦合,減少編譯時間
.h中要引入類時,只需要聲明有這個類,具體細節(jié)不用暴露,使用向前聲明(forward declaring)該類,即:@class XXXX。
在.m實現(xiàn)文件中使用該類時需要知道所有接口襲擊,即需要import,#import XXXX
類名,應加上三個大寫字母作為前綴(兩個字母為Apple類保留,WTF??),為了減少沒有命名空間導致的問題
Initializer和dealloc初始化,將dealloc方法寫在實現(xiàn)文件最前面,init方法放在dealloc之后。
使用ARC的話,dealloc方法中不用調用super dealloc,dealloc中只應釋放引用,取消KVO的訂閱或Notification,不做其他事情。
Designated和secondary Initializer
designated初始化方法提供所有的參數(shù),一個類應該有且只有一個,secondary初始化方法是一個或多個,并且提供一個或者更多的默認參數(shù)來調用Designated初始化方法的初始化方法。
eg:
@implementation ZOCEvent
// designated初始化方法,一個類應該有且只有一個
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date
location:(CLLocation *)location
{
self = [super init];
if (self) {
_title = title;
_date = date;
_location = location;
}
return self;
}
// 一下為secondary初始化方法
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date
{
return [self initWithTitle:title date:date location:nil];
}
- (instancetype)initWithTitle:(NSString *)title
{
return [self initWithTitle:title date:[NSDate date] location:nil];
}
@end
在類繼承中調用任何 designated 初始化方法都是合法的,而且應該保證 所有的 designated initializer 在類繼承中是是從祖先(通常是 NSObject)到你的類向下調用的。 實際上這意味著第一個執(zhí)行的初始化代碼是最遠的祖先,然后從頂向下的類繼承,所有類都有機會執(zhí)行他們特定的初始化代碼。這樣,你在你做你的特定的初始化工作前,所有你從超類繼承的東西是不可用的狀態(tài)。即使它的狀態(tài)不明確,所有 Apple 的框架的 Framework 是保證遵守這個約定的,而且你的類也應該這樣做。
定義一個新類的時候有三個不同方式:
- 不需要重載任何初始化函數(shù)
- 重載designated initializer
- 定義一個新的designated initializer
第一個方案是最簡單的:你不需要增加類的任何初始化邏輯,只需要依照父類的designated initializer。 當你希望提供額外的初始化邏輯的時候你可以重載 designated initializer。你只需要重載你的直接的超類的 designated initializer 并且確認你的實現(xiàn)調用了超類的方法。
在你希望提供你自己的初始化函數(shù)的時候,你應該遵守這三個步驟來保證正確的性:
- 定義你的 designated initializer,確保調用了直接超類的 designated initializer
- 重載直接超類的 designated initializer。調用你的新的 designated initializer.
- 為新的 designated initializer 寫文檔
通過宏來指定,正確實現(xiàn)例子:
@interface ZOCNewsViewController : UIViewController
- (instancetype)initWithNews:(ZOCNews *)news ZOC_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ZOC_UNAVAILABLE_INSTEAD(initWithNews:);
- (instancetype)init ZOC_UNAVAILABLE_INSTEAD(initWithNews:);
@end
@implementation ZOCNewsViewController
- (instancetype)initWithNews:(ZOCNews *)news
{
// call to the immediate superclass's designated initializer
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}
// Override the immediate superclass's designated initializer (重載直接父類的 designated initializer)
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call the new designated initializer
return [self initWithNews:nil];
}
@end
注意:你應該永遠不從 designated initializer 里面調用一個 secondary initializer (如果secondary initializer 遵守約定,它會調用 designated initializer)。如果這樣,調用很可能會調用一個子類重寫的 init 方法并且陷入無限遞歸之中。
secondary initializer 是一種方便提供默認值、行為到 designated initializer 的 方法。也就是說,你不應該強制很多初始化操作在這樣的方法里面,并且你應該一直假設這個方法不會得到調用。我們保證的是唯一被調用的方法是 designated initializer。 這意味著你的 designated initializer 總是應該調用其他的 secondary initializer 或者你 self 的 designated initializer。有時候,因為錯誤,可能打成了 super,這樣會導致不符合上面提及的初始化順序(在這個特別的例子里面,是跳過當前類的初始化)
一個相關的返回類型可以明確的規(guī)定用instancetype關鍵字作為返回類型
初始化模式
類簇(class cluster):一個在共有的抽象超類下設置一組私有子類的架構->抽象工廠設計模式,比如NSNumber,NSArray。
特點:這個模式的精妙的地方在于,調用者可以完全不管子類,事實上,這可以用在設計一個庫,可以用來交換實際的返回的類,而不用去管相關的細節(jié),因為它們都遵從抽象超類的方法。
class cluster 的想法很簡單,你經(jīng)常有一個抽象類在初始化期間處理信息,經(jīng)常作為一個構造器里面的參數(shù)或者環(huán)境中讀取,來完成特定的邏輯并且實例化子類。這個"public facing" 應該知曉它的子類而且返回適合的私有子類。
單例:如果可能,請盡量避免使用單例而是依賴注入,使用單例請使用線程安全模式來創(chuàng)建共享實例dispatch_once()。注意單例的濫用
單例模式應該運用于類及類的接口趨向于作為單例來使用的情況
屬性,屬性應該盡可能描述性的命名,避免縮寫,而且是首字母小寫的駝峰命名,應該總是使用setter和getter方法訪問屬性,除了init和dealloc方法。
eg:NSString *text,不推薦NSString* text或 NSString * text
屬性getter/setter的好處:
- 使用 setter 會遵守定義的內存管理語義(strong, weak, copy etc...)。舉個例子,copy 每個時候你用 setter 并且傳送數(shù)據(jù)的時候,它會復制數(shù)據(jù)而不用額外的操作
- KVO 通知(willChangeValueForKey, didChangeValueForKey) 會被自動執(zhí)行
- 更容易debug:你可以設置一個斷點在屬性聲明上并且斷點會在每次 getter / setter 方法調用的時候執(zhí)行,或者你可以在自己的自定義 setter/getter 設置斷點。
- 允許在一個單獨的地方為設置值添加額外的邏輯。
getter特點:
- 它是對未來的變化有擴展能力的(比如,屬性是自動生成的)
- 它允許子類化
- 更簡單的debug(比如,允許拿出一個斷點在 getter 方法里面,并且看誰訪問了特別的 getter
- 它讓意圖更加清晰和明確:通過訪問 ivar _anIvar 你可以明確的訪問 self->_anIvar.這可能導致問題
。在 block 里面訪問 ivar (你捕捉并且 retain 了 self 即使你沒有明確的看到 self 關鍵詞) - 它自動產(chǎn)生KVO 通知
- 在消息發(fā)送的時候增加的開銷是微不足道的。
注意:永遠不能在init以及其他初始化函數(shù)里面和dealloc方法中用getter和setter方法,應該直接訪問實例變量。
一個子類可以重載getter和setter并且嘗試調用其他方法,訪問屬性他們可能沒有完全初始化。
點符號:屬性有關時盡量使用點符號,區(qū)分屬性的訪問還是方法調用
推薦
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不推薦
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
屬性定義:屬性參數(shù)按照原子性、讀寫和內存管理順序排列,例如@property (nonatomic, readwrite, copy) NSString *name;
除非特別情況,原子性請使用nonatomic,iOS 中atomic帶來的鎖特別影響性能。
NSString,NSArray,NSDictionary等可變對象和block請使用copy關鍵字。block最早在棧中創(chuàng)建,通過Copy拷貝到堆里。可變對象參考深拷貝與淺拷貝
對于getter公有,setter私有的公開屬性,需要在.h設置為readonly,并在類擴展中重新定義通用的屬性為readwrite。
@interface MyClass : NSObject
@property (nonatomic, readonly, strong) NSObject *object
@end
@interface MyClass ()
@property (nonatomic, readwrite, strong) NSObject *object
@end
私有屬性:應該在實現(xiàn)文件的類擴展(class extensions,沒有名字的categories)中聲明。
方法
參數(shù)斷言:最好使用NSParameterAssert()來斷言條件是否成立或是拋出一個異常。
私有方法:永遠不要在私有方法前加上_前綴,此前綴被Apple保留。
相等性:當我們要自己實現(xiàn)相等性的時候,需要實現(xiàn)isEqual和hash方法。
如果兩個對象是被isEqual認為相等的,它們的 hash 方法需要返回一樣的值。但是如果 hash 返回一樣的值,并不能確保他們相等。自行百度。
分類(Categories),在方法名加上前綴和下劃線
當一個類中邏輯方法很多時,可以將代碼按照邏輯劃分到不同的分類當中。將私有方法放到private分類中是一個好的實踐。如果不想暴露實現(xiàn)細節(jié)則在class_continuation分類(沒有名字的分類)中聲明。
注意:
- 分類中的方法應該加上自己小寫的前綴和下劃線。比如
- (void)abc_myCategoryMethod;,這樣可以避免與其他的Category中的方法重名。 - 不要在分類中重載系統(tǒng)方法,可能會導致莫名其妙的問題。
- 最好把所有屬性都定義在主接口里。盡量不要在分類中聲明屬性,雖然可以在分類中添加屬性,但是這樣會使調試變得困難,推薦使用存取方法。
協(xié)議(Protocol),實現(xiàn)抽象接口,利于代碼復用
在 Objective-C 的世界里面經(jīng)常錯過的一個東西是抽象接口。接口(interface)這個詞通常指一個類的 .h 文件,但是它在 Java 程序員眼里有另外的含義:
一系列不依賴具體實現(xiàn)的方法的定義。
當實現(xiàn)一個 protocol 你總應該堅持里氏替換原則。
這個原則是:你應該可以取代任意接口(也就是Objective-C里的"protocol")實現(xiàn),而不用改變客戶端或者相關實現(xiàn)。
可以參考代碼
通過協(xié)議提供匿名對象
- 協(xié)議可以提供匿名類型。具體的對象類型可以淡化成遵從某協(xié)議的id類型,協(xié)議里規(guī)定了對象所應實現(xiàn)的方法。
- 使用匿名對象來隱藏類型名稱(或類名)
- 如果具體類型不重要,重要的是對象能夠響應(定義在協(xié)議里的)特定方法,那么可以使用匿名對象來表示
通知(Notification)
當你定義你自己的 NSNotification 的時候你應該把你的通知的名字定義為一個字符串常量,就像你暴露給其他類的其他字符串常量一樣。
你應該在公開的接口文件中將其聲明為 extern 的, 并且在對應的實現(xiàn)文件里面定義。
// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification
// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
對象的通訊
Block
當block作為異步接口的時候,盡量使用一個單獨的block作為接口的最后一個參數(shù)。把需要提供的數(shù)據(jù)和錯誤信息整合到一個單獨的block中,比分別提供要好
- 通常這成功處理和失敗處理會共享一些代碼(比如讓一個進度條或者提示消失);
- Apple 也是這樣做的,與平臺一致能夠帶來一些潛在的好處;
- block 通常會有多行代碼,如果不作為最后一個參數(shù)放在后面的話,會打破調用點;
- 使用多個 block 作為參數(shù)可能會讓調用看起來顯得很笨拙,并且增加了復雜性。
- 若 objects 不為 nil,則 error 必須為 nil
- 若 objects 為 nil,則 error 必須不為 nil
- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion {
if (objects) {
// do something with the data
}
else {
// some error occurred, 'error' variable should not be nil by contract
}
}
注意:Apple 提供的一些同步接口在成功狀態(tài)下向 error 參數(shù)(如果非 NULL) 寫入了垃圾值,所以檢查 error 的值可能出現(xiàn)問題。
關于block的一點知識
- block 是在棧上創(chuàng)建的
- block 可以復制到堆上
- block會捕獲棧上的變量(或指針),將其復制為自己私有的const(變量)。
- (如果在Block中修改Block塊外的)棧上的變量和指針,那么這些變量和指針必須用__block關鍵字申明(譯者注:否則就會跟上面的情況一樣只是捕獲他們的瞬時值)。
如果 block 沒有在其他地方被保持,那么它會隨著棧生存并且當棧幀(stack frame)返回的時候消失。
僅存在于棧上時,block對對象訪問的內存管理和生命周期沒有任何影響。
如果 block 需要在棧幀返回的時候存在,它們需要明確地被復制到堆上,這樣,block 會像其他 Cocoa 對象一樣增加引用計數(shù)。
當它們被復制的時候,它會帶著它們的捕獲作用域一起,retain 他們所有引用的對象。
如果一個 block引用了一個棧變量或指針,那么這個block初始化的時候會擁有這個變量或指針的const副本,
所以(被捕獲之后再在棧中改變這個變量或指針的值)是不起作用的。
當一個 block 被復制后,__block 聲明的棧變量的引用被復制到了堆里,復制完成之后,
無論是棧上的block還是剛剛產(chǎn)生在堆上的block(棧上block的副本)都會引用該變量在堆上的副本。
...
CGFloat blockInt = 10;
void (^playblock)(void) = ^{
NSLog(@"blockInt = %zd", blockInt);
};
blockInt ++;
playblock();
...
//結果為:blockInt = 10
self的循環(huán)引用
當使用代碼塊和異步分發(fā)的時候,要注意避免引用循環(huán)。 總是使用 weak 來引用對象,避免引用循環(huán)。
此外,把持有 block 的屬性設置為 nil (比如 self.completionBlock = nil) 是一個好的實踐。它會打破 block 捕獲的作用域帶來的引用循環(huán)。
// 多語句是需要在block里強引用一次
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
委托和數(shù)據(jù)源
委托模式 是 Apple 的框架里面使用廣泛的模式,同時它是四人幫的書“設計模式”中的重要模式之一。
委托代理模式是單向的,消息的發(fā)送方(委托方)需要知道接收方(代理方)是誰,反過來就沒有必要了。
對象之間耦合較松,發(fā)送方僅需知道它的代理方是否遵守相關 protocol 即可。
本質上,委托代理模式僅需要代理方提供一些回調方法,即代理方需要實現(xiàn)一系列空返回值的方法。
然而Apple并沒有遵守改原則,比如TableView的Delegate和DataSource
為了分離概念,我們應該這樣做:
委托模式(delegate pattern):事件發(fā)生的時候,委托者需要通知代理者。
數(shù)據(jù)源模式(datasource pattern): 委托者需要從數(shù)據(jù)源對象拉取數(shù)據(jù)。
@class ZOCSignUpViewController;
@protocol ZOCSignUpViewControllerDelegate <NSObject>
- (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller;
@end
@protocol ZOCSignUpViewControllerDataSource <NSObject>
- (ZOCUserCredentials *)credentialsForSignUpViewController:(ZOCSignUpViewController *)controller;
@end
@interface ZOCSignUpViewController : UIViewController
@property (nonatomic, weak) id<ZOCSignUpViewControllerDelegate> delegate;
@property (nonatomic, weak) id<ZOCSignUpViewControllerDataSource> dataSource;
@end
代理方法必須以調用者(即委托者)作為第一個參數(shù),就像上面的例子一樣。否則代理者無法區(qū)分不同的委托者實例。
繼承
有時候你可能需要重載代理方法??紤]有兩個 UIViewController 子類的情況:UIViewControllerA 和 UIViewControllerB,有下面的類繼承關系。
UIViewControllerA 遵從 UITableViewDelegate 并且實現(xiàn)了 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath.
你可能會想要在 UIViewControllerB 中提供一個不同的實現(xiàn),這個實現(xiàn)可能是這樣子的:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat retVal = 0;
if ([super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) {
retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath];
}
return retVal + 10.0f;
}
但如果超類沒有實現(xiàn)該方法時,此時調用[super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]方法,將使用 NSObject 的實現(xiàn)。
根據(jù)消息機制,會出現(xiàn)crash。
這時可以換一種實現(xiàn)方式:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat retVal = 0;
if ([[UIViewControllerA class] instancesRespondToSelector:@selector(tableView:heightForRowAtIndexPath:)]) {
retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath];
}
return retVal + 10.0f;
}
多重委托
多重委托是一個非常基礎的概念,但是,大多數(shù)開發(fā)者對此非常不熟悉而使用 NSNotifications。多重委托實現(xiàn)方式有多重,可參考LBDelegateMatrioska,可自行百度。
使用NSCache來構建緩存,而非NSDictionary
NSCache提供優(yōu)雅的自動刪減功能,而且線程安全,不會像Dictionary一樣拷貝Key.NSCache可與NSPurgeableData搭配使用,實現(xiàn)數(shù)據(jù)的自動清除功能。
Aspect Oriented Programming (AOP,面向切面編程)
Aspect Oriented Programming (AOP,面向切面編程) 在 Objective-C 社區(qū)內沒有那么有名,但是 AOP 在運行時可以有巨大威力。 但是因為沒有事實上的標準,Apple 也沒有開箱即用的提供,也顯得不重要,開發(fā)者都不怎么考慮它。
通常 AOP 被用來實現(xiàn)橫向切面。統(tǒng)計與日志就是一個完美的例子。
我們使用運行時的特性來增加切面:
- 在類的特定方法調用前運行特定的代碼
- 在類的特定方法調用后運行特定的代碼
- 增加代碼來替代原來的類的方法的實現(xiàn)
可參考Aspects