《禪與Objective-C 編程藝術》讀書筆記

前言

《禪與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的定義是1BOOL是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 { /* ... */ }

或者

image

忽略警告:

#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()注意單例的濫用

單例模式應該運用于類及類的接口趨向于作為單例來使用的情況

屬性,屬性應該盡可能描述性的命名,避免縮寫,而且是首字母小寫的駝峰命名,應該總是使用settergetter方法訪問屬性,除了initdealloc方法。

eg:NSString *text,不推薦NSString* textNSString * 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)isEqualhash方法。

如果兩個對象是被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


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容