最近再次拜讀了<<Effective Objective-C 2.0>>這本書(shū), 經(jīng)典的書(shū)確實(shí)值得閱讀, 并且里面的很多東西, 并不過(guò)時(shí), 書(shū)中有52條建議, 但這里筆者只是選取了其中的幾條來(lái)分享, 這幾條可能是我們?cè)陂_(kāi)發(fā)中比較常用的, 還有就是因?yàn)槠渌牟皇悄苡煤芏痰恼Z(yǔ)言寫(xiě)出來(lái)的, 如果你沒(méi)有讀過(guò)這本經(jīng)典的書(shū), 還是建議閱讀一下原書(shū).
- 對(duì)于OC中的對(duì)象聲明例如
NSObject *obj1 = [NSObject new];
, obj1這個(gè)指針變量是分配在棧上的, 他指向的是這一個(gè)分配在堆上面的實(shí)例對(duì)象, 如果進(jìn)行下面的賦值操作NSObject *obj2 = obj1;
,那么并沒(méi)有新生成一個(gè)實(shí)例對(duì)象, 只是在棧上分配了一個(gè)新的指針變量obj2, 而obj2和obj1指向的實(shí)例對(duì)象是同一個(gè).
- 關(guān)于文件頭文件的引入問(wèn)題, 一般情況下不建議在
A.h
文件中引入其他的B.h
文件, 因?yàn)樵趧e人引入A.h
的時(shí)候, 同時(shí)也引入了B.h
文件, 增加不必要的文件耦合和編譯時(shí)間, 一般在.h
文件中使用前向聲明
即@class B
, 而在.m文件中才真的引入頭文件, 當(dāng)然對(duì)于protocol不能使用前向聲明, 如果將protocol放在了另一個(gè).h文件中, 那么就必須要引入這個(gè)頭文件了. - 盡量使用字面量語(yǔ)法來(lái)初始化字符串, 數(shù)組, 字典等, 因?yàn)樽置媪空Z(yǔ)法其實(shí)是一種
語(yǔ)法糖
, 使用它可以讓代碼可讀性更高, 當(dāng)然對(duì)于一些必須要使用到初始化方法的時(shí)候字面量語(yǔ)法就不好用了.例如:
NSString *str = @"string";
NSArray *arr = @[obj1, obj2];
arr[1]// 讀取使用下標(biāo)而盡量不使用對(duì)應(yīng)的函數(shù)...
[array setObject:<#(nonnull id)#> atIndexedSubscript:<#(NSUInteger)#>]
- 少用#define來(lái)定義常量, 因?yàn)楹甓x只是簡(jiǎn)單的代碼替換, 并沒(méi)有類(lèi)型判斷, 不便于我們閱讀判斷, 同時(shí)宏定義可以被覆蓋, 當(dāng)別人引入了我們的頭文件的時(shí)候, 可能會(huì)覆蓋我們里面定義的宏, 帶來(lái)很麻煩的調(diào)試, 我們應(yīng)該使用C語(yǔ)言風(fēng)格的
const
,static
,extern
相結(jié)合來(lái)定義常量
/// 使用static 和const 定義文件內(nèi)部的常量 一般使用k開(kāi)頭命名
static float const kAnimationTime = 2.0f;
/// 使用const定義全局的常量, 在其他文件中可以通過(guò) extern float const kAnimationTime引入使用, 一般不用k開(kāi)頭命名, 而使用class名字
float const CustomAnimationTime = 2.0f;
- 用好枚舉, 使用枚舉來(lái)表示選項(xiàng), 狀態(tài)碼, 可以讓代碼更清晰, 這個(gè)在系統(tǒng)的API中也經(jīng)常看到, 比如按鈕的狀態(tài), autoresizing... , 例如如果你需要用一些狀態(tài)碼來(lái)表示網(wǎng)絡(luò)請(qǐng)求的結(jié)果: 你可能會(huì)有兩種方法
1. 定義一個(gè)整形變量, 然后說(shuō)明, 不同的整數(shù)代表不同的狀態(tài), 那么這樣對(duì)于開(kāi)發(fā)就很不方便, 必須得很清楚并且很正確的輸入對(duì)應(yīng)的整數(shù)才能表示相應(yīng)的狀態(tài), 那么就很容易出錯(cuò), 和不便于維護(hù)
int statusCode;
if (statusCode == 200) { }/// 請(qǐng)求成功
else if () ....
2. 使用枚舉, 對(duì)不同的狀態(tài)定義不同的名字, 這樣就很清晰方便了, 當(dāng)然定義的時(shí)候使用NS_ENUM比使用enum要`好`
typedef NS_ENUM(NSInteger, ErrorCode) {
ErrorCodeNotFind,
ErrorCodeLostConnection,
ErrorCodeUnknow
};
顯然上面你應(yīng)該選用枚舉, 同時(shí)還有一種情況就是, 定義多選項(xiàng)
, 這個(gè)你是會(huì)把他們都放進(jìn)一個(gè)數(shù)組中么?? 當(dāng)然不要這樣做, 這個(gè)時(shí)候也應(yīng)該使用枚舉來(lái)定義, 不過(guò)會(huì)有一點(diǎn)的小技巧, Apple對(duì)這種進(jìn)行了一個(gè)包裝, 使用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
};
因?yàn)樯厦娑x的枚舉值都為2的整數(shù)次冪值, 所以后面就可以使用位操作符 與(&)和或(|)來(lái)進(jìn)行選項(xiàng)的篩選
ErrorOptions options = ErrorOptionsOne | ErrorOptionsTwo; //--- 0011
if (options & ErrorOptionsOne) {// ErrorOptionsOne
// 結(jié)束判斷后面
}
else if (options & ErrorOptionsTwo) {// ErrorOptionsTwo
// ...
}
else {
// ...
}
- 需要遍歷操作的時(shí)候, 盡量不要用C語(yǔ)言風(fēng)格的for遍歷, 而是采用OC的 for-in方式的快速枚舉, 當(dāng)然使用block的方式來(lái)遍歷未必不是更好的一種方式, 尤其是遍歷字典的時(shí)候.
- 需要緩存的時(shí)候使用NSCache而不要使用NSArray或者NSDictionary, 因?yàn)槭褂肗SCache來(lái)進(jìn)行緩存當(dāng)內(nèi)存不足的時(shí)候系統(tǒng)會(huì)自動(dòng)清理緩存, 并且會(huì)首先清理緩存時(shí)間較長(zhǎng)的東西, 如果使用NSArray或者NSDictionary就沒(méi)有這個(gè)福利了
- 不要在load方法里面執(zhí)行耗時(shí)的操作, 因?yàn)檫@個(gè)時(shí)候會(huì)阻塞當(dāng)前的線(xiàn)程, 如果是主線(xiàn)程被阻塞, 那么...就不能接受用戶(hù)的響應(yīng), 同時(shí)不要在load方法里面使用其他的類(lèi)和調(diào)用函數(shù), 因?yàn)檫@個(gè)時(shí)候程序是
脆弱
的, 有可能使用的class還沒(méi)有被加載到系統(tǒng)中來(lái), 當(dāng)然使用Foundation里面的NSString...這些是沒(méi)有問(wèn)題的 - initialize這個(gè)方法在文檔中寫(xiě)明了是在第一次使用這個(gè)類(lèi)的時(shí)候才會(huì)調(diào)用一次(懶加載), 但是需要注意的是, 如果父類(lèi)中實(shí)現(xiàn)了initialize這個(gè)方法, 而子類(lèi)中沒(méi)有實(shí)現(xiàn)這個(gè)方法, 當(dāng)初始化子類(lèi)的時(shí)候, 父類(lèi)的這個(gè)initialize方法是會(huì)被調(diào)用多次的(消息轉(zhuǎn)發(fā)機(jī)制), 比如
ZJChildClass類(lèi)里面沒(méi)有重寫(xiě)initialize方法, 但是他的父類(lèi)重寫(xiě)了, 所以在初始化ZJChildClass的時(shí)候, 父類(lèi)的initialize會(huì)被調(diào)用兩次, 即會(huì)打印兩條
@interface ZJBaseClass : NSObject
@end
@implementation ZJBaseClass
+ (void)initialize {
NSLog(@"加載一次-----");
}
@end
@interface ZJChildClass : ZJBaseClass
@end
所以一般都是這樣來(lái)重寫(xiě)initialize方法的, 保證只會(huì)像我們期望的那樣調(diào)用
一次
+ (void)initialize {
if (self == [ZJBaseClass class]) { /// 不能用 [self class]
NSLog(@"加載一次-----");
}
}
- 對(duì)只需要執(zhí)行一次的代碼使用
dispatch_once
, 這樣可以保證線(xiàn)程安全
, 并且只執(zhí)行一次, 最常見(jiàn)的是用來(lái)實(shí)現(xiàn)單例
+ (instancetype)sharedInstance {
static Object *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self new];
});
return sharedInstance;
}
- 多用GCD少用NSObject的一些performSelector等方法, 因?yàn)槭褂胮erformSelector這種方式可能會(huì)造成內(nèi)存泄漏, 一般情況下使用GCD都可以完成, 比如dispatch_after來(lái)實(shí)現(xiàn)延時(shí)后執(zhí)行
- 使用NSTimer的時(shí)候要特別注意內(nèi)存泄漏的問(wèn)題, 因?yàn)镹STimer會(huì)持有目標(biāo)對(duì)象, 很容易造成循環(huán)引用的問(wèn)題, 也許你會(huì)想到在這個(gè)目標(biāo)對(duì)象的dealloc里面讓NSTimer失效(調(diào)用 invalidation 并且置為nil), 但是這根本就沒(méi)有用, 因?yàn)檠h(huán)引用的原因, 根本就不會(huì)調(diào)用dealloc方法, 所以在里面銷(xiāo)毀是沒(méi)有用的, 需要在對(duì)象被銷(xiāo)毀之前手動(dòng)銷(xiāo)毀計(jì)時(shí)器