1: Objective-C語言起源
Objective-C(以下簡稱OC)由SmallTalk語言演化而來。OC采用"消息結(jié)構(gòu)"的語法方式,是一種動態(tài)語言。與傳統(tǒng)的“函數(shù)調(diào)用”式語言相比,OC實(shí)際執(zhí)行的動作由運(yùn)行時而非編譯期決定。就好像是“函數(shù)調(diào)用”式的函數(shù)是多態(tài)一樣。
OC的對象總是分配在“堆”上的。但是我們不需要使用 malloc 和 free來分配和釋放這些內(nèi)存,這些工作由OC的“引用計(jì)數(shù)“自動完成。
2: 在類的頭文件中盡量少引用其他頭文件
在C語言中我們已經(jīng)知道這一規(guī)則,即“前置聲明“(forward declaring)。在不需要知道某個類的詳細(xì)細(xì)節(jié)的時候,我們最好在頭文件中前置聲明該類,然后在實(shí)現(xiàn)文件中引用類的頭文件。如EPerson類有一個EEmployer的成員:
//EPerson.h
@class EEmployer;//前置聲明
@interface EPerson : NSObject
...
@property (nonatomic, strong) EEmployer *employer;
@end
//EPerson.mm
#import "EPerson.h"
#import "EEmployer.h"
@implementation EPerson
...
@end
這樣做有幾個好處: 一是可以優(yōu)化編譯時間;而是可以避免頭文件循環(huán)引用。
雖然#import指令可以避免死循環(huán),但意味著有一個類文件無法被正確編譯。
3. 多用字面量語法(string literal)
用類似C語言的語法,如:
NSString *somStr = @"This is a string literal";
NSNumber *someNum = @1;
NSNumber *floatNum = @2.5f;
NSNumber *boolNum = @YES;
NSArray *animals = @[@"cat",@"dog",@"mouse"];
NSDictionary *personDic = @{@"firstName":@"Matt", @"lastName":@"Galloway"};
優(yōu)點(diǎn):
- 簡潔易讀。
- 編寫、修改簡單。
- 對于數(shù)組和字典,還可以及早拋出異常。比如其中有nil的元素,字面量語法會直接拋出異常,但普通的alloc方法生成的數(shù)組或字典只會截取nil之前的元素,會誤導(dǎo)我們。
缺點(diǎn):
- 除了字符串以外,字面量語法創(chuàng)建的對象必須屬于Foundation框架,不能屬于自定義的類。
- 字面量語法創(chuàng)建的對象是不可變的,若要可變版本的對象,還要復(fù)制一份。
4.用類型常量代替宏定義
這點(diǎn)在C語言中就提到過,好處就是利用編譯器特性,可以驗(yàn)證類型。
如果常量只用在一個編譯單元內(nèi),則在其.m文件中用static const修飾
如果常量需要全局可見,則在一個頭文件中使用extern聲明全局變量,并在某一個實(shí)現(xiàn)文件中定義其值。這種常量出現(xiàn)在全局符號表中,通常用與之相關(guān)的類名做前綴。
5. 枚舉類型
typedef NS_ENUM(NSUInteger, EOCConnectionState) {
EOCConnectStateDisconnected,
EOCConnectStateConnecting,
EOCConnectStateConnected
}
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {
EOCPermittedDirectionUp = 1 << 0,
EOCPermittedDirectDown = 1 << 1,
EOCPermittedDirectLeft = 1 << 2,
EOCPermittedDirectRight = 1 << 3,
}
6. 理解"屬性"
@synthesize 可以更改默認(rèn)的實(shí)例變量名,但一半不推薦使用,為了使代碼可讀性更強(qiáng)。
@dynamic 可以阻止編譯器自動合成存取方法。而且編譯時發(fā)現(xiàn)沒有定義存取方法,也不會報錯,它相信這些方法能在運(yùn)行期間找到。
- 原子性
用在多線程同時訪問一個屬性的場景。開發(fā)中我們一般都用的是nonatomic,原因是原子性要使用同步鎖,這種開銷比較大,而且在一個線程在連續(xù)多次讀取某屬性值的時候有別的線程在同時改寫該值,那么即便將該屬性聲明為nonatomic,還是會讀到不同的屬性值,因而還是不能保證“線程安全”。若真想實(shí)現(xiàn)“線程安全“,還要更深層的鎖定機(jī)制。
讀寫權(quán)限
內(nèi)存管理語義
方法名
7. 對象內(nèi)部盡量直接訪問實(shí)例變量
直接使用實(shí)例變量_firstName與使用存取方法self.firstName有幾個區(qū)別:
1). 直接訪問實(shí)例變量不需要消息轉(zhuǎn)發(fā)機(jī)制,編譯器生成帶啊直接訪問實(shí)例變量所在的內(nèi)存區(qū)域,速度快。
2). 直接訪問實(shí)例變量不會調(diào)用”設(shè)置方法“,這樣繞過了屬性相關(guān)的"內(nèi)存管理語義“,這樣不太好。
3). 直接訪問實(shí)例變量,不會觸發(fā)KVO通知,也有可能出現(xiàn)問題。
4). 使用屬性方法助于斷點(diǎn)調(diào)試
這種方案是: 寫入實(shí)例變量時,使用設(shè)置方法,讀取實(shí)例變量時,直接訪問。這樣既可以提高讀寫速度,又可以確保屬性的“內(nèi)存管理語義”。
這個方案注意亮點(diǎn):
1). 在初始化方法中基本總是應(yīng)該直接訪問實(shí)例變量,除非待初始化的變量是聲明在超類里,我們又在子類中無法直接訪問。
2). 使用了懶加載技術(shù)后,都要通過存取方法來訪問。
8. 理解“對象等同性”
比較對象時,“==”操作符只是比較兩者指針本身,應(yīng)該使用"isEqual"方法或者對象本身提供的"等同性判斷方法",后者要求受測對象屬于同一個類。
NSObject協(xié)議中,有兩個用于判斷等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
如果isEqual方法判定兩個對象相等,那么hash方法也必須返回同一個值; 如果兩個對象的hash方法返回同一個值,那么isEqual方法未必認(rèn)為兩者相等。
覆寫hash方法時,既要考慮效率也要考慮碰撞率。
一些特定類具有自己的等同性判斷方法:
NSString -> isEqualToString
NSArray -> isEqualToArray
NSDictionary -> isEqualToDictionary
我們可以自己來判斷等同性,這樣既可以無須檢查參數(shù)類型,提升檢測速度,也使代碼更美觀易讀。
等同性判定有深度之分,比如NSArray可以比較每個元素是否相等(深度等同性判定),也可以只判定部分?jǐn)?shù)據(jù)是否相等。要根據(jù)具體需求制定檢測方案。
把可變對象放入容器之后,盡量不要再改變對象內(nèi)容,這樣有隱患。
9. 類族模式
類族模式可以隱藏抽象基類背后的實(shí)現(xiàn)細(xì)節(jié)。
“工廠模式”是其中之一。
Cocoa系統(tǒng)框架中有很多類族,如UIKit、NSArray等。
10. 關(guān)聯(lián)對象
這個在做method swizzling的時候會經(jīng)常用到。
將兩個對象關(guān)聯(lián)起來,再別的地方需要用到的時候再讀取出來,類似給對象動態(tài)添加屬性。
關(guān)聯(lián)時要指定存儲策略,類似于屬性添加內(nèi)存語義。
UIAlertView是一個好例子:
- (void)askUserQuestion {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question"
message:"What do you want to do?" delegate:self
cancelButtonTitle:@"cancel" otherButtonTitle:@"ok", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex) {
if (buttonIndex == 0) {
[self doCancel];
} else {
[self doContinue];
}
};
objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, BJC_ASSOCIATION_COPY);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
block(buttonIndex);
}
11. 理解消息傳遞(objc_msgSend)
這個術(shù)語我們已經(jīng)很熟悉了,Objective-C中就是objc_msgSend,使用動態(tài)綁定機(jī)制,在運(yùn)行時才決定調(diào)用那種方法。
編譯器會將所有的消息轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言調(diào)用:
void objc_msgSend(id self, SEL cmd, ...);
objc_msgSend會依據(jù)接受者與選擇器的類型來動態(tài)調(diào)用適當(dāng)?shù)姆椒?。首先,在接收者所屬的類中搜尋“方法列表”,若找到與選擇器名稱相符合的方法,就跳轉(zhuǎn)至其實(shí)現(xiàn)的代碼;若找不到,就沿著繼承體系繼續(xù)向上查找;若最終沒有找到,就執(zhí)行“消息轉(zhuǎn)發(fā)“(message forwarding)機(jī)制。
同時,objc_msgSend會將匹配的結(jié)果緩存在類的“快速映射表”中,以后遇到與選擇器相同的消息就可以直接執(zhí)行了。
每個類中有函數(shù)指針表(類似于C++中的虛函數(shù)表),指針指向函數(shù)的實(shí)現(xiàn)地址,選擇器的名稱是查表時用的key。