一. 常見(jiàn)實(shí)用技巧
1. 在類(lèi)的頭文件中盡量少引用其他頭文件
一般來(lái)說(shuō),應(yīng)當(dāng)在某個(gè)類(lèi)的頭文件使用向前聲明來(lái)提及別的類(lèi),并在類(lèi)的實(shí)現(xiàn)引入那些類(lèi)的頭文件。這樣可以降低類(lèi)之間的耦合。
有時(shí)無(wú)法使用聲明筒严,比如聲明某個(gè)類(lèi)遵守一項(xiàng)協(xié)議。盡量把該協(xié)議的聲明放在分類(lèi)中情萤,或單獨(dú)頭文件引入鸭蛙。
2. 多用字面量語(yǔ)法,少用與之等價(jià)的語(yǔ)法
字面量語(yǔ)法其實(shí)是一種“語(yǔ)法糖”筋岛,與常規(guī)方法比更加扼要簡(jiǎn)介娶视。
NSString *string = @"string";
NSNumber *number = @1;
NSArray *array = @[@"1",@"2",@"3"];
NSDictionary *dic = @[@"key":@"value"];
array[1];
dic[@"key"];
//前面都是不可變對(duì)象,要想創(chuàng)建可變對(duì)象
NSMutableArray *mutableArr = [@[@"1",@"2",@"3"] mutableCopy];
3. 多用類(lèi)型常量泉蝌,少用#define預(yù)處理指令
不要用預(yù)處理指定定義常量歇万,這樣定義出來(lái)的常量不含類(lèi)型信息,編譯器只是在編譯前據(jù)此執(zhí)行查找和替換操作勋陪。即使有人重新定義了常值量,編譯器也不會(huì)警告硫兰。
在實(shí)現(xiàn)文件中使用static const 定義“只在編譯單元內(nèi)可見(jiàn)的常量”诅愚。此類(lèi)常量不在全局符號(hào)表,無(wú)需加前綴劫映。
static const NSTimeInterval kAnimationDuration = 0.5;
對(duì)外公開(kāi)常量時(shí)在頭文件使用extern聲明全局常量违孝,在實(shí)現(xiàn)文件定義其值。這種常量會(huì)出現(xiàn)在全局符號(hào)表泳赋,其名稱(chēng)要加以區(qū)隔雌桑,通常用類(lèi)名作為前綴。
//.h中
extern NSString *const EOCStringConstant;
//.m中
NSString *const EOCStringConstant = @"VALUE";
4. 用枚舉表示狀態(tài)祖今、選項(xiàng)校坑、狀態(tài)碼
用NS_ENUM和NS_OPTION(用于多個(gè)選項(xiàng)同時(shí)使用)宏來(lái)定義枚舉類(lèi)型,并指明其底層數(shù)據(jù)類(lèi)型千诬。在處理枚舉類(lèi)型的switch語(yǔ)句中不要實(shí)現(xiàn)default分支耍目。
5. 用前綴避免命名空間沖突
Objective-C沒(méi)有其他語(yǔ)言那種內(nèi)置的命名空間機(jī)制。如果發(fā)生命名沖突徐绑,那么應(yīng)用程序的鏈接過(guò)程就會(huì)出錯(cuò)邪驮。所以要選擇與你的公司、應(yīng)用程序或二者皆有關(guān)聯(lián)之名稱(chēng)作為類(lèi)名(包括分類(lèi))的前綴傲茄。一個(gè)容易忽略的地方毅访,實(shí)現(xiàn)文件里面的純C函數(shù)和全局變量沮榜,在編譯好的目標(biāo)文件中,這些名稱(chēng)要作為“頂級(jí)符號(hào)”喻粹,所以也要加上前綴蟆融。
6. 總為第三方的分類(lèi)名稱(chēng)加前綴
向第三方添加分類(lèi)時(shí),要為分類(lèi)名稱(chēng)和里面的方法名加上自己專(zhuān)用的前綴磷斧。
7. 勿在分類(lèi)中聲明屬性
把封裝數(shù)據(jù)所用的全部屬性都定義在主接口(主文件)振愿。在分類(lèi)拓展其他功能(包括存取方法),但盡量不要定義屬性弛饭。
8. 盡量使用不可變對(duì)象
盡量使用不可變對(duì)象冕末。若某屬性?xún)H對(duì)對(duì)象內(nèi)部修改,則在.m中將其readonly屬性拓展為readwrite屬性侣颂。
不要把可變的collection作為屬性公開(kāi)(防止通過(guò)該屬性直接修改內(nèi)容档桃,有可能還要執(zhí)行某些操作),而應(yīng)是提供相關(guān)方法來(lái)修改對(duì)象中的collection憔晒。
9. 為私有方法名加前綴
為私有方法的名稱(chēng)加前綴容易將其和公共方法區(qū)分開(kāi)藻肄。不能單用_作為前綴,這是預(yù)留給蘋(píng)果公的。
- (void)p_doSomething{
//.........
}
二. 高級(jí)技巧部分
1. 提供全能初始化方法
在類(lèi)中提供一個(gè)全能初始化方法拒担,其他初始化方法均應(yīng)調(diào)用此方法嘹屯。
若全能初始化方法與父類(lèi)不同,則需要覆蓋父類(lèi)對(duì)應(yīng)的方法从撼。如果父類(lèi)的初始化方法不適合子類(lèi)州弟,那么應(yīng)該覆寫(xiě)這個(gè)父類(lèi)方法,并在其中拋出異常低零。
- (instancetype)init{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must use initWithDimension: instead " userInfo:nil];
}
2. 實(shí)現(xiàn)description方法
實(shí)現(xiàn)description方法返回一個(gè)有意義的字符串婆翔,用以描述該實(shí)例。如果想在調(diào)試時(shí)打印出更詳細(xì)的對(duì)象信息(用LLDB調(diào)試)掏婶,可以實(shí)現(xiàn)debugDescription方法啃奴。
3. 在對(duì)象內(nèi)部盡量直接訪問(wèn)實(shí)例變量
在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí),應(yīng)該直接使用實(shí)例變量來(lái)讀(不需要經(jīng)過(guò)方法派送雄妥,直接訪問(wèn)內(nèi)存)最蕾,而寫(xiě)入數(shù)據(jù)時(shí),應(yīng)該通過(guò)屬性來(lái)寫(xiě)茎芭。
在初始化方法和dealloc方法中揖膜,總是應(yīng)該直接使用實(shí)例變量來(lái)讀寫(xiě)數(shù)據(jù)。懶加載情況下梅桩,需要通過(guò)屬性來(lái)讀寫(xiě)數(shù)據(jù)壹粟。
4. 以“類(lèi)族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
使用類(lèi)族模式可以靈活應(yīng)對(duì)多個(gè)類(lèi),將它們的實(shí)現(xiàn)細(xì)節(jié)隱藏在抽象基類(lèi)中,以保持接口簡(jiǎn)介趁仙。用戶無(wú)需創(chuàng)建子類(lèi)實(shí)例洪添,只需調(diào)用基類(lèi)方法來(lái)創(chuàng)建即可。系統(tǒng)框架中有很多類(lèi)族雀费,比如NSArray和NSMutableArray干奢。
/* Employee.h */
typedef NS_ENUM(NSUInteger,EmployeeType) {
EmployeeTypeDeveloper,
EmployeeTypeDesginer,
EmployeeTypeFinance
};
@interface Employee : NSObject
+ (Employee)employeeWithTypt:(EmployeeType)type;
- (void)doWork;
@end
/* Employee.m */
@implementation Employee
+ (Employee)employeeWithTypt:(EmployeeType)type{
switch (type) {
case EmployeeTypeDeveloper:
return [[EmployeeDeveloper alloc] init];
break;
case EmployeeTypeFinance:
return [[EmployeeFinance alloc] init];
break;
case EmployeeTypeDesginer:
return [[EmployeeDesginer alloc] init];
break;
}
}
- (void)doWork{
//子類(lèi)實(shí)現(xiàn)
}
@end
/* EmployeeDesginer.m */
@implementation EmployeeDesginer
- (void)doWork{
//具體實(shí)現(xiàn)
}
@end
5. 在既有類(lèi)中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
有時(shí)需要在某類(lèi)存放相關(guān)信息杰标,當(dāng)我們不方便繼承該類(lèi)來(lái)改寫(xiě)汗侵,就可以直接在該類(lèi)使用關(guān)聯(lián)對(duì)象。不過(guò)使用關(guān)聯(lián)對(duì)象容易引入難以查找的BUG撩扒,比如循環(huán)引用辕羽。
//設(shè)置關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
//獲取關(guān)聯(lián)對(duì)象值
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
//移除所有關(guān)聯(lián)對(duì)象值
objc_removeAssociatedObjects(id _Nonnull object)
6. 通過(guò)委托與數(shù)據(jù)源協(xié)議進(jìn)行對(duì)象間的通訊
常規(guī)的委托模式中逛尚,信息從類(lèi)流向受委托者(Delegate)。也可以用協(xié)議定義一套數(shù)據(jù)源接口刁愿,讓類(lèi)從數(shù)據(jù)源(DataSource)獲取數(shù)據(jù)绰寞,這樣信息就是從數(shù)據(jù)源流向類(lèi)。(比如UITableView中的delegate和DataSource铣口,一個(gè)處理用戶和列表的操作滤钱,一個(gè)提供列表顯示的數(shù)據(jù))
若有必要,可實(shí)現(xiàn)含有位段的結(jié)構(gòu)體脑题,將委托對(duì)象能否響應(yīng)協(xié)議方法緩存其中件缸。
struct {
unsigned numberOfSectionsInTableView : 1;
unsigned titleForHeaderInSection : 1;
unsigned titleForFooterInSection : 1;
unsigned commitEditingStyle : 1;
unsigned canEditRowAtIndexPath : 1;
} _dataSourceHas;
7. 將類(lèi)的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類(lèi)中
使用分類(lèi)機(jī)制把類(lèi)的實(shí)現(xiàn)代碼按功能劃分成易于管理的小塊。這樣類(lèi)中的方法也不會(huì)過(guò)于臃腫叔遂,使用分類(lèi)也便于調(diào)試停团。
將私有方法歸入名叫Private的分類(lèi)中,以隱藏細(xì)節(jié)掏熬。
8. 使用類(lèi)拓展(匿名分類(lèi))隱藏實(shí)現(xiàn)細(xì)節(jié)
通過(guò)類(lèi)擴(kuò)展向類(lèi)中新增實(shí)例變量,也把私有方法的聲明放在其中秒梅。
如果某屬性在主接口聲明readonly旗芬,而類(lèi)內(nèi)部又要設(shè)置方法修改此屬性,那就在類(lèi)拓展中將其改為readwrite捆蜀。
如果想讓遵守的協(xié)議不為人知疮丛,則可在類(lèi)拓展中聲明。
9. 通過(guò)協(xié)議提供匿名對(duì)象
協(xié)議可在某種程度上提供匿名類(lèi)型辆它。具體的對(duì)象類(lèi)型可以淡化成遵守某協(xié)議的id類(lèi)型誊薄,協(xié)議里規(guī)定對(duì)象所應(yīng)實(shí)現(xiàn)的方法。如果具體類(lèi)型不重要锰茉,重要的是對(duì)象能夠響應(yīng)(定義在協(xié)議里)的特定方法呢蔫,那么可以使用匿名對(duì)象來(lái)表示。
- (void)setValue:(id<NSCopying>)value forKey:(NSString *)key
10. 在dealloc方法中只釋放引用并解除監(jiān)聽(tīng)
在dealloc方法中飒筑,應(yīng)該做的事就是釋放指向其他對(duì)象的引用片吊,并取消監(jiān)聽(tīng)(KVC或通知)绽昏。如果對(duì)象持有文件描述符或套接字等系統(tǒng)資源,應(yīng)該在dealloc之前提供一個(gè)close方法來(lái)釋放資源俏脊。
執(zhí)行異步任務(wù)的方法不應(yīng)在dealloc里調(diào)用全谤,只能在正常狀態(tài)下執(zhí)行的那些方法也不應(yīng)在dealloc里調(diào)用,因?yàn)榇藭r(shí)對(duì)象已經(jīng)處于被回收階段爷贫。
11. 編寫(xiě)“異常安全代碼”時(shí)留存內(nèi)存管理問(wèn)題
如果手動(dòng)管理引用計(jì)數(shù)认然,而且必須捕獲異常,一定要注意將try內(nèi)所創(chuàng)立的對(duì)象清理干凈漫萄。
UIView *view = nil;
@try{
view = [[UIView alloc] init];
[view addSubview:[UIView new]];
}
@catch(...){
NSLog(@"there was an error")
}
@finally{
[view release];
}
若只用ARC且要捕獲異常卷员,則需要打開(kāi)編譯器的-fobjc-arc-expections標(biāo)志,因?yàn)锳RC不自動(dòng)生成安全處理異常所需的清理代碼卷胯。開(kāi)啟標(biāo)志后子刮,編譯器會(huì)自動(dòng)生成這種代碼,不過(guò)會(huì)導(dǎo)致程序變大窑睁,降低運(yùn)行效率挺峡。在發(fā)現(xiàn)大量異常捕獲操作時(shí),應(yīng)該考慮重構(gòu)代碼担钮,使用NSError錯(cuò)誤傳遞法來(lái)取代異常橱赠。
12. 用僵尸對(duì)象調(diào)試內(nèi)存管理問(wèn)題
系統(tǒng)在回收對(duì)象時(shí),可以不將其真的回收箫津,而是將它轉(zhuǎn)成僵尸對(duì)象狭姨。通過(guò)環(huán)境變量NSZombieEnabled可開(kāi)啟此功能。
系統(tǒng)會(huì)修改對(duì)象的isa指針苏遥,另其指向特俗的僵尸類(lèi)饼拍,從而使該對(duì)象變成僵尸對(duì)象。僵尸類(lèi)能響應(yīng)所有的選擇子田炭,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接收者的消息师抄。然后終止程序。
13.多用塊枚舉教硫,少用for循環(huán)
遍歷collection有四種方法叨吮。
- for循環(huán)
- NSEnumerator遍歷法
NSArray *array = @[@"2",@"3",@"4"];
NSEnumerator *enumer = [array objectEnumerator];
id object;
while ((object = [enumer nextObject]) != nil){
NSLog(@"%@",object);
}
- 快速遍歷法
//反向
for (id object in [array reverseObjectEnumerator]) {
NSLog(@"%@",object);
}
- 塊枚舉法
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@",obj);
//遍歷到下標(biāo)1就停止
if (idx == 1) {
*stop = YES;
}
}];
塊遍歷法是最新、最先進(jìn)的瞬矩。塊遍歷法可以獲取更多信息茶鉴,也可以修改方法簽名(id obj -> 確定類(lèi)型),避免類(lèi)型轉(zhuǎn)換景用。另外涵叮,塊遍歷法本身能通過(guò)GCD并發(fā)執(zhí)行遍歷操作,無(wú)需另外編寫(xiě)代碼,而其他遍歷方式則無(wú)法輕易實(shí)現(xiàn)這一點(diǎn)围肥。
16.對(duì)自定義其內(nèi)存管理語(yǔ)義的collection使用無(wú)縫橋接
通過(guò)無(wú)縫橋接技術(shù)剿干,可以在Foundation框架的OC對(duì)象和CoreFoundation框架的C語(yǔ)言結(jié)構(gòu)體之間來(lái)回轉(zhuǎn)換。
NSArray *array = @[@"1",@"2",@"3"];
//cfArray是指向struct__CFArray的指針
CFArrayRef cfArray = (__bridge CFArrayRef)array;
NSLog(@"count = %ld",CFArrayGetCount(cfArray));
在CoreFoundation層面創(chuàng)建collection時(shí)穆刻,可以指定許多回調(diào)函數(shù)來(lái)處理其元素置尔。然后,通過(guò)無(wú)縫橋接技術(shù)氢伟,將其轉(zhuǎn)換為具備特殊內(nèi)存管理語(yǔ)義的OC對(duì)象榜轿。
14.構(gòu)建緩存時(shí)選用NSCache而非NSDictionary
實(shí)現(xiàn)緩存時(shí)應(yīng)選用NSCache而非NSDictionary。因?yàn)镹SCache可以提供優(yōu)雅的自動(dòng)刪減功能朵锣,而且是線程安全的谬盐,此外,它與字典不同诚些,并不會(huì)拷貝鍵飞傀。
可以給NSCache對(duì)象設(shè)置上限,用以限制緩存中對(duì)象個(gè)數(shù)及總成本诬烹,而這些尺度則定義了刪減其中對(duì)象的時(shí)機(jī)砸烦。但是絕對(duì)不能把這些尺度當(dāng)成可靠的硬限制,它們僅對(duì)NSCache起指導(dǎo)作用绞吁。
- (void)downloadDataWithURL:(NSURL *)url{
NSPurgeableData *cahceData = [_cache objectForKey:url];
if (cahceData) {
//purge引用計(jì)數(shù) +1幢痘,
[cahceData beginContentAccess];
[self useData:cahceData];
//purge引用計(jì)數(shù) -1,變?yōu)?告訴系統(tǒng)必要時(shí)可以丟棄自己占據(jù)的內(nèi)存
[cahceData endContentAccess];
}else{
//網(wǎng)絡(luò)下載數(shù)據(jù)
NSData *fetchData = [self fetchDataWithURL:url];
NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:fetchData];
[_cache setObject:purgeableData forKey:url cost:purgeableData.length];
//不需要beginContentAccess家破,類(lèi)似于內(nèi)存管理颜说,創(chuàng)建的過(guò)程purge引用計(jì)數(shù)也會(huì)加1
[self useData:cahceData];
[cahceData endContentAccess];
}
}
將NSPurgeableData和NSCache配套使用,可以實(shí)現(xiàn)自動(dòng)清除功能汰聋。當(dāng)NSPurgeableData對(duì)象所占內(nèi)存為系統(tǒng)所丟棄時(shí)门粪,該對(duì)象自身也會(huì)從緩存中清除。
如果緩存使用得當(dāng)烹困,那么應(yīng)用程序的響應(yīng)速度就會(huì)提高庄拇。只有那種重新計(jì)算起來(lái)很費(fèi)事的數(shù)據(jù),才值得放入緩存韭邓,比如那些需要從網(wǎng)絡(luò)獲取或從磁盤(pán)讀取的數(shù)據(jù)。
15.精簡(jiǎn)load和ininialize的實(shí)現(xiàn)代碼
在加載階段溶弟,如果類(lèi)實(shí)現(xiàn)了load方法女淑,那么系統(tǒng)就會(huì)調(diào)用它。分類(lèi)里也可以定義此方法辜御,類(lèi)的load方法要比分類(lèi)的先調(diào)用鸭你。與其他方法不同,load方法不參與覆寫(xiě)機(jī)制(只會(huì)實(shí)現(xiàn)類(lèi)自身的load方法)。
在load方法中袱巨,盡量減少執(zhí)行的操作阁谆,因?yàn)檎麄€(gè)程序在執(zhí)行l(wèi)oad方法時(shí)變得阻塞,不要在里面調(diào)用可能加鎖的方法愉老,正常也不寫(xiě)其他任務(wù)场绿。其主要作用是用來(lái)調(diào)試,比如在分類(lèi)寫(xiě)方法判斷是否正確加載嫉入。
首次使用某個(gè)類(lèi)之前焰盗,系統(tǒng)會(huì)向其發(fā)送ininialize消息(惰性加載)。由于此方法遵從覆寫(xiě)規(guī)則咒林,所以通常要在里面判斷初始化的是哪個(gè)類(lèi)熬拒。無(wú)法在編譯期設(shè)定的全局常量,可以放在ininialize初始哈垫竞。
static NSMutableArray *array;
//會(huì)先調(diào)用父類(lèi)的再調(diào)用自己的
+ (void)initialize{
if (self == [YCCache class]) {
//執(zhí)行操作
array = [NSMutableArray array];
}
}
所以load方法和ininialize方法應(yīng)該實(shí)現(xiàn)得精簡(jiǎn)一點(diǎn)澎粟,有助于保持應(yīng)用程序響應(yīng)能力,也能減少引入依賴(lài)環(huán)欢瞪。
16.別忘了NSTimer會(huì)保留其目標(biāo)對(duì)象
NSTimer對(duì)象會(huì)保留其目標(biāo)活烙,直到計(jì)時(shí)器本身失效為止,調(diào)用invalidate方法可使計(jì)時(shí)器失效引有。另外瓣颅,一次性的計(jì)時(shí)器在觸發(fā)完任務(wù)后也會(huì)失效。反復(fù)執(zhí)行的計(jì)時(shí)器容易引入保留環(huán)(比如計(jì)時(shí)器和控制器)譬正,可以擴(kuò)充N(xiāo)STimer的功能宫补,用Block來(lái)打破保留環(huán)。
@implementation NSTimer (YCBlocksSupport)
- (NSTimer *)yc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats{
return [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(yc_blockInvoke:) userInfo:block repeats:repeats];
}
- (void)yc_blockInvoke:(NSTimer *)timer{
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
17. 不要使用retainCount
雖然在ARC已將retainCount方法廢棄了曾我,但是即使在MRC中也是不應(yīng)該調(diào)用的粉怕。對(duì)象的保留計(jì)數(shù)看似有用,實(shí)際上在任何給定的時(shí)間點(diǎn)的“保留計(jì)數(shù)”都無(wú)法反應(yīng)對(duì)象生命周期的全貌抒巢。
比如像單例對(duì)象贫贝,其引用計(jì)數(shù)很大,也絕對(duì)不會(huì)變蛉谜,其保留和釋放操作都是空操作稚晚。即使是普通對(duì)象,可能也處于自動(dòng)釋放池中型诚,其保留計(jì)數(shù)也不是準(zhǔn)確的客燕。retainCount可能永遠(yuǎn)不返回0,因?yàn)橛袝r(shí)系統(tǒng)會(huì)優(yōu)化對(duì)象的釋放行為狰贯,在保留計(jì)數(shù)為1的時(shí)候就把它回收了也搓。
三. 對(duì)象相關(guān)概念
1. 了解Objective-C的起源
Objective-C使用的是“消息結(jié)構(gòu)”而非“函數(shù)調(diào)用”赏廓。區(qū)別在于使用消息結(jié)構(gòu)的語(yǔ)言,其運(yùn)行時(shí)所需執(zhí)行的代碼由運(yùn)行環(huán)境決定傍妒,而使用函數(shù)調(diào)用的語(yǔ)言幔摸,則由編譯器決定。
Objective-C為C語(yǔ)言添加了面向?qū)ο蟮奶匦圆罚瞧涑纫洹bjective-C使用動(dòng)態(tài)綁定的消息結(jié)構(gòu),在運(yùn)行時(shí)才檢查對(duì)象類(lèi)型昔案。接收一條消息之后尿贫,執(zhí)行什么代碼由編譯環(huán)境決定。
2. 理解“屬性”
使用屬性@property踏揣,編譯器會(huì)在編譯器自動(dòng)合成訪問(wèn)這些屬性所需的方法庆亡,并且自動(dòng)向類(lèi)中添加適當(dāng)類(lèi)型的實(shí)例變量(以_開(kāi)頭)。如果想改名捞稿,可以使用@syntheszize語(yǔ)法又谋。如果想阻止編譯器自動(dòng)合成存取方法,可以使用@dynamic關(guān)鍵字娱局。
3. 理解“對(duì)象等同性”
想判斷對(duì)象的等同性彰亥,需要重寫(xiě)“isEqual:”和hash方法。相同的對(duì)象必須有相同的哈希碼衰齐,但是哈希碼相同的對(duì)象卻未必相同任斋。編寫(xiě)hash方法時(shí),應(yīng)該使用計(jì)算速度最快且哈希碰撞幾率低的算法耻涛。
不要盲目地逐個(gè)監(jiān)測(cè)每個(gè)屬性废酷,應(yīng)該依照具體需求來(lái)指定監(jiān)測(cè)方案。
4. 理解Objective-C錯(cuò)誤模型
只有發(fā)生了可使整個(gè)應(yīng)用程序崩潰的嚴(yán)重錯(cuò)誤時(shí)抹缕,才使用異常澈蟆。
- (instancetype)mustOverwriteMethod{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must be overwriden " userInfo:nil];
}
在錯(cuò)誤不那么嚴(yán)重的情況下(提醒用戶即可),可以用委托方法來(lái)處理錯(cuò)誤(將NSError傳給處理異常的類(lèi))卓研。
- (void)connection:(NSURLConnection *)connection didiFailWithError:(NSError *)error;
也可以將錯(cuò)誤信息放在NSError對(duì)象里趴俘,經(jīng)由"輸出參數(shù)"返回給調(diào)用者。
- (void)test{
NSError *error = nil;
[self doSomething:&error];
if (error) {
//.....
}
}
- (void)doSomething:(NSError * __autoreleasing *)error{
//.....
}
5. 理解NSCopying協(xié)議
若想讓自己寫(xiě)的對(duì)象有Copy功能奏赘,則需要實(shí)現(xiàn)NSCopying協(xié)議寥闪。若自定義的對(duì)象有可變版本和不可變版本,就要同時(shí)實(shí)現(xiàn)NSCopying和NSMutableCopying協(xié)議磨淌。
復(fù)制對(duì)象時(shí)需決定采用深拷貝還是淺拷貝橙垢,一般情況下推薦使用淺拷貝。如果需要深拷貝伦糯,則考慮新增一個(gè)專(zhuān)門(mén)進(jìn)行深拷貝的方法柜某。
四. GCD相關(guān)技巧
1. 多用派發(fā)隊(duì)列,少用同步鎖
有多個(gè)線程要執(zhí)行同一份代碼時(shí)敛纲,為防止出錯(cuò)喂击,通常要使用鎖來(lái)實(shí)現(xiàn)某種同步機(jī)制。
第一種是采用內(nèi)部的同步塊淤翔。
- (void)synchronizedMethod{
@synchronized(self){
//....(safe)
}
}
該實(shí)例中同步行為針對(duì)的對(duì)象使self翰绊。雖然可以保證每個(gè)對(duì)象實(shí)例都能不收干擾得運(yùn)行synchronizedMethod方法,但是濫用@synchronized(self)會(huì)降低代碼效率旁壮。因?yàn)楣灿靡粋€(gè)同步鎖的同步塊监嗜,都必須按順序執(zhí)行。若是在self對(duì)象頻繁加鎖抡谐,那么程序可能要等另一段與此無(wú)關(guān)的代碼執(zhí)行完畢裁奇,才能繼續(xù)執(zhí)行當(dāng)前代碼。
另一種是直接使用NSLock對(duì)象麦撵。
_lock = [NSLock alloc] init];
- (void)synchronizedMethod{
[lock lock];
//....(safe)
[lock unlock];
}
這兩種方法都很好刽肠,不過(guò)也有缺陷。同步塊會(huì)導(dǎo)致死鎖免胃,效率也不高音五。直接用鎖對(duì)象的話,遇到死鎖會(huì)很麻煩羔沙。所以可以使用GCD加鎖躺涝,更加簡(jiǎn)單、高效扼雏。
- 使用串行隊(duì)列:
_syncQueue = dispatch_queue_create("com.text.www", DISPATCH_QUEUE_SERIAL);
//設(shè)置可以不用同步,所以把同步派發(fā)改成異步派發(fā)坚嗜。但是異步派發(fā)需要拷貝代碼塊,所以在執(zhí)行代碼塊的任務(wù)比較繁重時(shí)才考慮這樣子做
- (void)setSomething:(NSString *)something{
dispatch_async(_syncQueue, ^{
_something = something;
});
}
- (NSString *)something{
__block NSString *temp;
dispatch_sync(_syncQueue, ^{
temp = _something;
});
return temp
}
- 使用并行隊(duì)列
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (void)setSomething:(NSString *)something{
dispatch_barrier_async(_syncQueue, ^{
_something = something;
});
}
- (NSString *)something{
__block NSString *temp;
dispatch_sync(_syncQueue, ^{
temp = _something;
});
return temp
}
將同步和異步派發(fā)結(jié)合起來(lái)呢蛤,可以實(shí)現(xiàn)和普通加鎖機(jī)制一樣的同步行為惶傻,這樣做不會(huì)阻塞執(zhí)行異步派發(fā)的線程。使用同步隊(duì)列和柵欄塊其障,可以使同步行為更加高效银室。
2. 多用GCD,少用performSelector系列方法
performSelector系列方法在內(nèi)存管理方法容易有疏失励翼。它無(wú)法確定將要執(zhí)行的選擇子具體是什么蜈敢,因此ARC編譯器無(wú)法適當(dāng)?shù)夭迦雰?nèi)存管理方法。
performSelector系列方法所能處理的選擇子(方法)太過(guò)局限汽抚,選擇子的返回值類(lèi)型(只能是id抓狭,即對(duì)象)及發(fā)送給方法的參數(shù)個(gè)數(shù)(2個(gè))都受到限制。
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2
所以應(yīng)該要用對(duì)應(yīng)的GCD方法來(lái)替代造烁。
3. 掌握GCD(派發(fā)隊(duì)列)和操作隊(duì)列的使用時(shí)機(jī)
GCD是純C的API否过,操作隊(duì)列(NSOperation和NSOperationQueue)是Objective-C的對(duì)象午笛。使用操作隊(duì)列的優(yōu)點(diǎn)有:
- 取消某個(gè)操作。(GCD添加到隊(duì)列中不能取消了苗桂,NSOperation在運(yùn)行任務(wù)前可以cancel药磺,不過(guò)已經(jīng)啟動(dòng)的任務(wù)也取消不了。)
- 指定操作間的依賴(lài)關(guān)系煤伟。
- 通過(guò)鍵值觀察監(jiān)控NSOperation的屬性癌佩,用比GCD更精細(xì)的方式來(lái)監(jiān)聽(tīng)到任務(wù)狀態(tài)的改變。(比如監(jiān)聽(tīng)isCancelled或isFinished)
- 指定操作的優(yōu)先級(jí)便锨。(GCD的優(yōu)先級(jí)是針對(duì)隊(duì)列來(lái)說(shuō)的围辙。NSOperation的線程優(yōu)先級(jí)決定了運(yùn)行此操作的線程處于何種優(yōu)先級(jí)上。)
- 重用NSOperation對(duì)象放案。
操作隊(duì)列有很多地方勝過(guò)派發(fā)隊(duì)列姚建,提供了多種執(zhí)行任務(wù)的方法。但是具體選擇還要看運(yùn)用場(chǎng)景卿叽。
4.不要使用dispatch_get_current_queue
//創(chuàng)建A桥胞、B兩個(gè)串行隊(duì)列
dispatch_queue_t queueA = dispatch_queue_create("com.test.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.test.queueB", NULL);
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_sync(queueA, ^{
//deadLock
});
});
});
這段代碼執(zhí)行到最內(nèi)層的派發(fā)操作時(shí),總會(huì)死鎖考婴。最里面的任務(wù)(最里層的dispatch_sync)是加到A的隊(duì)列后面贩虾,所以必須最外層的dispatch_sync執(zhí)行完,而最外層的dispatch_sync又必須等里面所有的任務(wù)執(zhí)行完(包括最里層的dispatch_sync)沥阱。
為了怕重入(從原來(lái)的串行隊(duì)列又派發(fā)任務(wù))缎罢,可能想使用dispatch_get_current_queue判斷是不是之前的隊(duì)列來(lái)解決,代碼如下考杉。
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_block_t block = ^{ /*任務(wù)*/ };
if (dispatch_get_current_queue() == queueA) {
block();
}else{
dispatch_sync(queueA, block);
}
});
});
實(shí)際上策精,這樣也不能避免。因?yàn)閐ispatch_get_current_queue返回的是queueB而不是queueA崇棠。
隊(duì)列之間會(huì)形成一套層級(jí)體系咽袜,意味著排在某個(gè)隊(duì)列的塊,會(huì)在其上級(jí)隊(duì)列中執(zhí)行枕稀。層級(jí)里地位最高的總是那個(gè)全局并發(fā)隊(duì)列询刹。dispatch_get_current_queue獲取的是它當(dāng)前的隊(duì)列。
要解決這個(gè)問(wèn)題萎坷,最好的方法就是使用dispatch_get_current_queue給隊(duì)列設(shè)置標(biāo)識(shí)凹联,然后判斷是否是原隊(duì)列。
//創(chuàng)建A哆档、B兩個(gè)串行隊(duì)列
dispatch_queue_t queueA = dispatch_queue_create("com.test.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.test.queueB", NULL);
dispatch_set_target_queue(queueB, queueA);
static int specificKey;
CFStringRef specificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA, &specificValue, &specificKey, (dispatch_function_t)CFRelease);
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_block_t block = ^{ /*任務(wù)*/ };
//通過(guò)key獲取標(biāo)識(shí)符
CFStringRef retrievedValue = dispatch_get_specific(&specificKey);
if (retrievedValue) {
block();
}else{
dispatch_sync(queueA, block);
}
});
});