- 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
因為上面定義的枚舉值都為2的整數次冪值, 所以后面就可以使用位操作符 與(&)和或(|)來進行選項的篩選typedef NS_OPTIONS(NSInteger, ErrorOptions) {ErrorOptionsNone = 0,ErrorOptionsOne = 1 << 0, ///左移操作 --- 1 --- 0001ErrorOptionsTwo = 1 << 1, --- 2 --- 0010ErrorOptionsThree = 1 << 2 --- 4 --- 0100 };
ErrorOptions options = ErrorOptionsOne | ErrorOptionsTwo; //--- 0011if (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方法是會被調用多次的(消息轉發機制), 比如
所以一般都是這樣來重寫initialize方法的, 保證只會像我們期望的那樣ZJChildClass類里面沒有重寫initialize方法, 但是他的父類重寫了, 所以在初始化ZJChildClass的時候, 父類的initialize會被調用兩次, 即會打印兩條@interface ZJBaseClass : NSObject@end@implementation ZJBaseClass+ (void)initialize {NSLog(@"加載一次-----");}@end@interface ZJChildClass : ZJBaseClass@end
調用
一次+ (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方法, 所以在里面銷毀是沒有用的, 需要在對象被銷毀之前手動銷毀計時器