重讀 Effective Objective-C 2.0 小記

最近再次拜讀了< >這本書, 經典的書確實值得閱讀, 而且裏面的不少東西, 並不過期, 書中有52條建議, 但這裏筆者只是選取了其中的幾條來分享, 這幾條多是咱們在開發中比較經常使用的, 還有就是由於其餘的不是能用很短的語言寫出來的, 若是你沒有讀過這本經典的書, 仍是建議閱讀一下原書. objective-c


  1. 對於OC中的對象聲明例如NSObject *obj1 = [NSObject new];, obj1這個指針變量是分配在棧上的, 他指向的是這一個分配在堆上面的實例對象, 若是進行下面的賦值操做NSObject *obj2 = obj1;,那麼並無新生成一個實例對象, 只是在棧上分配了一個新的指針變量obj2, 而obj2和obj1指向的實例對象是同一個.
  2. 關於文件頭文件的引入問題, 通常狀況下不建議在A.h文件中引入其餘的B.h文件, 由於在別人引入A.h的時候, 同時也引入了B.h文件, 增長沒必要要的文件耦合和編譯時間, 通常在.h文件中使用前向聲明@class B, 而在.m文件中才真的引入頭文件, 固然對於protocol不能使用前向聲明, 若是將protocol放在了另外一個.h文件中, 那麼就必需要引入這個頭文件了.
  3. 儘可能使用字面量語法來初始化字符串, 數組, 字典等, 由於字面量語法實際上是一種語法糖, 使用它可讓代碼可讀性更高, 固然對於一些必需要使用到初始化方法的時候字面量語法就很差用了.例如:
    NSString *str = @"string";
    NSArray *arr = @[obj1, obj2];
    arr[1]// 讀取使用下標而儘可能不使用對應的函數...
    [array setObject:<#(nonnull id)#=""> atIndexedSubscript:<#(nsuinteger)#>]
        
        
        
    
       
        
        
        
    
       複製代碼
  4. 少用#define來定義常量, 由於宏定義只是簡單的代碼替換, 並無類型判斷, 不便於咱們閱讀判斷, 同時宏定義能夠被覆蓋, 當別人引入了咱們的頭文件的時候, 可能會覆蓋咱們裏面定義的宏, 帶來很麻煩的調試, 咱們應該使用C語言風格的 const, static, extern相結合來定義常量
    /// 使用static 和const 定義文件內部的常量 通常使用k開頭命名
    static float const kAnimationTime = 2.0f;
    /// 使用const定義全局的常量, 在其餘文件中能夠經過 extern float const kAnimationTime引入使用, 通常不用k開頭命名, 而使用class名字
    float const CustomAnimationTime = 2.0f;複製代碼
  5. 用好枚舉, 使用枚舉來表示選項, 狀態碼, 可讓代碼更清晰, 這個在系統的API中也常常看到, 好比按鈕的狀態, autoresizing... , 例如若是你須要用一些狀態碼來表示網絡請求的結果: 你可能會有兩種方法
    1. 定義一個整形變量, 而後說明, 不一樣的整數表明不一樣的狀態, 那麼這樣對於開發就很不方便, 必須得很清楚而且很正確的輸入對應的整數才能表示相應的狀態, 那麼就很容易出錯, 和不便於維護
     int statusCode;
     if (statusCode == 200) { }/// 請求成功
     else if () ....
     2. 使用枚舉, 對不一樣的狀態定義不一樣的名字, 這樣就很清晰方便了, 固然定義的時候使用NS_ENUM比使用enum要`好`
    typedef NS_ENUM(NSInteger, ErrorCode) {
     ErrorCodeNotFind,
     ErrorCodeLostConnection,
     ErrorCodeUnknow
    };複製代碼
    顯然上面你應該選用枚舉, 同時還有一種狀況就是, 定義多選項, 這個你是會把他們都放進一個數組中麼?? 固然不要這樣作, 這個時候也應該使用枚舉來定義, 不過會有一點的小技巧, Apple對這種進行了一個包裝, 使用NS_OPTIONS而不是enum
    typedef NS_OPTIONS(NSInteger, ErrorOptions) {
     ErrorOptionsNone = 0,
     ErrorOptionsOne = 1 << 0, ///左移操做    --- 1 --- 0001
     ErrorOptionsTwo = 1 << 1,               --- 2 --- 0010
     ErrorOptionsThree = 1 << 2              --- 4 --- 0100
    };複製代碼
    由於上面定義的枚舉值都爲2的整數次冪值, 因此後面就可使用位操做符 與(&)和或(|)來進行選項的篩選
    ErrorOptions options = ErrorOptionsOne | ErrorOptionsTwo; //--- 0011
     if (options & ErrorOptionsOne) {// ErrorOptionsOne
         // 結束判斷後面
     }
     else if (options & ErrorOptionsTwo) {// ErrorOptionsTwo
         // ...
     }
     else {
         // ...
     }複製代碼
  6. 須要遍歷操做的時候, 儘可能不要用C語言風格的for遍歷, 而是採用OC的 for-in方式的快速枚舉, 固然使用block的方式來遍歷未必不是更好的一種方式, 尤爲是遍歷字典的時候.
  7. 須要緩存的時候使用NSCache而不要使用NSArray或者NSDictionary, 由於使用NSCache來進行緩存當內存不足的時候系統會自動清理緩存, 而且會首先清理緩存時間較長的東西, 若是使用NSArray或者NSDictionary就沒有這個福利了
  8. 不要在load方法裏面執行耗時的操做, 由於這個時候會阻塞當前的線程, 若是是主線程被阻塞, 那麼...就不能接受用戶的響應, 同時不要在load方法裏面使用其餘的類和調用函數, 由於這個時候程序是脆弱的, 有可能使用的class尚未被加載到系統中來, 固然使用Foundation裏面的NSString...這些是沒有問題的
  9. initialize這個方法在文檔中寫明瞭是在第一次使用這個類的時候纔會調用一次(懶加載), 可是須要注意的是, 若是父類中實現了initialize這個方法, 而子類中沒有實現這個方法, 當初始化子類的時候, 父類的這個initialize方法是會被調用屢次的(消息轉發機制), 好比
    ZJChildClass類裏面沒有重寫initialize方法, 可是他的父類重寫了, 因此在初始化ZJChildClass的時候, 父類的initialize會被調用兩次, 即會打印兩條
     @interface ZJBaseClass : NSObject
     @end
     @implementation ZJBaseClass
     + (void)initialize {
         NSLog(@"加載一次-----");
     }
     @end
     @interface ZJChildClass : ZJBaseClass
     @end複製代碼
    因此通常都是這樣來重寫initialize方法的, 保證只會像咱們指望的那樣調用一次
    + (void)initialize {
         if (self == [ZJBaseClass class]) { /// 不能用 [self class]
             NSLog(@"加載一次-----");
        }
     }複製代碼
  10. 對只須要執行一次的代碼使用dispatch_once, 這樣能夠保證線程安全, 而且只執行一次, 最多見的是用來實現單例
    + (instancetype)sharedInstance {
    static Object *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [self new];
    });
    return sharedInstance;
    }複製代碼
  11. 多用GCD少用NSObject的一些performSelector等方法, 由於使用performSelector這種方式可能會形成內存泄漏, 通常狀況下使用GCD均可以完成, 好比dispatch_after來實現延時後執行
  12. 使用NSTimer的時候要特別注意內存泄漏的問題, 由於NSTimer會持有目標對象, 很容易形成循環引用的問題, 也許你會想到在這個目標對象的dealloc裏面讓NSTimer失效(調用 invalidation 而且置爲nil), 可是這根本就沒有用, 由於循環引用的緣由, 根本就不會調用dealloc方法, 因此在裏面銷燬是沒有用的, 須要在對象被銷燬以前手動銷燬計時器
相關文章
相關標籤/搜索