第一條:了解Objective-C語言的起源
Objective-C語言是一門面向?qū)ο蟮恼Z言滥壕,與C++而账、Java等有所相似性雄。然而在語法上娄周,則有許多差別涕侈。這個(gè)差別產(chǎn)生的原因在于,Objective-C是由Smalltalk演化而來煤辨,直接借用了或者說繼承了很多后者的思想裳涛,使用“消息結(jié)構(gòu)”(messaging stricture)而非“函數(shù)調(diào)用”(function calling)。這個(gè)特性使得Objective-C語言众辨,在運(yùn)行時(shí)才回去查找所要執(zhí)行的方法端三。實(shí)際上,編譯器甚至都不關(guān)心接受消息的對(duì)象是何種類型泻轰。
Smalltalk之父Alan Kay曾經(jīng)說過
I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging” – that is what the kernel of Smalltalk/Squeak is all about.
對(duì)象和類只是消息的載體技肩。面向?qū)ο笏枷雽⒊绦虬垂δ芎瓦壿嫹殖闪巳舾蓚€(gè)類,每個(gè)類包含自己的代碼浮声、功能實(shí)現(xiàn)并提供對(duì)外接口虚婿,以黑箱模式運(yùn)行,使得外部無需了解內(nèi)部而協(xié)同使用泳挥,分解了復(fù)雜度并控制在一個(gè)個(gè)較小規(guī)模中然痊,以消息作為其間所有的協(xié)作方式。消息才是Objective-C的核心屉符。
Objective-C語言是C的“超集”(superset)剧浸,亦或者說“Objective-C == C + runtime”。
所有C語言中的功能在編寫Objective-C代碼的時(shí)候依然適用矗钟。所以唆香,掌握C語言有助于寫出高效的Objective-C代碼。而同時(shí)由于許多跨平臺(tái)的第三方庫是用C++編寫的吨艇,我們最好也要了解一些C++躬它。
第二條:在類的頭文件中盡量少引入其他頭文件
在Objective-C中,以類名做文件名东涡,分別創(chuàng)建兩個(gè)文件冯吓,頭文件后綴使用.h倘待,實(shí)現(xiàn)文件后綴使用.m。
我們?cè)谙胍褂闷渌募臅r(shí)候组贺,往往并不需要知曉其他文件的全部細(xì)節(jié)凸舵,只需要知道有一個(gè)類名就可以。我們使用@class方法去實(shí)現(xiàn)
使用@class時(shí)失尖,若需要引入多個(gè)啊奄,標(biāo)準(zhǔn)寫法為@class classA,ClassB雹仿,ClassC
這種方法叫做“前向聲明”增热。
在實(shí)現(xiàn)文件中,需要實(shí)現(xiàn)的時(shí)候胧辽,就可以使用#import ClassA寫法峻仇。
這樣使用的原因在于:減少了很多根本用不到的內(nèi)容,減少編譯時(shí)間邑商,提升性能摄咆;同時(shí)也可以避免兩個(gè)類互相引用的問題,避免“循環(huán)引用”人断。
假如在某些情況下吭从,必須要在.h文件中使用#import時(shí),比如要聲明某個(gè)類遵循一項(xiàng)協(xié)議恶迈,這種情況下涩金,盡量吧“該類遵循某個(gè)協(xié)議”這條聲明移到“class-continuation分類”中。若是還不行暇仲,就把協(xié)議單獨(dú)放到一個(gè)頭文件中步做,然后將其引入。
第三條:多用字面量語法奈附,少用與之等價(jià)的方法
字面數(shù)值
將整數(shù)全度、浮點(diǎn)數(shù)、布爾值放入Objective-C對(duì)象中斥滤,我們一般需要NSNumber将鸵,有兩種創(chuàng)建方法
NSNumber *someNumber = [NSNumber numberWithInt:1];
NSNumber *someNumber = @1;
字面量數(shù)組
NSArray *animals = [NSArray arraywithObjects:@"cat",@"dog",@"mouse",@"badger",nil];
NSArray *animals = @[@"cat",@"dog",@"mouse",@"badger",nil];
不過,我們使用這種方法的時(shí)候佑颇,數(shù)組元素不能為nil顶掉,因?yàn)閯?chuàng)建數(shù)組的時(shí)候,到nil為止挑胸。使用第一種方法創(chuàng)建的數(shù)組一喘,可能會(huì)缺少元素,而使用第二種即字面量方法創(chuàng)建的數(shù)組嗜暴,會(huì)直接拋出異常凸克。這也表明了使用字面量會(huì)更加安全,拋出異常令程序終止執(zhí)行闷沥,比創(chuàng)建好一個(gè)數(shù)組后發(fā)現(xiàn)少了要好萎战。我們可以通過異常直接發(fā)現(xiàn)這個(gè)錯(cuò)誤。
字面量字典
NSDictionary *personData = [NSDictionary dictionaryWithObjectsAndKey:@"Matt",@"firstName",@"galloway",@"lastName",[NSNumber numberWithInt:28],@"age",nil];
NSDictionary *personData =
@{@"firstName":@"Matt",
@"lastName":@"Galloway"
@"age":@28};
這里的優(yōu)勢顯而易見舆逃,鍵出現(xiàn)在對(duì)象之前蚂维,更加好理解。
局限性
除了字符串意外路狮,所有創(chuàng)建出來的對(duì)象虫啥,必須屬于Foundation框架才行。并且奄妨,創(chuàng)建出來的字符串涂籽、數(shù)組、字典對(duì)象都是不可變的砸抛。假如想使用可變版本的评雌,需要mutableCopy一份。
第四條:多用類型常量直焙,少用#define預(yù)處理指令
有時(shí)候景东,我們需要使用某個(gè)常量的時(shí)候,會(huì)這么使用
#define ANIMATION_DURATION 0.3
但是使用這個(gè)方法有時(shí)候會(huì)遇到一個(gè)問題:它會(huì)將所有遇到的ANIMATION_DURATION都替換成0.3.
為了避免這種情況
static const NSTimeInterval kANimationDuration = 0.3;
此外奔誓,我們還要注意常量的名稱斤吐,常用的做法是,若常量局限于某“實(shí)現(xiàn)文件”內(nèi)厨喂,則在前面加k和措;若是常量在類之外課件,則通常以類名為前綴杯聚。
常量一定要同時(shí)使用static與const來聲明臼婆。
假如要使得外界可見,一般我們會(huì)將其放入一個(gè)“全局符號(hào)表”中幌绍。
.h
extern NSString *const EOCStringConstant;
.m
NSString *const EOCStringConstant = @"VALUE";
extern關(guān)鍵字會(huì)告訴編譯器颁褂,在全局符號(hào)表中將會(huì)有一個(gè)名叫EOCStringConstant的符號(hào),也就是說傀广,編譯器無需查看其定義颁独,即允許代碼使用此常量。因?yàn)樗牢北?dāng)鏈接成二進(jìn)制文件之后誓酒,肯定能找到這個(gè)常量。
此類常量必須要定義,且只能定義一次靠柑。通常將其定義的與聲明該常量的頭文件相關(guān)的實(shí)現(xiàn)文件中寨辩。由實(shí)現(xiàn)文件生成目標(biāo)文件時(shí),編譯器會(huì)在“數(shù)據(jù)段”為字符串分類存儲(chǔ)空間歼冰。鏈接器會(huì)把此目標(biāo)文件與其他目標(biāo)文件相鏈接靡狞,以生成最終的二進(jìn)制文件,凡是用到EOCStringConstant這個(gè)全局符號(hào)的地方隔嫡,鏈接器都能將其解析甸怕。
這樣要優(yōu)于使用#define預(yù)處理指令,因?yàn)榫幾g器會(huì)確保常量值不變腮恩。一旦在一處文件中定義好梢杭,即可隨處使用;而采用預(yù)處理指令所定義的常量可能會(huì)無意中遭人修改秸滴,從而導(dǎo)致應(yīng)用程序各個(gè)部分所使用的值互不相同武契。
第5條:用枚舉表示狀態(tài)、選項(xiàng)缸榛、狀態(tài)碼
應(yīng)用枚舉來表示狀態(tài)機(jī)的狀態(tài)吝羞、傳遞給方法的選項(xiàng)及狀態(tài)碼等值,給這些值起個(gè)易懂的名字内颗。
如果把傳遞給某個(gè)方法的選項(xiàng)表示為枚舉類型钧排,而多個(gè)選項(xiàng)又可同時(shí)使用,那么就將各選項(xiàng)的值定義為2的冪均澳,以便通過按位或操作將其組合起來恨溜。
在處理枚類型的switch語句中不要出現(xiàn)default分支。這樣的話找前,加入新枚舉之后糟袁,編譯器就會(huì)提示開發(fā)者:switch語句并未處理所有枚舉。
我們知道躺盛,在Objective-C中项戴,“對(duì)象”(object)就是最基本的構(gòu)造單元,開發(fā)者通過對(duì)象來存儲(chǔ)和傳遞數(shù)據(jù)槽惫。在對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做“消息傳遞”(Messaging)周叮。
第6條:理解“屬性”這一概念
“屬性”(property)是Objective-C的一個(gè)特征,用于封裝對(duì)象中的數(shù)據(jù)界斜。Objective-C對(duì)象通常會(huì)把其所需要的數(shù)據(jù)保存為各種實(shí)例變量仿耽。實(shí)例變量一般通過“存取方法”來訪問。其中各薇,“獲取方法”(getter)用于讀取變量值项贺,而“設(shè)置方法”(setter)用于寫入變量值。
Objective-C會(huì)把實(shí)例變量當(dāng)做一種存儲(chǔ)偏移量的特殊變量,交給“類對(duì)象”保管开缎。偏移量會(huì)在運(yùn)行期查找棕叫,如果類的定義變了,那么存儲(chǔ)的偏移量也就變了啥箭。這樣的話谍珊,無論何時(shí)訪問實(shí)例變量,總能找到正確的偏移量急侥。甚至可以在運(yùn)行期向類中新增實(shí)例變量。
當(dāng)然侮邀,我們還可以使用屬性的方法去解決這個(gè)問題坏怪,也就是不不要直接訪問實(shí)例變量,而通過存取方法來做绊茧。
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
與下面等價(jià)
@interface EOCPerson : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
使用“點(diǎn)語法”的效果與直接調(diào)用存取方法相同铝宵。
EOCPerson *aPerson = [Person new];
aPerson.firstName = @"Bob";//Same as
[aPerson setFirstName:@"Bob"];
NSString *lastName = aPerson.lastName;//same as
NSString *lastName = [aPerson lastName];
屬性特質(zhì)
屬性可以分為四類:
原子性
使用同步鎖,為nonatomic(默認(rèn))华畏,不適用則為atomic
iOS程序中鹏秋,絕大部分都是nonatomic,atomic會(huì)帶來非常大的性能問題亡笑。而且往往也達(dá)不到“線程安全”侣夷,如果要實(shí)現(xiàn),還需要更加深層的鎖定機(jī)制仑乌。
讀寫權(quán)限
具備readwrite特質(zhì)的屬性擁有setter和getter方法百拓。編譯器會(huì)自動(dòng)生成
具備readonly特質(zhì)的屬性僅擁有獲取方法。你可以利用此特質(zhì)晰甚,把某個(gè)屬性對(duì)外公開為只讀屬性衙传,然后在“class-continuation分類”中將其重新定義為讀寫屬性。
內(nèi)存管理語義
assign(默認(rèn)):assign用于值類型厕九,如int蓖捶、float、double和NSInteger扁远,CGFloat等表示單純的復(fù)制俊鱼。還包括不存在所有權(quán)關(guān)系的對(duì)象,比如常見的delegate穿香。
strong:strong是在IOS引入ARC的時(shí)候引入的關(guān)鍵字亭引,是retain的一個(gè)可選的替代。表示實(shí)例變量對(duì)傳入的對(duì)象要有所有權(quán)關(guān)系皮获,即強(qiáng)引用焙蚓。strong跟retain的意思相同并產(chǎn)生相同的代碼,但是語意上更好更能體現(xiàn)對(duì)象的關(guān)系。
weak:在setter方法中购公,需要對(duì)傳入的對(duì)象不進(jìn)行引用計(jì)數(shù)加1的操作萌京。
unsafe_unretailed 此特質(zhì)的語義和assign相同,但適用于“對(duì)象類型”宏浩,該特質(zhì)表達(dá)一種“非擁有關(guān)系”知残,當(dāng)對(duì)象遭到摧毀時(shí),屬性值不會(huì)自動(dòng)清空比庄,這一點(diǎn)與weak有區(qū)別求妹。
copy:與strong類似,但區(qū)別在于實(shí)例變量是對(duì)傳入對(duì)象的副本擁有所有權(quán)佳窑,而非對(duì)象本身制恍。
方法名
getter=<name>指定“獲取方法“的方法名。
setter=<name>指定“設(shè)置方法“的方法名神凑。
第7條:在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量
由于不經(jīng)過Objective-C的“方法派發(fā)”步驟净神,所以直接訪問實(shí)例變量的速度回比較快。編譯器所生成的代碼會(huì)直接訪問對(duì)象實(shí)例變量的那塊內(nèi)存溉委;直接訪問實(shí)例變量時(shí)鹃唯,不會(huì)調(diào)用其“設(shè)置方法”,這就繞過了為相關(guān)屬性所定義的“內(nèi)存管理語義”瓣喊;直接訪問實(shí)例變量坡慌,不會(huì)觸發(fā)“鍵值觀測(KVO)”通知;通過屬性來訪問會(huì)有助于排查與之相關(guān)的錯(cuò)誤型宝,因?yàn)榭梢栽凇矮@取方法”和“設(shè)置方法”中新增斷點(diǎn)八匠,監(jiān)控該屬性的調(diào)用者及其訪問時(shí)機(jī)。
在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí)趴酣,應(yīng)該直接通過實(shí)例變量來讀梨树,而寫入數(shù)據(jù)時(shí),則應(yīng)使用屬性來寫岖寞。
在初始化方法和dealloc方法中抡四,總是應(yīng)該使用實(shí)例變量(self.xxx)來讀寫數(shù)據(jù)。
有時(shí)會(huì)使用懶加載技術(shù)配置某份數(shù)據(jù)仗谆,這種情況下指巡,需要使用屬性來讀取數(shù)據(jù)。
第8條:理解“對(duì)象等同性”這一概念
按照==操作符比較出來的結(jié)果未必是我們想要的隶垮,因?yàn)樵摬僮鞅容^的是兩個(gè)指針本身藻雪,而不是其所指的對(duì)象。應(yīng)該使用NSObject協(xié)議中聲明的“isEqual”方法狸吞,來判斷兩個(gè)對(duì)象的等同性勉耀。
在NSString中有相應(yīng)的比較方法指煎,叫“isEqualToString:”。該方法比“isEqual”要快便斥,后者還要執(zhí)行額外的步驟至壤,以判斷對(duì)象的類型。
NSObject協(xié)議中有兩個(gè)用于判斷等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
NSObject類對(duì)這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)是:當(dāng)且僅當(dāng)其“內(nèi)存地址”完全相等時(shí)枢纠,兩個(gè)對(duì)象才相等像街。如果“isEqual”方法判定兩個(gè)對(duì)象相等,那么其hash方法必須返回同一個(gè)值晋渺,但是镰绎,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值,那么“isEqual”方法未必會(huì)認(rèn)為兩者相等些举。
在編寫hash判斷方法時(shí)跟狱,要格外的注意性能問題。因?yàn)閏ollection在檢索hash table的時(shí)候户魏,會(huì)用對(duì)象的哈希碼做索引。江入某個(gè)collection使用set實(shí)現(xiàn)的挪挤,那么set可能會(huì)根據(jù)哈希碼把對(duì)象分裝到不同的數(shù)組中叼丑。在向set中添加新對(duì)象時(shí),要根據(jù)其哈希碼找到與之相關(guān)的那個(gè)數(shù)組扛门,依次檢查檢查其中各個(gè)元素鸠信,看數(shù)組中以后的對(duì)象時(shí)候和將要添加的新對(duì)象相等。如果相等论寨,那就說明要添加的對(duì)象已經(jīng)在set里面了星立。由此可知,如果令每個(gè)對(duì)象都返回相同的哈希碼葬凳,那么在set中已有100000個(gè)對(duì)象的情況下绰垂,若是繼續(xù)向其中添加對(duì)象,則需將這1000000個(gè)對(duì)象全部掃描一遍火焰。
若是計(jì)算哈希碼劲装,可以使用如下方法
- (NSUIntger)hash {
NSUIntger firstNameHash = [_firstName hash];
NSUIntger lastNameHash = [_lastName hash];
NSUIntger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
這種方法既能保持較高效率,又能使生成的哈希碼至少位于一定范圍內(nèi)昌简,而不會(huì)過于頻繁的重復(fù)占业。
當(dāng)然,我們也可以自己實(shí)現(xiàn)等同性判斷方法纯赎,在寫這樣的方法時(shí)谦疾,也應(yīng)一并覆寫“isEqual”方法。
- (BOOL)isEqualToPerson:(EOCPerson *)otherPerson {
if(self == object ) return YES;
if(![_first那么isEqualToString:otherPerson.firstName]){
return NO;
}
return YES;
}
- (BOOL)isEqual:(id)object {
if([self class] == [object class]) {
return [self isEqualToPerson:(EOCPerson *)object];
}else{
return [super isEqual:object];
}
}
不要盲目的逐個(gè)監(jiān)測每條屬性犬金,而是應(yīng)該依照具體需求來制定檢測方案念恍。比如判斷數(shù)組六剥,我們就可以先判斷數(shù)組的所含對(duì)象的數(shù)量是否相等。
如果把對(duì)象放入collection之后改變其內(nèi)容樊诺,可能會(huì)造成嚴(yán)重的后果仗考,要小心對(duì)待
第9條:以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
“類族”是一種很有用的模式,可以隱藏“抽象基類”背后的實(shí)現(xiàn)細(xì)節(jié)词爬⊥菏龋“工廠模式”是創(chuàng)建類族的辦法之一。
如果對(duì)象所屬的類位于某個(gè)類族中顿膨,那么在查詢其類型信息時(shí)就要當(dāng)心了世澜。你可能覺得創(chuàng)建了某個(gè)類的實(shí)現(xiàn),然而實(shí)際上穿件的卻是其子類的實(shí)例板惑。
在系統(tǒng)框架中枫攀,很多地方都使用了類族模式。比如NSArray囊咏。
我們?cè)诮oCocoa中的類族添加子類的時(shí)候恕洲,要注意一下幾點(diǎn):
- 子類應(yīng)該繼承自類族中的抽象基類
- 子類應(yīng)該定義自己的數(shù)據(jù)存儲(chǔ)方式
- 子類應(yīng)該復(fù)寫超類文檔中指明需要覆寫的方法。
第10條:在既有類中梅割,使用關(guān)聯(lián)對(duì)象(Associated Object)存放自定義數(shù)據(jù)
有時(shí)需要在對(duì)象中存放相關(guān)信息霜第,這是我們通常對(duì)從對(duì)象所屬的類中繼承一個(gè)子類,然后改用這個(gè)子類對(duì)象户辞。然而有時(shí)候我們無法這么做泌类,這時(shí)候就要使用關(guān)聯(lián)對(duì)象了。
關(guān)聯(lián)類型 | 等效的@property屬性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic,copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
下列方法可以管理關(guān)聯(lián)對(duì)象:
- void objc_setAssociatedObject(id object,void *key,id value,objc_AssociationPolicy policy)
此方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值- id objc_getAssociatedObject(id object,void *key)
此方法根據(jù)給定的鍵和策略為某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值- void objc_removeAssicuatedObjects(id object)
此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象
在設(shè)置關(guān)聯(lián)對(duì)象值時(shí)底燎,通常使用靜態(tài)全局變量做鍵
只有在其他方法完全不可行的時(shí)候才選用關(guān)聯(lián)對(duì)象方法刃榨,因?yàn)檫@種做法通常會(huì)引入難于查找的bug。
第11條:理解objc_msgSend的作用
在C語言當(dāng)中双仍,C語言使用“靜態(tài)綁定”枢希,也就是說,在編譯期就能決定運(yùn)行時(shí)所應(yīng)調(diào)用的函數(shù)殊校。
而Objective-C使用“動(dòng)態(tài)綁定”晴玖,在底層,所有方法都是普通的C語言函數(shù)为流,然而對(duì)象收到消息后呕屎,究竟該調(diào)用那個(gè)方法則完全于運(yùn)行期決定,甚至可以在程序運(yùn)行時(shí)改變敬察。
給對(duì)象發(fā)送消息可以這樣來寫:
id returnValue = [someObject messageName:parameter];
在這里秀睛,someObject叫做“接收者”,messageName叫做“選擇子”莲祸,選擇子與參數(shù)合起來稱之為“消息”蹂安。編譯器看到此消息后椭迎,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù)田盈,叫做objc_msgSend,其原型如下:
void objc_msgSend(id self,SEL cmd,...)
編譯器會(huì)把它轉(zhuǎn)換成
id returnValue = objc_msgSend(someObject,
@selector(messageName:),
parameter);
為了完成此操作畜号,該方法需要在接受者所屬的類中搜尋其“方法列表”如果能找到與選擇子名稱相符的防范,就跳轉(zhuǎn)至其代碼允瞧。若是找不到简软,那就沿著繼承體系繼續(xù)向上查找,等找到合適的方法之后再跳轉(zhuǎn)述暂。如果最終還是找不到相符的方法痹升,那就執(zhí)行“消息轉(zhuǎn)發(fā)”操作。
objc_msgSend會(huì)將匹配結(jié)果緩存在“快速映射表”里面畦韭,每個(gè)類都有這一塊緩存疼蛾,若是稍后還想該類發(fā)送與選擇子相同的消息,那么執(zhí)行起來就會(huì)很快了艺配。
除了以上的方法察郁,還有一些其他的函數(shù):
- objc_msgSend_stret 如果待發(fā)送的消息要返回結(jié)構(gòu)體,就由它處理转唉。
- objc_msgSend_fpret 如果待發(fā)送的消息要返回浮點(diǎn)數(shù)绳锅,就由它處理。
- objc_msgSendSuper 如果要給超類發(fā)消息酝掩,例如[super message:parameter],那么久交由此處理。
在每個(gè)類中都有一張表格眷柔,其中的指針都會(huì)指向這種函數(shù)期虾,而選擇子的名稱則是查表所用的“鍵”。
這里為了優(yōu)化驯嘱,使用了尾調(diào)用優(yōu)化镶苞。
如果某函數(shù)的最后一項(xiàng)操作是調(diào)用另外一個(gè)函數(shù),那么就會(huì)調(diào)用這個(gè)方法鞠评。編譯器會(huì)生成調(diào)轉(zhuǎn)至另一函數(shù)所需的指令碼茂蚓,而且不會(huì)向調(diào)用堆棧中推入新的“棧幀”。這么做法非常關(guān)鍵剃幌,如果不這么做的話聋涨,會(huì)過早的發(fā)生棧溢出。
第12條:理解消息轉(zhuǎn)發(fā)機(jī)制
在編譯期間向類發(fā)送了其無法解讀的消息并不會(huì)報(bào)錯(cuò)负乡,因?yàn)樵谶\(yùn)行期可以繼續(xù)向類中添加方法牍白,所以編譯器會(huì)在編譯時(shí)還無法確定類中到底會(huì)不會(huì)有某個(gè)方法實(shí)現(xiàn)。當(dāng)對(duì)象收到了無法解讀的消息時(shí)抖棘,就會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制茂腥。
消息轉(zhuǎn)發(fā)分為兩大階段狸涌。第一階段先征詢接受者,所屬的類最岗,看其是否能動(dòng)態(tài)添加方法帕胆,以處理這個(gè)未知的選擇子,這叫做動(dòng)態(tài)方法解析般渡。第二階段涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”懒豹。如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接受者自己就無法再以動(dòng)態(tài)新增方法的手段來響應(yīng)包含該選擇子的消息了诊杆。此時(shí)歼捐,運(yùn)行期系統(tǒng)會(huì)請(qǐng)求接受者以其他手段來處理與消息相關(guān)的方法調(diào)用。這里又分兩步晨汹,首先豹储,請(qǐng)接受者看看有沒有其他對(duì)象能處理這條消息。若有淘这,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象剥扣。若沒有備用的接受者,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制铝穷,運(yùn)行期系統(tǒng)會(huì)把與消息相關(guān)的全部細(xì)節(jié)都封裝到NSInvocation當(dāng)中钠怯,再給接受者最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息曙聂。
第13條:用method swizzling調(diào)試黑盒方法
我們?cè)诮o定的選擇子名稱相對(duì)的方法在運(yùn)行期改變晦炊,這種方法叫“方法調(diào)配”(method swizzling)。
類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)上宁脊,是的“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠據(jù)此找到應(yīng)該調(diào)用的方法断国。這些方法以函數(shù)指針的方法表示,這種指針叫做IMP榆苞,原型如下:
id (*IMP)(id,SEL,...)
我們要實(shí)現(xiàn)方法互換稳衬,需要以下方法
//交換方法實(shí)現(xiàn)
void method_exchangeImplementations(Method m1,Method m2)
//取出對(duì)應(yīng)方法
Method class_getInstanceMethod(Class aClass,SEL aSelector)
看起來沒有什么用處,但是結(jié)合添加方法和category坐漏,就可以達(dá)到讓人意想不到的效果薄疚。
在category中添加一個(gè)方法,與原本的方法互換赊琳,就會(huì)達(dá)到調(diào)用的效果街夭。
這里實(shí)現(xiàn)埋點(diǎn),日志意義非常大慨畸。
一般來說莱坎,只有調(diào)試程序的時(shí)候才需要在runtime中修改方法實(shí)現(xiàn),這種做法不宜濫用寸士。
第14條:理解"類對(duì)象"的用意
描述Objective-C對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)定義在運(yùn)行期程序庫的頭文件里檐什,id類型本身也在定義這里:
typedef struct objc_object {
Class isa
}*id;
由此可見碴卧,每個(gè)對(duì)象結(jié)構(gòu)體的首個(gè)成員是Class的變量。該變量定義了對(duì)象所屬的類乃正,通常稱為"isa"指針住册。
Class對(duì)象也定義在運(yùn)行期程序庫的頭文件中:
typedef struct objc_class *Class
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
} ;
在類繼承中查詢類的信息:
isMemberOfClass能夠判斷出對(duì)象是否為某個(gè)特定類的實(shí)例,而isKindOfClass則能判斷出對(duì)象是否為某類或其派生類的實(shí)例瓮具。
每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針荧飞,用以表明其類型,而這些Class對(duì)象則構(gòu)成了累的繼承體系名党。
如果對(duì)象類型無法在編譯期確定叹阔,那么就應(yīng)該使用類型信息查詢方法來探知。
盡量使用類型信息查詢方法來確定對(duì)象類型传睹,而不要直接比較類對(duì)象耳幢,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能。
第15條:用前綴避免命名空間沖突
Apple宣稱其保留使用所有"兩字母前綴"的權(quán)利欧啤,所以我們選用的前綴應(yīng)該是三個(gè)字母的睛藻。 而且,如果自己開發(fā)的程序使用到了第三方庫邢隧,也應(yīng)該加上前綴店印。
第16條:提供“全能初始化方法”
有時(shí),由于要實(shí)現(xiàn)各種設(shè)計(jì)需求倒慧,一個(gè)類可以有多個(gè)創(chuàng)建實(shí)例的初始化方法按摘。我們應(yīng)該選定其中一個(gè)作為全能初始化方法,令其他初始化方法都來調(diào)用它纫谅。這個(gè)我們就叫做“全能初始化方法”院峡。
只有在全能初始化方法里面才能存儲(chǔ)內(nèi)部數(shù)據(jù)。這樣一來系宜,當(dāng)?shù)讓訑?shù)據(jù)存儲(chǔ)機(jī)制改變時(shí),只需修改此方法的代碼就好发魄,無需改動(dòng)其他初始化方法盹牧。
在我們擁有了一個(gè)全能初始化方法后,最好還是要覆寫init方法來設(shè)置默認(rèn)值励幼。并且汰寓,每個(gè)子類的全能初始化方法都應(yīng)該調(diào)用其超類的對(duì)象方法,并且逐級(jí)向上苹粟,否則可能無法實(shí)現(xiàn)相應(yīng)的方法有滑。
假如我們并不想覆寫超類的全能初始化方法,我們可以在本類中在覆寫超類的全能初始化方法并拋出異常嵌削。
第17條:實(shí)現(xiàn)description方法
在打印我們自己定義的類的實(shí)例對(duì)象時(shí)毛好,在控制臺(tái)輸出的結(jié)果往往是只包含了類名和內(nèi)存地址望艺,它的信息顯然是不具體的,遠(yuǎn)達(dá)不到調(diào)試的要求。
這時(shí)肌访,我們就需要重寫description方法了找默。我們將這些屬性值放在字典里打印,則更具有可讀性:
- (NSString*)description {
return [NSString stringWithFormat:@"<%@: %p, %@>",[self class],self,
@{ @"title":_title,
@"latitude":@(_latitude),
@"longitude":@(_longitude)}
];
}
輸出結(jié)果:
location = <EOCLocation: 0x7f98f2e01d20, {
latitude = "51.506";
longitude = 0;
title = London;
}>
如果你想要在調(diào)試器中實(shí)現(xiàn)類似效果吼驶,就需要重寫debugDescription方法了惩激。可以實(shí)現(xiàn)和description一樣的效果蟹演,并且當(dāng)你不想要在log中出現(xiàn)過分詳細(xì)的內(nèi)容的時(shí)候风钻,完全可以使用debugDescription在調(diào)試器中使用。
第18條:盡量使用不可變對(duì)象
書中作者建議盡量把對(duì)外公布出來的屬性設(shè)置為只讀酒请,在實(shí)現(xiàn)文件內(nèi)部設(shè)為讀寫骡技。具體做法是:
在頭文件中,設(shè)置對(duì)象屬性為readonly蚌父,在實(shí)現(xiàn)文件中設(shè)置為readwrite哮兰。這樣一來,在外部就只能讀取該數(shù)據(jù)苟弛,而不能修改它喝滞,使得這個(gè)類的實(shí)例所持有的數(shù)據(jù)更加安全。
而且膏秫,對(duì)于集合類的對(duì)象右遭,更應(yīng)該仔細(xì)考慮是否可以將其設(shè)為可變的。
如果在公開部分只能設(shè)置其為只讀屬性缤削,那么就在非公開部分存儲(chǔ)一個(gè)可變型窘哈。這樣一來,當(dāng)在外部獲取這個(gè)屬性時(shí)亭敢,獲取的只是內(nèi)部可變型的一個(gè)不可變版本,例如:
在公共API中:
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公開的不可變集合
- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
@end
在這里滚婉,我們將friends屬性設(shè)置為不可變的set。然后帅刀,提供了來增加和刪除這個(gè)set里的元素的公共接口让腹。
在實(shí)現(xiàn)文件里:
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation EOCPerson {
NSMutableSet *_internalFriends; //實(shí)現(xiàn)文件里的可變集合
}
- (NSSet*)friends {
return [_internalFriends copy]; //get方法返回的永遠(yuǎn)是可變set的不可變型
}
- (void)addFriend:(EOCPerson*)person {
[_internalFriends addObject:person]; //在外部增加集合元素的操作
//do something when add element
}
- (void)removeFriend:(EOCPerson*)person {
[_internalFriends removeObject:person]; //在外部移除元素的操作
//do something when remove element
}
- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName {
if ((self = [super init])) {
_firstName = firstName;
_lastName = lastName;
_internalFriends = [NSMutableSet new];
}
return self;
}
我們也要將內(nèi)部可變的set去開放給外邊一個(gè)不可變的set。但是這里會(huì)造成一個(gè)問題扣溺,也就是我們?cè)趦?nèi)部修改set的時(shí)候骇窍,外部可能有觀察者正在使用這個(gè)set。這時(shí)候锥余,我們需要運(yùn)用“派發(fā)隊(duì)列”等手段腹纳,把數(shù)據(jù)的存取操作都設(shè)置為同步操作。
第19條:使用清晰而協(xié)調(diào)的命名方式
類、方法及變量的命名是Objective-C編程的重要環(huán)節(jié)嘲恍。它的語法結(jié)構(gòu)使得代碼讀起來和句子一樣足画,名字中一般都帶有"in"、"for"蛔钙、"with"等介詞锌云。
方法名與變量名要使用“駝峰命名法”(camel casing),以小寫字母開頭吁脱,其后每個(gè)單詞首字母大寫桑涎。類名也用駝峰命名法,不過首字母要大寫兼贡,并且前面通常還有三個(gè)前綴字母攻冷。
作者給我們總結(jié)了幾條注意事項(xiàng):
- 如果方法的返回值是新出創(chuàng)建的,那么方法名的首個(gè)詞應(yīng)該是返回值的類型遍希,除非前面還有修飾詞等曼。
- 應(yīng)該把表示參數(shù)類型的名詞放在參數(shù)前面。
- 如果方法要在當(dāng)前對(duì)象上執(zhí)行操作凿蒜,那么就應(yīng)該包含動(dòng)詞禁谦;若執(zhí)行操作還需要參數(shù),則應(yīng)該在動(dòng)詞后面加上一個(gè)或多個(gè)動(dòng)詞废封。
- 不要使用str這種簡稱州泊,應(yīng)該使用string這樣的全稱。
- Boolean 屬性應(yīng)該加is前綴漂洋。如果返回的非屬性的Boolean值遥皂,那么應(yīng)該根據(jù)其功能,選用has或者is當(dāng)前綴
- 將get這個(gè)前綴留給那些借由“輸出參數(shù)”來保存返回值的方法刽漂。
第20條:為私有方法名加前綴
建議在實(shí)現(xiàn)文件里將非公開的方法都加上前綴演训,便于調(diào)試,而且這樣一來也很容易區(qū)分哪些是公共方法贝咙,哪些是私有方法样悟。因?yàn)橥卜椒ㄊ遣槐阌谌我庑薷牡摹?br> 作者推薦使用字母和_相結(jié)合的方式。比如說在私有方法前加 p
- 不要用下劃線來區(qū)分私有方法和公共方法庭猩,因?yàn)闀?huì)和蘋果公司的API重復(fù)乌奇。
第21條:理解Objective-C錯(cuò)誤類型
首先要注意的是,ARC在默認(rèn)情況下不是“異常安全的”眯娱。也就是說如果拋出異常,那么本應(yīng)該在作用域末尾釋放的對(duì)象現(xiàn)在卻不會(huì)自動(dòng)釋放了爬凑。
現(xiàn)在Objective-C使用的策略是:只有在極其罕見的情況下才會(huì)拋出異常徙缴,異常跑出來之后,無須考慮恢復(fù)問題,而應(yīng)用也會(huì)推出于样。換一句話說:程序崩啦疏叨!
那么問題來了,程序只有在遇到嚴(yán)重錯(cuò)誤的時(shí)候會(huì)崩潰穿剖,在遇到不那么嚴(yán)重的錯(cuò)誤是蚤蔓,該如何處理呢?
會(huì)令方法返回nil/0糊余,或者是使用NSError秀又,來標(biāo)明錯(cuò)誤的發(fā)生。
返回nil/0很簡單贬芥,就是返回值會(huì)是nil或者0吐辙,這樣我們就明白發(fā)生了錯(cuò)誤。
而NSError則更加靈活蘸劈,我們可以通過NSError將導(dǎo)致錯(cuò)誤的原因返回給使用者昏苏。
使用NSError可以封裝三種信息:
Error domain:錯(cuò)誤范圍,類型是字符串
Error code :錯(cuò)誤碼威沫,類型是整數(shù)
User info:用戶信息贤惯,類型是字典
NSError的用法:
1.通過委托協(xié)議來傳遞NSError,告訴代理錯(cuò)誤類型棒掠。
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
2.作為方法的“輸出參數(shù)”返回給調(diào)用者
- (BOOL)doSomething:(NSError**)error
使用范例:
NSError *error = nil;
BOOL ret = [object doSomething:&error];
if (error) {
// There was an error
}
第22條:理解NSCopying協(xié)議
使用對(duì)象時(shí)我們經(jīng)撤豕梗拷貝它。如果我們想令自己的類支持拷貝操作句柠,那就要實(shí)現(xiàn)NSCopying協(xié)議浦译,該協(xié)議只有一個(gè)方法:
- (id)copyWithZone:(NSZone*)zone
NSZone這個(gè)參數(shù)是以前的歷史遺留,現(xiàn)在每個(gè)程序都只有一個(gè)區(qū):默認(rèn)區(qū)溯职,所以精盅,我們可以不必管這個(gè)參數(shù)了。
若想使某個(gè)類支持拷貝功能谜酒,只需聲明該類遵從NSCopying協(xié)議叹俏,并實(shí)現(xiàn)其中的copyWithZone方法就可以。
如果我們想獲得某對(duì)象的不可變型僻族,統(tǒng)一調(diào)用copy方法粘驰;獲得某對(duì)象的可變型,統(tǒng)一調(diào)用mutableCopy方法述么。
例如數(shù)組的拷貝:
-[NSMutableArray copy] => NSArray
-[NSArray mutableCopy] => NSMutableArray
深淺拷貝
Foundation框架中的集合類默認(rèn)都執(zhí)行淺拷貝:只拷貝容器對(duì)象本身蝌数,而不復(fù)制其中的數(shù)據(jù)。 而深拷貝的意思是連同對(duì)象本身和它的底層數(shù)據(jù)都要拷貝度秘。
[圖片上傳失敗...(image-f73c33-1527125798111)]
- (id)initWithSet:(NSArray*)array copyItems:(BOOL)copyItems;
在這里顶伞,我們自己提供了一個(gè)深拷貝的方法:該方法需要傳入兩個(gè)參數(shù):需要拷貝的數(shù)組和是否拷貝元素(是否深拷貝)
- (id)deepCopy {
EOCPerson *copy = [[[self class] alloc] initWithFirstName:_firstName andLastName:_lastName];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES];
return copy;
}
因?yàn)闆]有專門定義深拷貝的協(xié)議,所以其具體執(zhí)行方式由每個(gè)類來決定,你時(shí)許決定自己所寫的類是否要提供深拷貝方法即可唆貌。另外滑潘,不要嘉定遵從了NSCopying協(xié)議的對(duì)象都會(huì)執(zhí)行深拷貝。在絕大多數(shù)情況下锨咙,執(zhí)行的都是淺拷貝语卤。
第23條:通過委托與數(shù)據(jù)源協(xié)議進(jìn)行對(duì)象間通信
定義:定義一套接口,某對(duì)象若想接受另一個(gè)對(duì)象的委托酪刀,則需遵從此接口粹舵,一遍成為其"委托對(duì)象"。而這"另一個(gè)對(duì)象"則可以給其委托對(duì)象回傳一些信息蓖宦,也可以在發(fā)生相關(guān)事件時(shí)通知委托對(duì)象齐婴。
對(duì)于代理模式,在iOS中分為兩種:
delegate:信息從類流向委托者稠茂,從委托對(duì)象中獲取信息柠偶。
datasource:信息從數(shù)據(jù)源流向類,將信息傳遞給類睬关。
- 注意:用屬性定義委托對(duì)象的時(shí)候诱担,需要設(shè)置為weak,而非strong电爹。
使用@optional關(guān)鍵字標(biāo)注方法蔫仙,表示方法未必執(zhí)行。
在使用的時(shí)候丐箩,最好再加上一個(gè)判斷:判斷委托對(duì)象是否存在
NSData *data = /* data obtained from network */;
if ( (_delegate) && ([_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)]))
{
[_delegate networkFetcher:self didReceiveData:data];
}
優(yōu)化
我們需要在使用的時(shí)候檢查委托對(duì)象是否響應(yīng)選擇子摇邦,但是如果頻繁的執(zhí)行操作,除了第一次操作是有用的以外屎勘,后續(xù)的操作都是多余的施籍。所以,我們可以把委托對(duì)象能否響應(yīng)某個(gè)協(xié)議方法這一信息緩存起來概漱,以便優(yōu)化程序效率丑慎。
這里我們可以使用"段位"(bitfield)數(shù)據(jù)類型來實(shí)現(xiàn)。把結(jié)構(gòu)體中某個(gè)字段所占用的二進(jìn)制位個(gè)數(shù)設(shè)為特定的值瓤摧。
@interface EOCNetworkFetcher () {
struct {
unsigned int didReceiveData :1
unsigned int didFailWithError :1
unsigned int didUpdateProgressTo :1
}_delegate;
}
@end
這樣我們就可為代理設(shè)置緩存竿裂。
//set flag
_delegate.didReceiveData = 1;
//Check flag
if(_delegateFlags.didReceiceData){
//yes,flag set
}
然后我們實(shí)現(xiàn)緩存的代碼可以寫在delegate的設(shè)置方法中
- (void)setDelegate:(id<EOCNetworkFetcher>)delegate {
_delegate = deleagte;
_delegateFlags.didReceiveData = [delegate respondsToSelect:@selector(networkFetcher:didReceiveData:)];
}
這樣的話我們每次調(diào)用delegate的方法前,就不用檢測委托對(duì)象能否響應(yīng)給指定的選擇子了照弥,二十直接查詢結(jié)構(gòu)體的標(biāo)志:
if(_delegateFlags.didUpdateProgressTo){
[_delegate networkFetcher:self didUpdateProgressTo:currentProgress];
}
第24條:將類的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類中
通常一個(gè)類會(huì)有很多方法腻异,而這些方法往往可以用某種特有的邏輯來分組。我們可以利用OC的分類機(jī)制这揣,將類的這些方法按一定的邏輯劃入幾個(gè)分區(qū)中悔常。
例子:
無分類的類:
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;
- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
/* Friendship methods */
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
/* Work methods */
- (void)performDaysWork;
- (void)takeVacationFromWork;
/* Play methods */
- (void)goToTheCinema;
- (void)goToSportsGame;
@end
分類之后:
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;
- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
@end
@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end
@interface EOCPerson (Work)
- (void)performDaysWork;
- (void)takeVacationFromWork;
@end
@interface EOCPerson (Play)
- (void)goToTheCinema;
- (void)goToSportsGame;
@end
其中敢会,F(xiàn)riendShip分類的實(shí)現(xiàn)代碼可以這么寫:
// EOCPerson+Friendship.h
#import "EOCPerson.h"
@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end
// EOCPerson+Friendship.m
#import "EOCPerson+Friendship.h"
@implementation EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person {
/* ... */
}
- (void)removeFriend:(EOCPerson*)person {
/* ... */
}
- (BOOL)isFriendsWith:(EOCPerson*)person {
/* ... */
}
@end
注意:在新建分類文件時(shí),一定要引入被分類的類文件这嚣。
通過分類機(jī)制,可以把類代碼分成很多個(gè)易于管理的功能區(qū)塞俱,同時(shí)也便于調(diào)試姐帚。因?yàn)榉诸惖姆椒Q會(huì)包含分類的名稱,可以馬上看到該方法屬于哪個(gè)分類中障涯。
利用這一點(diǎn)罐旗,我們可以創(chuàng)建名為Private的分類,將所有私有方法都放在該類里唯蝶。這樣一來九秀,我們就可以根據(jù)private一詞的出現(xiàn)位置來判斷調(diào)用的合理性,這也是一種編寫“自我描述式代碼(self-documenting)”的辦法粘我。
第25條:總是為第三方類的分類名稱加前綴
分類機(jī)制通常用于向不知道源碼的既有類中新增功能鼓蜒。但是使用的時(shí)候也容易忽視其中的問題,這個(gè)問題在于:分類中的方法是直接添加在類里面的征字。將分類中的方法假如類中這一操作是在運(yùn)行期系統(tǒng)加載分類時(shí)完成的都弹。運(yùn)行期系統(tǒng)會(huì)把分類中所實(shí)現(xiàn)的每個(gè)方法都加入類的方法列表中。如果類中本來就有此方法匙姜,而分類又實(shí)現(xiàn)了一次畅厢,那么分類中的方法會(huì)覆蓋原來那一份實(shí)現(xiàn)代碼。
我們需要在分類中的方法前加上前綴氮昧,這么做的好處有以下幾點(diǎn):
- 可以避免自己活他人實(shí)現(xiàn)的方法名重復(fù)
- 避免和蘋果自己的api重復(fù)框杜,即使現(xiàn)在沒有孟辑,也有可能在未來的某個(gè)時(shí)間甲喝,蘋果新加了方法
- 避免他們使用自己代碼是算谈,發(fā)生誤會(huì)
第26條:勿在分類中聲明屬性
除了實(shí)現(xiàn)文件里的class-continuation分類中可以聲明屬性外丸氛,其他分類無法向類中新增實(shí)例變量衔憨。
因此适秩,類所封裝的全部數(shù)據(jù)都應(yīng)該定義在主接口中趟济,這里是唯一能夠定義實(shí)例變量的地方稼虎。
關(guān)于分類庐杨,需要強(qiáng)調(diào)一點(diǎn):
分類機(jī)制选调,目標(biāo)在于擴(kuò)展類的功能,而不是封裝數(shù)據(jù)灵份。
第27條:使用class-continuation分類 隱藏實(shí)現(xiàn)細(xì)節(jié)
通常仁堪,我們需要減少在公共接口中向外暴露的部分(包括屬性和方法),而因此帶給我們的局限性可以利用class-continuation分類的特性來補(bǔ)償:
可以在class-continuation分類中增加實(shí)例變量填渠。
可以在class-continuation分類中將公共接口的只讀屬性設(shè)置為讀寫弦聂。
可以在class-continuation分類中遵循協(xié)議鸟辅,使其不為人知。
第28條:通過協(xié)議提供匿名對(duì)象
我們用協(xié)議把自己所寫的API之中的實(shí)現(xiàn)細(xì)節(jié)隱藏起來莺葫,將返回的對(duì)象設(shè)計(jì)為遵從此協(xié)議的純id類型匪凉。這樣,想要隱藏的類名就不會(huì)出現(xiàn)在API當(dāng)中了捺檬。若是接口背后有多個(gè)不同的實(shí)現(xiàn)類再层,而你又不想之名具體使用哪個(gè)類,那么就可以使用這個(gè)方法堡纬。
如果具體的類型不重要聂受,重要的是對(duì)象能夠響應(yīng)(定義在協(xié)議里面)的特定方法,那么可使用匿名對(duì)象來表示烤镐。
第29條:理解引用計(jì)數(shù)
NSObject協(xié)議聲明了以下三個(gè)方法用于操作引用計(jì)數(shù)器蛋济。
retain 遞增保留計(jì)數(shù)
release 遞減保留計(jì)數(shù)
autorelease 自動(dòng)釋放池
對(duì)象創(chuàng)建出來之后,引用計(jì)數(shù)至少為1.我們絕不應(yīng)該說保留計(jì)數(shù)一定是某個(gè)值炮叶,只能說你所執(zhí)行的操作是遞增了還是遞減了該計(jì)數(shù)碗旅。
在設(shè)置屬性的時(shí)候,需要使用實(shí)例變量的getter方法和setter方法悴灵。比如以下代碼
- (void)setFoo:(id)foo {
[foo retain];
[_foo release];
_foo = foo;
}
這里我們先保留新值并釋放了舊值扛芽,然后更新實(shí)例變量,令其指向新值积瞒。這個(gè)順序非常重要川尖,假如還未保留新值就把舊值釋放了,然后兩個(gè)值又指向同一個(gè)對(duì)象茫孔,俺么叮喳,先執(zhí)行的release操作就可能導(dǎo)致系統(tǒng)將此對(duì)象永久回收。而后續(xù)的retain操作則無法令這個(gè)已經(jīng)徹底回收的對(duì)象復(fù)生缰贝,于是實(shí)例變量就成了懸掛指針馍悟。
autorelease
將對(duì)象放入自動(dòng)釋放池之后,不會(huì)馬上使其引用計(jì)數(shù)-1剩晴,而是在當(dāng)前線程的下一次事件循環(huán)時(shí)遞減锣咒。
使用舉例:如果我們想釋放當(dāng)前需要使用的方法返回值是,可以將其暫時(shí)放在自動(dòng)釋放池中:
- (NSString*)stringValue {
NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return [str autorelease];
}
retain cycle
在其他有垃圾回收機(jī)制的語言中赞弥,幾個(gè)對(duì)象互相引用形成保留環(huán)的時(shí)候毅整,垃圾回收器會(huì)把幾個(gè)對(duì)象認(rèn)定為"孤島",將其全部收走绽左。而在Objective-C中悼嫉,往往要采用weak reference即弱引用的方法解決問題。此外拼窥,我們還可以在外界命令循環(huán)中的某個(gè)對(duì)象不再保留另外一個(gè)對(duì)象戏蔑。
第30條:以ARC簡化引用計(jì)數(shù)
在Clang編譯器中帶有一個(gè)"靜態(tài)分析器(sratic analyzer)"蹋凝,用于指明程序里引用計(jì)數(shù)出問題的地方,它可以很簡單的通過引用計(jì)數(shù)的檢查來分析出是否有內(nèi)存泄漏总棵。這個(gè)也是ARC的思路來源鳍寂。
使用ARC的時(shí)候一定要記住,引用計(jì)數(shù)實(shí)際上還是要執(zhí)行的情龄,只不過保留與釋放操作現(xiàn)在是由ARC為你自動(dòng)添加伐割。
在ARC環(huán)境下,會(huì)自動(dòng)執(zhí)行retain刃唤、release、autorelease白群、dealloc方法尚胞,所以我們不能再調(diào)用了。實(shí)際上帜慢,ARC在調(diào)用這些方法的時(shí)候笼裳,并不通過普通的Objective-C消息派發(fā)機(jī)制,二十直接調(diào)用其底層的C語言版本粱玲,這要操作性能會(huì)更好躬柬。
在ARC中,在方法返回自動(dòng)釋放的對(duì)象時(shí)抽减,要執(zhí)行一個(gè)特殊函數(shù)允青。此時(shí)不會(huì)調(diào)用對(duì)象的autorelease方法,而是改用objc_autoreleaseReturnValue方法卵沉。此函數(shù)會(huì)檢視當(dāng)前方法返回之后即將要執(zhí)行的那段代碼颠锉。若發(fā)現(xiàn)那段代碼要在返回的對(duì)象上執(zhí)行retain操作,則設(shè)置全局?jǐn)?shù)據(jù)結(jié)構(gòu)中的一個(gè)標(biāo)志位史汗,而不執(zhí)行autorelease操作琼掠。與之相似,如果方法返回了一個(gè)自動(dòng)釋放的對(duì)象停撞,而調(diào)用方法的代碼要保留此對(duì)象瓷蛙,那么此時(shí)不直接執(zhí)行retain,而是改為執(zhí)行objc_retainAttoreleaseReturnValue函數(shù)戈毒。此函數(shù)要檢測剛才提到的那個(gè)標(biāo)志位艰猬,若已經(jīng)置位,則不執(zhí)行retain操作副硅。設(shè)置并檢測標(biāo)志位姥宝,要比調(diào)用autorelease和retain更快。
在ARC中恐疲,會(huì)采用一種安全的方式來設(shè)置:先保留新值腊满,再釋放舊值套么,最后設(shè)置實(shí)例變量。
ARC如何管理實(shí)例變量
在MRC下碳蛋,我們要這么寫dealloc方法:
-(void)dealloc {
[_foo release];
[_bar release];
[super dealloc];
}
而在ARC中胚泌,我們就不需要再這么寫了。因?yàn)锳RC會(huì)調(diào)用Objective-C++的一項(xiàng)特性來生成清理例程(cleanup routine)肃弟$枋遥回收Objective-C++對(duì)象是時(shí),待回收的對(duì)象會(huì)調(diào)用所有C++的析構(gòu)函數(shù)(destructor)笤受。編譯器如果發(fā)現(xiàn)某個(gè)對(duì)象里含有C++對(duì)象穷缤,就會(huì)生成名為.cxx_destruct的方法。而ARC會(huì)借助此特性箩兽,在該方法中生成清理內(nèi)存所需的代碼津肛。
不過,如果有非Objective-C的對(duì)象汗贫,比如CoreFoundation中的對(duì)象或是由malloc()分配在堆中的內(nèi)存身坐,那么仍然需要清理。ARC會(huì)自動(dòng)在.cxx_destruct方法中生成代碼并運(yùn)行dealloc方法落包,兒子啊生成的代碼中會(huì)自動(dòng)調(diào)用超類的dealloc方法部蛇。ARC環(huán)境下,dealloc方法可以像這么來寫
-(void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocateMemoryBlob);
}
第31條:在dealloc方法中只釋放引用并解除監(jiān)聽
永遠(yuǎn)不要自己調(diào)用dealloc方法咐蝇,運(yùn)行期系統(tǒng)會(huì)在適當(dāng)?shù)臅r(shí)候調(diào)用它涯鲁。根據(jù)性能需求我們有時(shí)需要在dealloc方法中做一些操作。那么我們可以在dealloc方法里做什么呢有序?
- 釋放對(duì)象所擁有的所有引用撮竿,不過ARC會(huì)自動(dòng)添加這些釋放代碼,可以不必操心笔呀。
- 而且對(duì)象擁有的其他非OC對(duì)象也要釋放(CoreFoundation對(duì)象就必須手動(dòng)釋放)
- 釋放原來的觀測行為:注銷通知幢踏。如果沒有及時(shí)注銷,就會(huì)向其發(fā)送通知许师,使得程序崩潰房蝉。
需要注意的是:
在dealloc方法中不應(yīng)該調(diào)用其他的方法,因?yàn)槿绻@些方法是異步的微渠,并且回調(diào)中還要使用當(dāng)前對(duì)象搭幻,那么很有可能當(dāng)前對(duì)象已經(jīng)被釋放了,會(huì)導(dǎo)致崩潰逞盆。同時(shí)檀蹋,在dealloc方法中也不能調(diào)用屬性的存取方法,因?yàn)楹苡锌赡茉谶@些方法里還有其他操作云芦。而且這個(gè)屬性還有可能處于鍵值觀察狀態(tài)俯逾,該屬性的觀察者可能會(huì)在屬性改變時(shí)保留或者使用這個(gè)即將回收的對(duì)象贸桶。
第32條:編寫“異常安全代碼”時(shí)留意內(nèi)存管理問題
在當(dāng)前的運(yùn)行期系統(tǒng)中,C++與Objective-C的異匙离龋互相兼容皇筛。也就是說,從其中一門語言里拋出的異常能用另外一門語言所編的"異常處理程序"來捕獲坠七。
在try塊中水醋,如果先保留了某個(gè)對(duì)象,然后在釋放它之前又拋出了異常彪置,那么除非在catch塊中能處理此問題拄踪,否則對(duì)象所占內(nèi)存就將泄漏。C++的析構(gòu)函數(shù)由Objective-C的異常處理例程來運(yùn)行拳魁。這對(duì)于C++對(duì)象很重要宫蛆,由于拋出異常會(huì)縮短生命周期,所以發(fā)生異常的時(shí)候必須析構(gòu)的猛,不然就會(huì)泄漏,而文件句柄等系統(tǒng)資源因?yàn)闆]有正確清理想虎,所以就更容易因此而泄漏了卦尊。
@try {
EOCSomeClass *object = [[EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
[object release];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
這里,我們用release方法釋放了try中的對(duì)象舌厨,但是這樣做仍然有問題:如果在doSomthingThatMayThrow方法中拋出了異常了呢岂却?
這樣就無法執(zhí)行release方法了。
解決辦法是使用@finnaly塊裙椭,無論是否拋出異常躏哩,其中的代碼都能運(yùn)行:
EOCSomeClass *object;
@try {
object = [[EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
@finally {
[object release];
}
而在ARC狀態(tài)下的時(shí)候,我們要這么寫:
@try {
EOCSomeClass *object = [[EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
我們不能使用release方法了揉燃。解決辦法就是需要打開編輯器的-fobjc-arc-exceptions標(biāo)志扫尺。不過會(huì)導(dǎo)致應(yīng)用程序過大,而且會(huì)降低運(yùn)行效率炊汤。
第33條:以弱引用避免保留環(huán)
- 將某些引用設(shè)為weak正驻,可以避免出現(xiàn)"保留環(huán)";
- weak引用可以自動(dòng)清空,也可以不自動(dòng)清空抢腐。自動(dòng)清空是隨著ARC而引入的新特性姑曙,由運(yùn)行期系統(tǒng)來實(shí)現(xiàn)。在具備自動(dòng)清空功能的弱引用上迈倍,可以隨意讀取其數(shù)據(jù)伤靠,因?yàn)檫@種引用不會(huì)指向已經(jīng)回收過的對(duì)象。
第34條:以“自動(dòng)釋放池快”降低內(nèi)存峰值
釋放對(duì)象的兩種方式:
- 調(diào)用release:保留計(jì)數(shù)遞減
- 調(diào)用autorelease將其加入自動(dòng)釋放池中啼染。在將來清空自動(dòng)釋放池時(shí)宴合,系統(tǒng)會(huì)向其中的對(duì)象發(fā)送release消息焕梅。
OSX和iOS的應(yīng)用程序分別運(yùn)行于Cocoa和CocoaTouch環(huán)境中。系統(tǒng)會(huì)自動(dòng)創(chuàng)建一些線程形纺,比如說主線程或是GCD機(jī)制中單額線程丘侠,這些線程默認(rèn)都有自動(dòng)釋放池,每次執(zhí)行runloop時(shí)逐样,就會(huì)自動(dòng)將其清空蜗字。因?yàn)椴恍枰约簛韯?chuàng)建自動(dòng)釋放池,通常只有一個(gè)地方需要?jiǎng)?chuàng)建自動(dòng)釋放池脂新,那就是在main函數(shù)里挪捕,我們用自動(dòng)釋放池來包裹應(yīng)用程序的主入口點(diǎn)。比方說争便,iOS程序的main函數(shù)經(jīng)常這樣寫:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
內(nèi)存峰值(high-memory waterline)是指應(yīng)用程序在某個(gè)限定時(shí)段內(nèi)的最大內(nèi)存用量(highest memory footprint)级零。新增的自動(dòng)釋放池塊可以減少這個(gè)峰值。
不用自動(dòng)釋放池減少峰值:
for (int i = 0; i < 100000; i++) {
[self doSomethingWithInt:i];
}
在這里滞乙,doSomethingWithInt:方法可能會(huì)創(chuàng)建臨時(shí)對(duì)象奏纪。隨著循環(huán)次數(shù)的增加,臨時(shí)對(duì)象的數(shù)量也會(huì)飆升斩启,而只有在整個(gè)for循環(huán)結(jié)束后序调,這些臨時(shí)對(duì)象才會(huì)得意釋放。
這種情況是不理想的兔簇,尤其在我們無法控制循環(huán)長度的情況下发绢,我們會(huì)不斷占用內(nèi)存并突然釋放掉它們。
又比如我們要從數(shù)據(jù)庫中讀取出很多對(duì)象時(shí):
NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
[people addObject:person];
}
這樣明顯多在內(nèi)存中多出很多不必要的臨時(shí)對(duì)象垄琐。我們就可以使用內(nèi)存釋放池:
NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
@autoreleasepool {
EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
[people addObject:person];
}
}
自動(dòng)釋放池機(jī)制就像棧一樣边酒。系統(tǒng)創(chuàng)建好自動(dòng)釋放池之后,就將其推入棧中狸窘,而清空自動(dòng)釋放池墩朦,則相當(dāng)于將其從棧中彈出。在對(duì)象上執(zhí)行自動(dòng)釋放操作翻擒,就等于將其放入棧頂?shù)哪莻€(gè)池里介杆。
而且,自動(dòng)釋放池還有一個(gè)好處:每個(gè)自動(dòng)釋放池均有其范圍韭寸,可以避免無意間誤用了那些在清空池之后已經(jīng)為系統(tǒng)所回收的對(duì)象春哨。
第35條:用“僵尸對(duì)象”調(diào)試內(nèi)存管理問題
某個(gè)對(duì)象被回收后,再向它發(fā)送消息是不安全的恩伺,這并不一定會(huì)引起程序崩潰赴背。
如果程序沒有崩潰,可能是因?yàn)椋?/p>
- 該內(nèi)存的部分原數(shù)據(jù)沒有被覆寫。
- 該內(nèi)存恰好被另一個(gè)對(duì)象占據(jù)凰荚,而這個(gè)對(duì)象可以應(yīng)答這個(gè)方法燃观。
如果被回收的對(duì)象占用的原內(nèi)存被新的對(duì)象占據(jù),那么收到消息的對(duì)象就不會(huì)是我們預(yù)想的那個(gè)對(duì)象便瑟。在這樣的情況下缆毁,如果這個(gè)對(duì)象無法響應(yīng)那個(gè)方法的話,程序依舊會(huì)崩潰到涂。
因此脊框,我們希望可以通過一種方法捕捉到對(duì)象被釋放后收到消息的情況。
這種方法就是利用僵尸對(duì)象践啄!
Cocoa提供了“僵尸對(duì)象”的功能浇雹。如果開啟了這個(gè)功能,運(yùn)行期系統(tǒng)會(huì)把所有已經(jīng)回收的實(shí)例轉(zhuǎn)化成特殊的“僵尸對(duì)象”(通過修改isa指針屿讽,令其指向特殊的僵尸類)昭灵,而不會(huì)真正回收它們,而且它們所占據(jù)的核心內(nèi)存將無法被重用伐谈,這樣也就避免了覆寫的情況烂完。
在僵尸對(duì)象收到消息后,會(huì)拋出異常诵棵,它會(huì)說明發(fā)送過來的消息抠蚣,也會(huì)描述回收之前的那個(gè)對(duì)象。
在設(shè)置僵尸對(duì)象之后非春,對(duì)象所屬的類會(huì)從xxxClass編程_NSZombie_xxxClass。_NSZombie_xxxClass實(shí)際上實(shí)在運(yùn)行期中生成的缓屠,當(dāng)首次碰到xxxClass類的對(duì)象要變成僵尸對(duì)象時(shí)奇昙,就會(huì)創(chuàng)建這么一個(gè)類。僵尸類從名為NSZombie的模板中輔助出來的敌完。這些僵尸對(duì)象沒有什么事情可以做储耐,只是充當(dāng)一個(gè)標(biāo)記。運(yùn)行期系統(tǒng)會(huì)給變成僵尸的類創(chuàng)建一個(gè)新類滨溉。創(chuàng)建新類的工作由運(yùn)行期函數(shù)objc_duplicateClass()來完成什湘,它會(huì)把整個(gè)NSZombie類結(jié)構(gòu)拷貝一份,并賦予其新的名字晦攒,在名字中保留原來類的名字闽撤。
NSZombie類并未實(shí)現(xiàn)出任何方法,此類沒有超類脯颜,因此和NSObject一樣是個(gè)根類哟旗。類里是有一個(gè)實(shí)例變量,就是isa。由于沒有實(shí)現(xiàn)任何方法闸餐,所以發(fā)給他的全部消息都要經(jīng)過"完整的消息轉(zhuǎn)發(fā)機(jī)制"饱亮。這樣,我們就可以在椛嵘常回溯中查看到前綴為__NSZombie __的僵尸對(duì)象近上。會(huì)打印出僵尸對(duì)象所受到的消息及其原來所屬的類。
第36條:不要使用retainCount
在非ARC得環(huán)境下使用retainCount可以返回當(dāng)前對(duì)象的引用計(jì)數(shù)拂铡,但是在ARC環(huán)境下調(diào)用會(huì)報(bào)錯(cuò)壹无,因?yàn)樵摲椒ㄒ呀?jīng)被廢棄了 。
它被廢棄的原因是因?yàn)樗祷氐囊糜?jì)數(shù)只能反映對(duì)象某一時(shí)刻的引用計(jì)數(shù)和媳,而無法“預(yù)知”對(duì)象將來引用計(jì)數(shù)的變化(比如對(duì)象當(dāng)前處于自動(dòng)釋放池中格遭,那么將來就會(huì)自動(dòng)遞減引用計(jì)數(shù))。
第37條:理解block這一概念
第38條:為常用的塊類型創(chuàng)建typedef
如果我們需要重復(fù)創(chuàng)建某種塊(相同參數(shù)拒迅,返回值)的變量,我們就可以通過typedef來給某一種塊定義屬于它自己的新類型她倘。
int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value){
// Implementation
return someInt;
}
這個(gè)塊有一個(gè)bool參數(shù)和一個(gè)int參數(shù)璧微,并返回int類型。我們可以給它定義類型:
typedef int(^EOCSomeBlock)(BOOL flag, int value);
再次定義的時(shí)候硬梁,就可以通過簡單的賦值來實(shí)現(xiàn):
EOCSomeBlock block = ^(BOOL flag, int value){
// Implementation
};
這么做的好處在于:
將代碼做的更加容易使用
當(dāng)你打算重構(gòu)塊的時(shí)候會(huì)很方便
第39條:用handler塊降低代碼分散程度
在界面編碼的時(shí)候前硫,我們經(jīng)常使用異步執(zhí)行任務(wù)。這么做的好處在于:處理用戶界面的顯示及觸摸操作所用的線程荧止,不會(huì)以內(nèi)要執(zhí)行I/O或網(wǎng)絡(luò)通信這類耗時(shí)的任務(wù)而阻塞屹电。這個(gè)線程我們一般稱之為主線程。
異步方法在執(zhí)行完任務(wù)之后跃巡,需要以某種手段通知相關(guān)代碼危号。這里有很多的方法,比如說delegate和block素邪。
與實(shí)現(xiàn)delegate模式相比外莲,block寫出來的代碼更為整潔。異步任務(wù)執(zhí)行完畢后所需要運(yùn)行的業(yè)務(wù)邏輯兔朦,和啟動(dòng)異步任務(wù)所用的代碼放在了一起偷线。
同時(shí),我們還可以用block的方式來處理錯(cuò)誤沽甥。而且声邦,在某些代碼必須執(zhí)行在特定的線程上的時(shí)候,會(huì)更加有效摆舟。
第40條:用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)
在使用塊的時(shí)候翔忽,若不仔細(xì)思考英融,會(huì)很容易導(dǎo)致保留環(huán)。
情況一:
@implementation EOCClass {
EOCNetworkFetcher *_networkFetcher;
NSData *_fetchedData;
}
- (void)downloadData {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
_networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data){
NSLog(@"Request URL %@ finished", _networkFetcher.url);
_fetchedData = data;
}];
}
在這里出現(xiàn)了保留環(huán):塊要設(shè)置_fetchedData變量歇式,就需要捕獲self變量驶悟。而self(EOCClass實(shí)例)通過實(shí)例變量保留了獲取器_networkFetcher,而_networkFetcher又保留了塊材失。
解決方案是:在塊中取得了data后痕鳍,將_networkFetcher設(shè)為nil。
- (void)downloadData {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
_networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data){
NSLog(@"Request URL %@ finished", _networkFetcher.url);
_fetchedData = data;
_networkFetcher = nil;
}];
}
情況二:
- (void)downloadData {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
EOCNetworkFetcher *networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
[networkFetcher startWithCompletionHandler:^(NSData *data){
NSLog(@"Request URL %@ finished", _networkFetcher.url);
_fetchedData = data;
}];
}
這里其實(shí)也出現(xiàn)了保留環(huán)龙巨,block要通過獲取獲取器對(duì)象來引用url笼呆,于是block就要保留獲取器,而同時(shí)獲取器反過來也保留了這個(gè)塊旨别。我們要在接下來要進(jìn)行的方法中做如下處理
-(void)p_requestCompleted{
if(_completionHandler){
_completionHandler(_downloadData);
}
self.completionHandler = nil;
}
第41條:多用派發(fā)隊(duì)列诗赌,少用同步鎖
多個(gè)線程執(zhí)行同一份代碼時(shí),很可能會(huì)造成數(shù)據(jù)不同步秸弛。作者建議使用GCD來為代碼加鎖的方式解決這個(gè)問題铭若。
方案一:使用串行同步隊(duì)列來將讀寫操作都安排到同一個(gè)隊(duì)列里:
_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);
//讀取字符串
- (NSString*)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
//設(shè)置字符串
- (void)setSomeString:(NSString*)someString {
dispatch_sync(_syncQueue, ^{
_someString = someString;
});
}
方案二:將寫操作放入柵欄快中,讓他們單獨(dú)執(zhí)行递览;將讀取操作并發(fā)執(zhí)行叼屠。
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//讀取字符串
- (NSString*)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
//設(shè)置字符串
- (void)setSomeString:(NSString*)someString {
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}
顯然,數(shù)據(jù)的正確性主要取決于寫入操作绞铃,那么只要保證寫入時(shí)镜雨,線程是安全的,那么即便讀取操作是并發(fā)的儿捧,也可以保證數(shù)據(jù)是同步的荚坞。
這里的dispatch_barrier_async方法使得操作放在了同步隊(duì)列里“有序進(jìn)行”,保證了寫入操作的任務(wù)是在串行隊(duì)列里菲盾。
第42條:多用GCD颓影,少用performSelector系列方法
因?yàn)镺bjective-C語言的特性。編譯器并不知道要調(diào)用的選擇子是什么亿汞,因此瞭空,也就不了解其方法簽名及返回值揪阿,甚至連是否有返回值都不清楚疗我。而且因?yàn)椴涣私夥椒跃蜎]辦法運(yùn)用ARC的內(nèi)存管理機(jī)制來判斷返回值是否應(yīng)該釋放南捂。
使用performSelector來執(zhí)行某個(gè)方法吴裤,但是performSelector系列的方法能處理的選擇子很局限:它無法處理帶有兩個(gè)以上的參數(shù)的選擇子;返回值只能是對(duì)象類型溺健。
但是我們?nèi)绻褂胋lock和GCD麦牺,就可以解決這些問題。
- 延后執(zhí)行某個(gè)任務(wù)的方法:
// 使用 performSelector:withObject:afterDelay:
[self performSelector:@selector(doSomething) withObject:nil afterDelay:5.0];
// 使用 dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
[self doSomething];
});
- 將任務(wù)放在主線程執(zhí)行:
// 使用 performSelectorOnMainThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];
// 使用 dispatch_async
// (or if waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
這里我們都優(yōu)先考慮第二種。
第43條:掌握GCD及操作隊(duì)列的使用時(shí)機(jī)
除了GCD剖膳,操作隊(duì)列(NSOperationQueue)也是解決多線程任務(wù)管理問題的一個(gè)方案魏颓。對(duì)于不同的環(huán)境,我們要采取不同的策略來解決問題:有時(shí)候使用GCD好些吱晒,有時(shí)則是使用操作隊(duì)列更加合理甸饱。
GCD是純C語言實(shí)現(xiàn)的API,而NSOperationQueue是Objective-C的仑濒。
使用NSOperation和NSOperationQueue的優(yōu)點(diǎn):
- 可以取消操作:在運(yùn)行任務(wù)前叹话,可以在NSOperation對(duì)象調(diào)用cancel方法,標(biāo)明此任務(wù)不需要執(zhí)行墩瞳。但是GCD隊(duì)列是無法取消的驼壶,因?yàn)樗裱鞍才藕弥缶筒还芰耍╢ire and forget)”的原則。
- 可以指定操作間的依賴關(guān)系:例如從服務(wù)器下載并處理文件的動(dòng)作可以用操作來表示喉酌。而在處理其他文件之前必須先下載“清單文件”热凹。而后續(xù)的下載工作,都要依賴于先下載的清單文件這一操作瞭吃。
- 監(jiān)控NSOperation對(duì)象的屬性:可以通過KVO來監(jiān)聽NSOperation的屬性:可以通過isCancelled屬性來判斷任務(wù)是否已取消碌嘀;通過isFinished屬性來判斷任務(wù)是否已經(jīng)完成。
- 可以指定操作的優(yōu)先級(jí):操作的優(yōu)先級(jí)表示此操作與隊(duì)列中其他操作之間的優(yōu)先關(guān)系歪架,我們可以指定它股冗。
- 重用NSOperation對(duì)象。我們?nèi)缫獎(jiǎng)?chuàng)建NSOperation的子類和蚪,可以方便的使用其中的方法止状。
第44條:通過Dispath Group機(jī)制,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務(wù)
有時(shí)需要等待多個(gè)并行任務(wù)結(jié)束的那一刻執(zhí)行某個(gè)任務(wù)攒霹,這個(gè)時(shí)候就可以使用dispath group函數(shù)來實(shí)現(xiàn)這個(gè)需求:
通過dispath group函數(shù)怯疤,可以把并發(fā)執(zhí)行的多個(gè)任務(wù)合為一組,于是調(diào)用者就可以知道這些任務(wù)何時(shí)才能全部執(zhí)行完畢催束。
//一個(gè)優(yōu)先級(jí)低的并發(fā)隊(duì)列
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//一個(gè)優(yōu)先級(jí)高的并發(fā)隊(duì)列
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//創(chuàng)建dispatch_group
dispatch_group_t dispatchGroup = dispatch_group_create();
//將優(yōu)先級(jí)低的隊(duì)列放入dispatch_group
for (id object in lowPriorityObjects) {
dispatch_group_async(dispatchGroup,lowPriorityQueue,^{ [object performTask]; });
}
//將優(yōu)先級(jí)高的隊(duì)列放入dispatch_group
for (id object in highPriorityObjects) {
dispatch_group_async(dispatchGroup,highPriorityQueue,^{ [object performTask]; });
}
//dispatch_group里的任務(wù)都結(jié)束后調(diào)用塊中的代碼
dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,notifyQueue,^{
// Continue processing after completing tasks
});
是用GCD會(huì)在適當(dāng)?shù)臅r(shí)候自動(dòng)創(chuàng)建新線程或者復(fù)用舊線程集峦。在并發(fā)隊(duì)列中,執(zhí)行任務(wù)所用的并發(fā)線程數(shù)量抠刺,取決于各種因素塔淤,而GCD主要是根據(jù)系統(tǒng)資源狀況來判定這些因素的。通過dispatch group所提供的這種簡便方式速妖,既可以并發(fā)執(zhí)行一系列給定的文物高蜂,又能在全部任務(wù)結(jié)束的時(shí)候得到通知。由于GCD有并發(fā)隊(duì)列機(jī)制罕容,所以能夠根據(jù)可用的系統(tǒng)資源狀況來并發(fā)執(zhí)行任務(wù)备恤。
第45條:使用dispatch_once來執(zhí)行只需運(yùn)行一次的線程安全代碼
有時(shí)我們可能只需要將某段代碼執(zhí)行一次稿饰,這時(shí)可以通過dispatch_once函數(shù)來解決妄痪。
dispatch_once函數(shù)比較重要的使用例子是單例模式:
我們?cè)趧?chuàng)建單例模式的實(shí)例時(shí)沧踏,可以使用dispatch_once函數(shù)來令初始化代碼只執(zhí)行一次,并且內(nèi)部是線程安全的宣赔。
而且惭笑,對(duì)于執(zhí)行一次的block來說梧喷,每次調(diào)用函數(shù)時(shí)傳入的標(biāo)記都必須完全相同,通常標(biāo)記變量聲明在static或global作用域里脖咐。
+ (id)sharedInstance {
static EOCClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
使用dispatch_once可以簡化代碼并且徹底保證線程安全铺敌,開發(fā)者無需擔(dān)心加鎖或者同步。另外屁擅,dispatch_once方法更加高效偿凭,此函數(shù)采用原子訪問(atomic access)來查詢標(biāo)記,以判斷所對(duì)應(yīng)的代碼是否已經(jīng)執(zhí)行過派歌。
第46條:不要使用dispatch_get_current_queue
我們無法用某個(gè)隊(duì)列來描述“當(dāng)前隊(duì)列”這一屬性弯囊,因?yàn)榕砂l(fā)隊(duì)列是按照層級(jí)來組織的。
[圖片上傳失敗...(image-74a524-1527207303216)]
安排在某條隊(duì)列中的快胶果,會(huì)在其上層隊(duì)列中執(zhí)行匾嘱,而層級(jí)地位最高的那個(gè)隊(duì)列總是全局并發(fā)隊(duì)列。
在這里早抠,B霎烙,C中的塊會(huì)在A里執(zhí)行。但是D中的塊蕊连,可能與A里的塊并行,因?yàn)锳和D的目標(biāo)隊(duì)列是并發(fā)隊(duì)列甘苍。
正因?yàn)橛辛诉@種層級(jí)關(guān)系载庭,所以檢查當(dāng)前隊(duì)列是并發(fā)的還是非并發(fā)的就不會(huì)總是很準(zhǔn)確囚聚。
dispatch_get_current_queue函數(shù)能夠用于解決由不可重入的隊(duì)列代碼所引發(fā)的死鎖靖榕,然而能用此函數(shù)解決的問題,也能用dispatch_queue_set_specific來解決靡挥。
第47條:熟悉系統(tǒng)框架
如果我們使用了系統(tǒng)提供的現(xiàn)成的框架序矩,那么用戶在升級(jí)系統(tǒng)后鸯绿,就可以直接享受系統(tǒng)升級(jí)所帶來的改進(jìn)跋破。
主要的系統(tǒng)框架:
Foundation:NSObject,NSArray,NSDictionary等
CFoundation框架:C語言API簸淀,F(xiàn)oundation框架中的許多功能,都可以在這里找到對(duì)應(yīng)的C語言API
CFNetwork框架:C語言API毒返,提供了C語言級(jí)別的網(wǎng)絡(luò)通信能力
CoreAudio:C語言API租幕,操作設(shè)備上的音頻硬件
AVFoundation框架:提供的OC對(duì)象可以回放并錄制音頻和視頻
CoreData框架:OC的API,將對(duì)象寫入數(shù)據(jù)庫
CoreText框架:C語言API拧簸,高效執(zhí)行文字排版和渲染操作
用C語言來實(shí)現(xiàn)API的好處:可以繞過OC的運(yùn)行期系統(tǒng)劲绪,從而提升執(zhí)行速度。用純C寫成的框架與用Objective-C寫成的一樣重要盆赤,若想成為優(yōu)秀的Objective-C開發(fā)者贾富,應(yīng)該掌握C語言的核心概念。
第48條:多用塊枚舉牺六,少用for循環(huán)
當(dāng)遍歷集合元素時(shí)颤枪,建議使用塊枚舉,因?yàn)橄鄬?duì)于傳統(tǒng)的for循環(huán),它更加高效票灰,而且簡潔,還能獲取到用傳統(tǒng)的for循環(huán)無法提供的值:
我們首先看一下傳統(tǒng)的遍歷:
傳統(tǒng)的for遍歷
NSArray *anArray = /* ... */;
for (int i = 0; i < anArray.count; i++) {
id object = anArray[i];
// Do something with 'object'
}
// Dictionary
NSDictionary *aDictionary = /* ... */;
NSArray *keys = [aDictionary allKeys];
for (int i = 0; i < keys.count; i++) {
id key = keys[i];
id value = aDictionary[key];
// Do something with 'key' and 'value'
}
// Set
NSSet *aSet = /* ... */;
NSArray *objects = [aSet allObjects];
for (int i = 0; i < objects.count; i++) {
id object = objects[i];
// Do something with 'object'
}
我們可以看到,在遍歷NSDictionary,和NSet時(shí)屈糊,我們又新創(chuàng)建了一個(gè)數(shù)組。雖然遍歷的目的達(dá)成了昧诱,但是卻加大了系統(tǒng)的開銷盏档。
利用快速遍歷:
NSArray *anArray = /* ... */;
for (id object in anArray) {
// Do something with 'object'
}
// Dictionary
NSDictionary *aDictionary = /* ... */;
for (id key in aDictionary) {
id value = aDictionary[key];
// Do something with 'key' and 'value'
}
NSSet *aSet = /* ... */;
for (id object in aSet) {
// Do something with 'object'
}
這種快速遍歷的方法要比傳統(tǒng)的遍歷方法更加簡潔易懂懦窘,但是缺點(diǎn)是無法方便獲取元素的下標(biāo)。
利用基于block的遍歷:
NSArray *anArray = /* ... */;
[anArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop){
// Do something with 'object'
if (shouldStop) {
*stop = YES; //使迭代停止
}
}];
“// Dictionary
NSDictionary *aDictionary = /* ... */;
[aDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop){
// Do something with 'key' and 'object'
if (shouldStop) {
*stop = YES;
}
}];
// Set
NSSet *aSet = /* ... */;
[aSet enumerateObjectsUsingBlock:^(id object, BOOL *stop){
// Do something with 'object'
if (shouldStop) {
*stop = YES;
}
我們可以看到午衰,在使用塊進(jìn)行快速枚舉的時(shí)候,我們可以不創(chuàng)建臨時(shí)數(shù)組扇单。雖然語法上沒有快速枚舉簡潔,但是我們可以獲得數(shù)組元素對(duì)應(yīng)的序號(hào)鄙信,字典元素對(duì)應(yīng)的鍵值,而且鸦采,我們還可以隨時(shí)令遍歷終止渔伯。
利用快速枚舉和塊的枚舉還有一個(gè)優(yōu)點(diǎn):能夠修改塊的方法簽名
for (NSString *key in aDictionary) {
NSString *object = (NSString*)aDictionary[key];
// Do something with 'key' and 'object'
}
NSDictionary *aDictionary = /* ... */;
[aDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop){
// Do something with 'key' and 'obj'
}];
如果我們可以知道集合里的元素類型,就可以修改簽名玄叠。這樣做的好處是:可以讓編譯期檢查該元素是否可以實(shí)現(xiàn)我們想調(diào)用的方法,如果不能實(shí)現(xiàn)疹吃,就做另外的處理。這樣一來蒋搜,程序就能變得更加安全。
個(gè)人添加
- block遍歷還有一個(gè)明顯的優(yōu)點(diǎn):如果數(shù)組的數(shù)量過多帮哈,除了block遍歷,其他的遍歷方法都需要添加autoreleasePool方法來優(yōu)化憾筏。block不需要,因?yàn)橄到y(tǒng)在實(shí)現(xiàn)它的時(shí)候就已經(jīng)實(shí)現(xiàn)了相關(guān)處理古拴。
第49條:對(duì)自定義其內(nèi)存管理語義的collection使用無縫橋接
通過無縫橋接技術(shù),可以再Foundation框架中的OC對(duì)象和CoreFoundation框架中的C語言數(shù)據(jù)結(jié)構(gòu)之間來回轉(zhuǎn)換满力。
創(chuàng)建CoreFoundation中的collection時(shí),可以指定如何處理其中的元素潦嘶。然后利用無縫橋接技術(shù)航厚,可以將其轉(zhuǎn)換為OCcollection。
簡單的無縫橋接演示:
NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));
這里麻顶,__bridge表示ARC仍然具備這個(gè)OC對(duì)象的所有權(quán)。CFArrayGetCount用來獲取數(shù)組的長高度矫钓。
為什么要使用無縫橋接技術(shù)呢新娜?因?yàn)橛行㎡C對(duì)象的特性是其對(duì)應(yīng)的CF數(shù)據(jù)結(jié)構(gòu)不具備的,反之亦然旁钧。因此我們需要通過無縫橋接技術(shù)來讓這兩者進(jìn)行功能上的“互補(bǔ)”。
第50條:構(gòu)建緩存時(shí)選用NSCache 而非NSDictionary
如果我們緩存使用得當(dāng)寄猩,那么應(yīng)用程序的響應(yīng)速度就會(huì)提高田篇。只有那種“重新計(jì)算起來很費(fèi)事的數(shù)據(jù),才值得放入緩存”兽赁,比如那些需要從網(wǎng)絡(luò)獲取或從磁盤讀取的數(shù)據(jù)惊科。
在構(gòu)建緩存的時(shí)候很多人習(xí)慣用NSDictionary或者NSMutableDictionary,但是作者建議大家使用NSCache蜡娶,它作為管理緩存的類淮菠,有很多特點(diǎn)要優(yōu)于字典枢赔,因?yàn)樗緛砭褪菫榱斯芾砭彺娑O(shè)計(jì)的。
NSCache優(yōu)于NSDictionary的幾點(diǎn):
- 當(dāng)系統(tǒng)資源將要耗盡時(shí)速梗,NSCache具備自動(dòng)刪減緩沖的功能。并且還會(huì)先刪減“最久未使用”的對(duì)象位隶。
NSCache不拷貝鍵,而是保留鍵笋妥。因?yàn)椴⒉皇撬械逆I都遵從拷貝協(xié)議(字典的鍵是必須要支持拷貝協(xié)議的,有局限性)信认。- NSCache是線程安全的:不編寫加鎖代碼的前提下其掂,多個(gè)線程可以同時(shí)訪問NSCache。
關(guān)于操控NSCache刪減內(nèi)容的時(shí)機(jī)
開發(fā)者可以通過兩個(gè)尺度來調(diào)整這個(gè)時(shí)機(jī):
緩存中的對(duì)象總數(shù).
將對(duì)象加入緩存時(shí)贤牛,為其指定開銷值殉簸。
對(duì)于開銷值,只有在能很快計(jì)算出開銷值的情況下蝠检,才應(yīng)該考慮采用這個(gè)尺度,不然反而會(huì)加大系統(tǒng)的開銷焰檩。
NSPurgeableData是NSMutableData的子類,把它和NSCache配合使用效果很好藤违。
因?yàn)楫?dāng)系統(tǒng)資源緊張時(shí),可以把保存NSPurgeableData的那塊內(nèi)存釋放掉特漩。
如果需要訪問某個(gè)NSPurgeableData對(duì)象,可以調(diào)用beginContentAccess方發(fā)蛤售,告訴它現(xiàn)在還不應(yīng)該丟棄自己所占據(jù)的內(nèi)存。
在使用完之后,調(diào)用endContentAccess方法炒嘲,告訴系統(tǒng)在必要時(shí)可以丟棄自己所占據(jù)的內(nèi)存空凸。
- 如果緩存使用得當(dāng)紊选,那么應(yīng)用程序的響應(yīng)速度就能提高。只有那種"重新計(jì)算起來很費(fèi)事"的數(shù)據(jù),才值得放入緩存此蜈,比如那些需要從網(wǎng)絡(luò)獲取或者從磁盤讀取的數(shù)據(jù)跺嗽。
第51條: 精簡initialize 與 load的實(shí)現(xiàn)代碼
每個(gè)類和分類在加入運(yùn)行期系統(tǒng)時(shí)份帐,都會(huì)調(diào)用load方法,而且僅僅調(diào)用一次彬坏,可能有些小伙伴習(xí)慣在這里調(diào)用一些方法幻赚,但是作者建議盡量不要在這個(gè)方法里調(diào)用其他方法,尤其是使用其他的類佳谦。原因是每個(gè)類載入程序庫的時(shí)機(jī)是不同的奸鸯,如果該類調(diào)用了還未載入程序庫的類咪笑,就會(huì)很危險(xiǎn)。而且娄涩,load方法并不會(huì)遵從繼承規(guī)則窗怒。如果某個(gè)類本身沒實(shí)現(xiàn)load方法,那么不管其各級(jí)超類是否實(shí)現(xiàn)此方法蓄拣,系統(tǒng)都不會(huì)調(diào)用弯蚜。
initialize方法與load方法類似优构,區(qū)別是這個(gè)方法會(huì)在程序首次調(diào)用這個(gè)類的時(shí)候調(diào)用(惰性調(diào)用),而且只調(diào)用一次(絕對(duì)不能主動(dòng)使用代碼調(diào)用)快毛。值得注意的一點(diǎn)是,如果子類沒有實(shí)現(xiàn)它瑰妄,它的超類卻實(shí)現(xiàn)了地技,那么就會(huì)運(yùn)行超類的代碼:這個(gè)情況往往很容易讓人忽視庵芭。
在實(shí)現(xiàn)initalize方法時(shí),需要加判斷是否是本類:
+ (void)initialize {
if (self == [EOCBaseClass class]) {
NSLog(@"%@ initialized", self);
}
}
- 在initalize方法里應(yīng)該只用來設(shè)置內(nèi)部數(shù)據(jù)笛坦。不應(yīng)該調(diào)用方法柿扣,即使是本類方法泡仗。
- 無法再編譯期設(shè)定的全局變量(比如說NSMutableArray),可以放在initialize里實(shí)現(xiàn)卖鲤。
load | initialize | |
---|---|---|
調(diào)用時(shí)間 | 被添加到 runtime 時(shí) | 收到第一條消息前欺旧,可能永遠(yuǎn)不調(diào)用 |
調(diào)用順序 | 父類->子類->分類 | 父類->子類 |
調(diào)用次數(shù) | 1次 | 多次 |
是否沿用父類的實(shí)現(xiàn) | NO | YES |
分類中的實(shí)現(xiàn) | 類和分類都執(zhí)行 | 覆蓋類中的方法,只執(zhí)行分類的實(shí)現(xiàn) |
第52條: 別忘了NSTimer會(huì)保留其目標(biāo)對(duì)象
在使用NSTimer的時(shí)候,NSTimer會(huì)生成指向其使用者的引用泼菌,而其使用者如果也引用了NSTimer,那么就會(huì)生成保留環(huán)。
#import <Foundation/Foundation.h>
@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end
@implementation EOCClass {
NSTimer *_pollTimer;
}
- (id)init {
return [super init];
}
- (void)dealloc {
[_pollTimer invalidate];
}
- (void)stopPolling {
[_pollTimer invalidate];
_pollTimer = nil;
}
- (void)startPolling {
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(p_doPoll)
userInfo:nil
repeats:YES];
}
- (void)p_doPoll {
// Poll the resource
}
@end
在這里,在EOCClass和_pollTimer之間形成了保留環(huán),如果不主動(dòng)調(diào)用stopPolling方法就無法打破這個(gè)保留環(huán)。
那么我們要如何解決呢夭苗?用block
通過給NSTimer增加一個(gè)分類就可以解決:
#import <Foundation/Foundation.h>
@interface NSTimer (EOCBlocksSupport)
+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (EOCBlocksSupport)
+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(eoc_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)eoc_blockInvoke:(NSTimer*)timer {
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
我們?cè)贜STimer類里添加了方法,我們來看一下如何使用它:
- (void)startPolling {
__weak EOCClass *weakSelf = self;
_pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{
EOCClass *strongSelf = weakSelf;
[strongSelf p_doPoll];
}
repeats:YES];
}
在這里役首,創(chuàng)建了一個(gè)self的弱引用确虱,然后讓塊捕獲了這個(gè)self變量,讓其在執(zhí)行期間存活钳宪。
一旦外界指向EOC類的最后一個(gè)引用消失缩多,該類就會(huì)被釋放圈纺,被釋放的同時(shí)钩述,也會(huì)向NSTimer發(fā)送invalidate消息(因?yàn)樵谠擃惖膁ealloc方法中向NSTimer發(fā)送了invalidate消息)。
而且尾抑,即使在dealloc方法里沒有發(fā)送invalidate消息歇父,因?yàn)閴K里的weakSelf會(huì)變成nil,所以NSTimer同樣會(huì)失效再愈。