47.熟悉系統(tǒng)框架
將一系列代碼封裝為動態(tài)庫(dynamic library)宿刮,并在其中放入描述其接口的頭文件,這樣做出來的東西就叫框架导盅。有時為iOS平臺構(gòu)建的第三方框架所使用的是靜態(tài)庫(static library)素征,這是因為iOS應(yīng)用程序不允許在其中包含動態(tài)庫急凰。這些東西嚴格來講并不是真正的框架,然而也經(jīng)常視為框架蹂窖。不過轧抗,所有iOS平臺的系統(tǒng)框架仍然使用動態(tài)庫。
在為Mac OS X或iOS系統(tǒng)開發(fā)“帶圖形界面的應(yīng)用程序”時恼策,會用到名為Cocoa的框架鸦致,在iOS上成為Cocoa Touch潮剪。其實Cocoa本身并不是框架,但是里面繼承了一批創(chuàng)建應(yīng)用程序時經(jīng)常會用到的框架分唾。
開發(fā)者會碰到的主要框架就是Foundation抗碰,像是NSObject、NSArray绽乔、NSDictionary等類都在其中弧蝇。Foundation框架是所有Objective-C應(yīng)用程序的“基礎(chǔ)”。
Foundation框架不僅提供了collection等基礎(chǔ)核心功能折砸,而且還提供了字符串處理這樣的復(fù)雜功能看疗。比方說,NSLinguisticTagger可以解析字符串并找到其中的全部名詞睦授、動詞两芳、代詞等。
還有個與Foundation相伴的框架去枷,叫做CoreFoundation怖辆。雖然從技術(shù)上講,CoreFoundation框架不是Objective-C框架删顶,但它確實編寫Objective-C應(yīng)用程序所應(yīng)熟悉的重要框架竖螃,F(xiàn)oundation框架中的許多功能,都可以在此框架中找到對應(yīng)的C語言API逗余。CoreFoundation與Foundation不僅名字相似特咆,而且還有更為緊密的聯(lián)系。有個功能叫做“無縫橋接”(toll-free bridging)录粱,可以把CoreFoundation中的C語言數(shù)據(jù)結(jié)構(gòu)平滑轉(zhuǎn)換為Foundation中的Objective-C對象腻格,也可以反向轉(zhuǎn)換。比方說关摇,F(xiàn)oundation框架中的字符串是NSString荒叶,而它可以轉(zhuǎn)換為CoreFoundation里與之等效的CFString對象。無縫橋接技術(shù)是用某些相當(dāng)復(fù)雜的代碼實現(xiàn)出來的输虱,這些代碼可以使運行期系統(tǒng)把CoreFoundation框架中的對象視為普通的Objective-C對象些楣。
除了Foundation與CoreFoundation之外,還有很多系統(tǒng)庫宪睹,其中包括但不限于下面列出的這些:
- CFNetwork 此框架提供了C語言級別的網(wǎng)絡(luò)通信能力愁茁,它將“BSD套接字”(BSD socket)抽象成易于使用的網(wǎng)絡(luò)接口。而Foundation則將該框架里的部分內(nèi)容封裝為Objective-C語言的接口亭病,以便進行網(wǎng)絡(luò)通信鹅很,例如可以用NSURLConnection從URL下載數(shù)據(jù)。
- CoreAudio 該框架所提供的C語言API可用來操作設(shè)備上的音頻文件罪帖。這個框架屬于比較難用的促煮,因為音頻處理本身就很復(fù)雜邮屁。所幸由這套API可以抽象出另外一套Objective-C式API,用后者來處理音頻問題會更簡單些菠齿。
- CoreData 此框架所提供的Objective-C接口可將對象放入數(shù)據(jù)庫佑吝,便于持久保存。CoreData會處理數(shù)據(jù)的獲取及存儲事宜绳匀,而且可以跨越Mac OS X及iOS平臺芋忿。
- CoreText 此框架提供的C語言接口可以高效執(zhí)行文字排版及渲染操作。
Objective-C編程時會經(jīng)常需要使用底層的C語言級API疾棵。用C語言來實現(xiàn)API的好處是戈钢,可以繞過Objective-C的運行期系統(tǒng),從而提升執(zhí)行速度是尔。當(dāng)然殉了,由于ARC只負責(zé)Objective-C的對象,所以使用這些API時尤其要注意內(nèi)存管理問題拟枚。
Mac OS X與iOS平臺的核心UI框架分別叫AppKit及UIKit宣渗,它們都提供了構(gòu)建在Foundation與CoreFoundation之上的Objective-C類。在這些主要的UI框架之下梨州,是CoreAnimation與CoreGraphics框架。
CoreAnimation是用Objective-C語言寫成的田轧,它提供了一些工具暴匠,而UI框架則用這些工具來渲染圖形并播放動畫。CoreAnimation本身并不是框架傻粘,它是QuartzCore框架的一部分每窖。
CoreGraphics框架是用C語言寫成的,其中提供了2D渲染所必備的數(shù)據(jù)結(jié)構(gòu)與函數(shù)弦悉。例如窒典,其中定義了CGPoint、CGSize稽莉、CGRect等數(shù)據(jù)結(jié)構(gòu)瀑志,而UIKit框架中的UIView類在確定視圖控件之間的相對位置時,這些數(shù)據(jù)結(jié)構(gòu)都要用到污秆。
還有很多框架構(gòu)建在UI框架之上劈猪,比如MapKit框架,它為iOS程序提供地圖功能良拼。又比如Social框架战得,它為Mac OS X及iOS程序提供了社交網(wǎng)絡(luò)功能。
要點:
- 許多系統(tǒng)框架都可以直接使用庸推。其中最重要的是Foundation與CoreFoundation常侦,這兩個框架提供了構(gòu)建應(yīng)用程序所需的許多核心功能浇冰。
- 很多常見任務(wù)都能用框架來做,例如音頻與視頻處理聋亡、網(wǎng)絡(luò)通信肘习、數(shù)據(jù)管理等。
- 請記咨蹦怼:用純C寫成的框架與用Objective-C寫成的一樣重要井厌,若想要成為優(yōu)秀的Objective-C開發(fā)者,應(yīng)該掌握C語言的核心概念致讥。
48.多用塊枚舉仅仆,少用for循環(huán)
在編程中經(jīng)常需要列舉collection中的元素,當(dāng)前的Objective-C語言有很多種辦法實現(xiàn)此功能垢袱,可以用標準的C語言循環(huán)墓拜,也可以用Objective-C 1.0的NSEnumerator以及Objective-C 2.0的快速遍歷。語言中引入“塊”這一特性后请契,又多出來幾種新的遍歷方式咳榜,采用這幾種新方式遍歷collection時,可以傳入塊爽锥,而collection中的每個元素都可能會放在塊里運行一遍涌韩,這種做法通常會大幅度簡化編碼過程。
- 使用Objective-C 1.0的NSEnumerator來遍歷
NSEnumerator是個抽象基類氯夷,其中只定義了兩個方法臣樱,供其具體子類來實現(xiàn):
- (NSArray*)allObjects;
- (nullable ObjectType)nextObject;
其中關(guān)鍵的方法是nextObject,它返回枚舉里的下個對象腮考。每次調(diào)用該方法時雇毫,其內(nèi)部數(shù)據(jù)結(jié)構(gòu)都會更新,使得下次調(diào)用方法時能返回下個對象踩蔚。等到枚舉中的全部對象都已返回之后棚放,再調(diào)用就將返回nil,這表示達到枚舉末端了馅闽。
- 快速遍歷
Objective-C 2.0引入了快速遍歷這一功能飘蚯。它為for循環(huán)開設(shè)了in關(guān)鍵字。這個關(guān)鍵字大幅簡化了遍歷collection所需的語法捞蛋。
如果某個類的對象支持快速遍歷孝冒,那么就可以宣稱自己遵從名為NSFastEnumeration的協(xié)議,從而令開發(fā)者可以采用此語法來迭代該對象拟杉。此協(xié)議只定義了一個方法:
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id __unsafe_unretained [])buffer
count:(NSUInteger)len;
該方法允許類實例同時返回多個對象庄涡,這就使得循環(huán)遍歷操作更為高效了。
- 基于塊的遍歷方式
在當(dāng)前的Objective-C語言中搬设,最新引入的一種做法就是基于塊來遍歷穴店。NSArray中定義了下面這個方法撕捍,它可以實現(xiàn)最基本的遍歷功能:
- (void)enumerateObjectsUsingBlock:
- (void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block ;
此方法提供了一種優(yōu)雅的機制,用于終止遍歷操作泣洞,開發(fā)者可以通過設(shè)定stop變量值來實現(xiàn)忧风。
- (void)enumerateObjectsWithOptions:
(NSEnumerationOptions)opts
usingBlock:
(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;
- (void)enumerateKeysAndObjectsWithOptions:
(NSEnumerationOptions)opts
usingBlock:
(void (^)(KeyType key, ObjectType obj, BOOL *stop))block;
NSEnumerationOptions類型是個enum,其各種取值可用“按位或”連接球凰,用以表明遍歷方式狮腿。例如,開發(fā)者可以請求以并發(fā)方式執(zhí)行各輪迭代呕诉,也就是說缘厢,如果當(dāng)前系統(tǒng)資源狀況允許,那么執(zhí)行每次迭代所用的塊就可以并行執(zhí)行了甩挫。通過NSEnumerationConcurrent選項即可開啟此功能贴硫。如果使用此選項,那么底層會通過GCD來處理并發(fā)執(zhí)行事宜伊者,具體實現(xiàn)時很可能會用到dispatch group英遭。反向遍歷是通過NSEnumerationReverse選項來實現(xiàn)的。
總體來看亦渗,塊枚舉法擁有其他遍歷方式都具備的又是挖诸,而且還能帶來更多好處。與快速遍歷法相比法精,它更多用一些代碼税灌,可是卻能提供遍歷時所針對的下標,在遍歷字典時也能同時提供鍵與值亿虽,而且還有選項可以開啟并發(fā)迭代功能。
要點:
- 遍歷collection有四種方式苞也。最基本的辦法是for循環(huán)洛勉,其次是NSEnumerator遍歷法及快速遍歷法,最新如迟、最先進的方式則是“塊枚舉法”收毫。
- “塊枚舉法”本身就能通過GCD來并發(fā)執(zhí)行遍歷操作,無須另行編寫代碼殷勘。而采用其他遍歷方式則無法輕易實現(xiàn)這一點此再。
- 若提前知道待遍歷的collection含有何種對象,則應(yīng)修改塊簽名玲销,指出對象的具體類型输拇。
49.對自定義其內(nèi)存管理語義的collection使用無縫橋接
使用“無縫橋接”技術(shù),可以在定義于Foundation框架中的Objective-C類和定義與CoreFoundation框架中的C數(shù)據(jù)結(jié)構(gòu)之間互相轉(zhuǎn)換贤斜。
下列代碼演示了簡單的無縫橋接:
NSArray *anNSArray = @[@1,@2,@3,@4,@5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li",CFArrayGetCount(aCFArray));
轉(zhuǎn)換中的__bridge告訴ARC如何處理轉(zhuǎn)換所涉及的Objective-C對象策吠。__bridge本身的意思是:ARC仍然具備這個Objective-C對象的所有權(quán)逛裤。而__bridge_retained則與之相反,意味著ARC將交出對象的所有權(quán)猴抹。若是前面那段代碼改用它來實現(xiàn)带族,那么用完數(shù)組之后就要加上CFRelease(aCFArray)以釋放其內(nèi)存。與之相似蟀给,反向轉(zhuǎn)換可通過__bridge_transfer來實現(xiàn)蝙砌。比方說,想把CFArrayRef轉(zhuǎn)換為NSArray跋理,并且想令A(yù)RC獲得對象所有權(quán)择克,那么就可以采用此種轉(zhuǎn)換方式。這三種轉(zhuǎn)換方式成為”橋式轉(zhuǎn)換“(bridged cast)薪介。*
以純Objective-C來編寫應(yīng)用程序時祠饺,為何要用到這種功能呢?這是因為:Foundation框架中的Objective-C類所具備的某些功能汁政,是CoreFoundation框架中的C語言數(shù)據(jù)結(jié)構(gòu)所不具備的道偷,反之亦然。在使用Foundation框架中的字典對象時會遇到一個大問題记劈,那就是其鍵的內(nèi)存管理語義為”拷貝“勺鸦,而值的語義卻是”保留“。除非使用強大的無縫橋接技術(shù)目木,否則無法改變其語義换途。
CoreFoundation框架中的字典類型叫做CFDictionary。其可變版本稱為CFMutableDictionary刽射。創(chuàng)建CFMutableDictionary時军拟,可以通過下列方法來指定鍵和值的內(nèi)存管理語義:
CFDictionaryRef CFDictionaryCreate(
CFAllocatorRef allocator,
const void **keys,
const void **values,
CFIndex numValues,
const CFDictionaryKeyCallBacks *keyCallBacks,
const CFDictionaryValueCallBacks *valueCallBacks);
首個參數(shù)表示將要使用的內(nèi)存分配器。CoreFoundation對象里的數(shù)據(jù)結(jié)構(gòu)需要占用內(nèi)存誓禁,而分配器負責(zé)分配及回收這些內(nèi)存懈息。開發(fā)者通常為這個參數(shù)傳入NULL,表示采用默認的分配器摹恰。
第二個參數(shù)定義了字典的初始化大小辫继。它并不會限制字典的最大容量,只是向分配器提示了一開始應(yīng)該分配多少內(nèi)存俗慈。加入要創(chuàng)建的字典含有10個對象姑宽,那就向該參數(shù)傳入10。
最后兩個參數(shù)值得注意闺阱。它們定義了許多回調(diào)函數(shù)炮车,用于指示字典中的鍵和值在遇到各種事件時應(yīng)該執(zhí)行何種操作。這兩個參數(shù)都是指向結(jié)構(gòu)體的指針,二者所對應(yīng)的結(jié)構(gòu)體如下:
typedef struct {
CFIndex version;
CFDictionaryRetainCallBack retain;
CFDictionaryReleaseCallBack release;
CFDictionaryCopyDescriptionCallBack copyDescription;
CFDictionaryEqualCallBack equal;
CFDictionaryHashCallBack hash;
} CFDictionaryKeyCallBacks;
typedef struct {
CFIndex version;
CFDictionaryRetainCallBack retain;
CFDictionaryReleaseCallBack release;
CFDictionaryCopyDescriptionCallBack copyDescription;
CFDictionaryEqualCallBack equal;
} CFDictionaryValueCallBacks;
version參數(shù)目前應(yīng)設(shè)為0示血。當(dāng)前編程時總是取這個值棋傍,不過將來蘋果公司也許會修改此結(jié)構(gòu)體,所以要預(yù)留該值以表示版本號难审。這個參數(shù)可以用于檢測新版與舊版數(shù)據(jù)結(jié)構(gòu)之間是否兼容瘫拣。結(jié)構(gòu)體中的其余成員都是函數(shù)指針,它們定義了各種事件發(fā)生時應(yīng)該采用哪個函數(shù)來執(zhí)行相關(guān)任務(wù)告喊。比方說麸拄,如果字典中加入了新的鍵與值,那么就會調(diào)用retain函數(shù)黔姜。此參數(shù)的類型定義如下:
typedef const void * (*CFDictionaryRetainCallBack)
(CFAllocatorRef allocator,
const void *value);
由此可見retain是個函數(shù)指針拢切,其所指向的函數(shù)接受兩個參數(shù),其類型分別是CFAllocatorRef與const void 秆吵。傳給此函數(shù)的value參數(shù)表示即將加入字典中的鍵或值淮椰。而返回的void則表示要加到字典里的最終值。開發(fā)者可以用下列代碼實現(xiàn)這個回調(diào)函數(shù):
const void * CustomCallback(CFAllocatorRef allocator,
const void *value)
{
return value;
}
這么寫只是把將加入字典中的值照原樣返回纳寂。于是主穗,如果用它充當(dāng)retain回調(diào)函數(shù)來創(chuàng)建字典,那么該字典就不會“保留”鍵與值了毙芜。將此種寫法與無縫橋接搭配起來忽媒,就可以創(chuàng)建出特殊的NSDictionary對象,而其行為與用Objective-C創(chuàng)建出來的普通字典不同腋粥。
下面的代碼演示了這種字典的創(chuàng)建步驟:
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
const void* EOCRetainCallback(CFAllocatorRef allocator,
const void *value)
{
return value;
}
void EOCReleaseCallback(CFAllocatorRef allocator,
const void *value)
{
CFRelease(value);
}
CFDictionaryKeyCallBacks keyCallbacks = {
0,
EOCRetainCallback,
EOCReleaseCallback,
NULL,
CFEqual,
CFHash
};
CFDictionaryValueCallBacks valueCallbacks = {
0,
EOCRetainCallback,
EOCReleaseCallback,
NULL,
CFEqual
};
CFMutableDictionaryRef aCFDictionary =
CFDictionaryCreateMutable(NULL,
0,
&keyCallbacks,
&valueCallbacks);
NSMutableDictionary *anNSDictinary =
(__bridge_transfer NSMutableDictionary *)aCFDictionary;
在設(shè)定回調(diào)函數(shù)時晦雨,copyDescription取值為NULL,因為采用默認實現(xiàn)就很好隘冲,而equal與hash回調(diào)函數(shù)分別設(shè)為CFEqual與CFHash闹瞧,因為這二者所采用的做法與NSMutableDictionary的默認實現(xiàn)相。CFEqual最終會調(diào)用NSObject的“isEqual:”方法展辞,而CFHash則會調(diào)用hash方法夹抗。由此可以看出無縫橋接技術(shù)更為強大的一面。
鍵與值所對應(yīng)的retain與release回調(diào)函數(shù)指針分別指向EOCRetainCallback與EOCReleaseCallback函數(shù)腔寡。如果用作鍵的對象不支持拷貝操作浩销,此時就不能使用普通的NSMutableDictionary了,因為對象所屬的類不支持NSCopying協(xié)議,因為“copyWithZone:”方法未實現(xiàn)授帕。開發(fā)者可以直接在CoreFoundation層創(chuàng)建字典,于是就能修改內(nèi)存管理語義聋伦,對鍵執(zhí)行“保留”而非“拷貝”操作了绪爸。
要點:
- 通過無縫橋接技術(shù),可以在Foundation框架中的Objective-C對象與CoreFoundation框架中的C語言數(shù)據(jù)結(jié)構(gòu)之間來回轉(zhuǎn)換。
- 在CoreFoundation層面創(chuàng)建collection時画舌,可以指定許多回調(diào)函數(shù)堕担,這些函數(shù)表示此collection應(yīng)如何處理器元素。然后曲聂,可運用無縫橋接技術(shù)霹购,將其轉(zhuǎn)換成具備特殊內(nèi)存管理語義的Objective-C collection。
50.構(gòu)建緩存時選用NSCache而非NSDictionary
NSCache是Foundation框架專為處理緩存任務(wù)而設(shè)計的朋腋。
NSCache勝過NSDictionary之處在于齐疙,當(dāng)系統(tǒng)資源將要耗盡時,它可以自動刪減緩存旭咽。如果采用普通的字典贞奋,那么就要自己編寫掛鉤,在系統(tǒng)發(fā)出“低內(nèi)存”(low memory)通知時手工刪減緩存穷绵。而NSCache則會自動刪減轿塔,由于其是NSFoundation框架的一部分,所以與開發(fā)者相比仲墨,它能在更深的層面上插入掛鉤勾缭。此外,NSCache還會先行刪減”最久未使用的”對象宗收。若想自己編寫代碼來為字典添加此功能漫拭,則會十分復(fù)雜。
NSCache并不會“拷貝”鍵混稽,而是會“保留”它采驻。此行為用NSDictionary也可以實現(xiàn),然而需要編寫相當(dāng)復(fù)雜的代碼匈勋。NSCache對象不拷貝鍵的原因在于:很多時候礼旅,鍵都是由不支持拷貝操作的對象來充當(dāng)?shù)摹?/strong>因此,NSCache不會自動拷貝鍵洽洁,所以說痘系,在鍵不支持拷貝操作的情況下,該類用起來比字典更方便饿自。另外汰翠,NSCache是線程安全的。而NSDictionary則絕對不具備此優(yōu)勢昭雌,意思就是:在開發(fā)者自己不編寫加鎖代碼的前提下复唤,多個線程便可以同時訪問NSCache。對緩存來說烛卧,線程安全通常很重要佛纫,因為開發(fā)者可能要在某個線程中讀取數(shù)據(jù),此時如果發(fā)現(xiàn)緩存里找不到指定的鍵,那么就要下載該鍵所對應(yīng)的數(shù)據(jù)了呈宇。而下載完數(shù)據(jù)之后所要執(zhí)行的回調(diào)函數(shù)好爬,有可能會放在背景線程中運行,這樣的話甥啄,就等于是用另外一個線程來寫入緩存了存炮。
開發(fā)者可以操控緩存刪減其內(nèi)容的時機。有兩個與系統(tǒng)資源相關(guān)的尺度可供調(diào)整型豁,其一是緩存中的對象總數(shù)僵蛛,其二是所有對象的”總開銷“。開發(fā)者在將對象加入緩存時迎变,可為其指定”開銷值“充尉。當(dāng)對象總數(shù)或總開銷超過上限時,緩存就可能會刪減其中的對象了衣形,在可用的系統(tǒng)資源趨于緊張時驼侠,也會這么做。然而要注意谆吴,是”可能“會刪減某個對象倒源,而不是”一定“會刪減某個對象。刪減對象時所遵照的順序句狼,由具體實現(xiàn)來定笋熬。
向緩存中添加對象時,只有在能很快計算出”開銷值“的情況下腻菇,才應(yīng)該考慮采用這個尺度胳螟。若計算過程很復(fù)雜,那么照這種方式來使用緩存就達不到最佳效果了筹吐。
下面演示了緩存的用法:
//Network fetcher class
typedef void(^EOCNetworkFetcherCompletionHandler) (NSData *data);
@interface EOCNetworkFetcher : NSObject
-(instancetype)initWithURL:(NSURL *)url;
-(void)startWithCompletionHandler:
(EOCNetworkFetcherCompletionHandler)handler;
@end
@interface CacheTest()
{
NSCache *_cache;
}
@end
@implementation CacheTest
-(instancetype)init{
if(self = [super init]){
_cache = [NSCache new];
//Cache a maximum of 100 URLs
_cache.countLimit = 100;
//The size in bytes of data is used as the cost
_cache.totalCostLimit = 5*1025*1024;//5MB
}
return self;
}
-(void)downloadDataForURL:(NSURL *)url{
NSData *cachedData = [_cache objectForKey:url];
if(cachedData){
//Cache hit
[self useData:cachedData];
}else{
//Cache miss
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc]initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
[_cache setObject:data forKey:url cost:data.length];
[self useData:data];
}];
}
}
@end
本例中糖耸,下載數(shù)據(jù)所用的URL,就是緩存的鍵丘薛。若緩存未命中嘉竟,則下載數(shù)據(jù)并將其放入緩存。而數(shù)據(jù)的開銷值則為其長度洋侨。創(chuàng)建NSCache時舍扰,將其中可緩存的總對象數(shù)目上限設(shè)為100,將總開銷上限設(shè)置為5MB希坚。
還有個類叫NSPurgeableData边苹,和NSCache搭配起來用,效果很好吏够,此類事NSMutableData的子類,而且實現(xiàn)了NSDiscardableContent協(xié)議。如果某個對象所占的內(nèi)存能夠根據(jù)需要隨時丟棄锅知,那么就可以實現(xiàn)該協(xié)議所定義的接口播急。這就是說,當(dāng)系統(tǒng)資源緊張時售睹,可以把保存NSPurgeableData對象的那塊內(nèi)存釋放掉桩警。NSDiscardableContent協(xié)議里定義了名為isContentDiscarded的方法,可用來查詢相關(guān)內(nèi)存是否已釋放昌妹。
如果需要訪問某個NSPurgeableData對象捶枢,可以調(diào)用其beginContentAccess方法,告訴它現(xiàn)在還不應(yīng)丟棄自己所占的內(nèi)存飞崖。用完之后烂叔,調(diào)用endContentAccess方法,告訴它在必要時可以丟棄自己所占的內(nèi)存了固歪。這些調(diào)用可以嵌套蒜鸡,所以說,它們就像遞增與遞減引用計數(shù)所用的方法那樣牢裳。只有對象的”引用計數(shù)“為0時才可以丟棄逢防。
如果將NSPurgeableData對象加入NSCache,那么當(dāng)該對象為系統(tǒng)所丟棄時蒲讯,也會自動從緩存中移除忘朝。通過NSCache的evictsObjectsWithDiscardedContent屬性,可以開啟或關(guān)閉此功能判帮。
剛才那個例子可以用NSPurgeableData改寫如下:
-(void)downloadDataForURL:(NSURL *)url{
NSPurgeableData *cachedData = [_cache objectForKey:url];
if(cachedData){
//Stop the data being purged
[cachedData beginContentAccess];
//Use the cached data
[self useData:cachedData];
//Mark that the data may be purged again
[cachedData endContentAccess];
}else{
//Cache miss
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc]initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];
[_cache setObject:purgeableData forKey:url cost:data.length];
//Don't need to beginContentAccess as it begins
//with access already marked
//Use the retrieved data
[self useData:data];
//Mark that the data may be purged now
[purgeableData endContentAccess];
}];
}
}
注意局嘁,創(chuàng)建好NSPurgeableData對象之后,其”purge引用計數(shù)“會多1脊另,所以無須再調(diào)用beginContentAccess了导狡,然而氣候必須調(diào)用endContentAccess,將多出來的這個”1“抵消掉偎痛。
要點:
- 實現(xiàn)緩存時應(yīng)選用NSCache而非NSDictionary對象旱捧。因為NSCache可以提供優(yōu)雅的自動刪減功能,而且是“線程安全的”踩麦,此外枚赡,它與字典不同,并不會拷貝鍵谓谦。
- 可以給NSCache對象設(shè)置上限贫橙,用以限制緩存中的對象總個數(shù)及“總成本“,而這些尺度則定義了緩存刪減其中對象的時機反粥。但是絕對不要把這些尺度當(dāng)成可靠的”硬限制“(hard limit)卢肃,它們僅對NSCache起指導(dǎo)作用疲迂。
- 將NSPurgeableData于NSCache搭配使用,可實現(xiàn)自動清除數(shù)據(jù)的功能莫湘,也就是說尤蒿,當(dāng)NSPurgeableData對象所占內(nèi)存為系統(tǒng)所丟棄時,該對象自身也會從緩存中移除幅垮。
- 如果緩存使用得當(dāng)腰池,那么應(yīng)用程序的響應(yīng)速度就能提高。只有那種”重新計算起來很費事的“數(shù)據(jù)忙芒,才值得放入緩存示弓,比如那些需要從網(wǎng)絡(luò)獲取或從磁盤讀取的數(shù)據(jù)。
51.精簡initialize與load的實現(xiàn)代碼
有時候呵萨,類必須先執(zhí)行某些初始化操作奏属,然后才能正常使用。NSObject類有兩個方法甘桑,可用來實現(xiàn)這種初始化操作拍皮。
load方法原型:
+ (void)load;
對于加入運行期系統(tǒng)中的每個類及分類來說,必定會調(diào)用此方法跑杭,而且僅調(diào)用一次铆帽。當(dāng)包含類或分類的程序庫載入系統(tǒng)時,就會執(zhí)行此方法德谅,而這通常就是指應(yīng)用程序啟動的時候爹橱,若程序是為iOS平臺設(shè)計的,則肯定會在此時執(zhí)行窄做。Mac OS X應(yīng)用程序更自由一些愧驱,它們可以使用”動態(tài)加載“之類的特性,等應(yīng)用程序啟動好之后再去加載程序庫椭盏。如果分類和其他所屬的類都定義了load方法组砚,則先調(diào)用類里的,再調(diào)用分類里的掏颊。
load方法的問題在于糟红,執(zhí)行該方法時,運行期系統(tǒng)處于”脆弱狀態(tài)“乌叶。在執(zhí)行子類的load方法之前盆偿,必定會先執(zhí)行所有超類的load方法,而如果代碼還依賴了其他程序庫准浴,那么程序庫里相關(guān)類的load方法也必定會先執(zhí)行事扭。然而,根據(jù)某個給定的程序庫乐横,卻無法判斷出其中各個類的載入順序求橄。因此今野,在load方法中使用其他類是不安全的。
比方說:
#import <Foundation/Foundation.h>
#import "EOCClassA.h"http:// From the same library
@interface EOCClassB : NSObject
@end
@implementation EOCClassB
+(void)load
{
NSLog(@"Loading EOCClassB");
EOCClassA *object = [EOCClassA new];
//Use 'object'
}
@end
此處使用NSLog沒問題罐农,而且相關(guān)字符串也會照常記錄腥泥,因為Foundation框架肯定在運行l(wèi)oad方法之前就已經(jīng)載入系統(tǒng)了。但是啃匿,在EOCClassB的load方法里使用EOCClassA卻不太安全,因為無法確定在執(zhí)行EOCClassB的load方法之前蛆楞,EOCClassA是不是已經(jīng)記載好了溯乒。可以想見:EOCClassA這個類,也許會在其load方法中執(zhí)行某些重要操作豹爹,只有執(zhí)行完這些操作之后裆悄,該類實例才能正常使用。
有個重要的事情需注意臂聋,那就是load方法并不像普通的方法那樣光稼,它并不遵從那套繼承規(guī)則,如果某個類本身沒實現(xiàn)load方法孩等,那么不管其各級超類是否實現(xiàn)此方法艾君,系統(tǒng)都不會調(diào)用。此外肄方,分類和其所屬的類里冰垄,都可能出現(xiàn)load方法。此時兩種實現(xiàn)代碼都會調(diào)用权她,類的實現(xiàn)要比分類的實現(xiàn)先執(zhí)行虹茶。
而且load方法務(wù)必實現(xiàn)得精簡一些,也就是要盡量減少其所執(zhí)行的操作隅要,因為整個應(yīng)用程序在執(zhí)行l(wèi)oad方法時都會阻塞蝴罪。如果load方法中包含繁雜的代碼,那么應(yīng)用程序在執(zhí)行期間就會變得無響應(yīng)步清。不要在里面等待鎖要门,也不要調(diào)用可能會加鎖的方法∧岱龋總之暂衡,能不做的事情就別做。
想執(zhí)行與類相關(guān)的初始化操作崖瞭,還有個辦法狂巢,就是覆寫下列方法:
+ (void)initialize;
對于每個類來說,該方法會在程序首次用該類之前調(diào)用书聚,且只調(diào)用一次唧领。它是由運行期系統(tǒng)來調(diào)用的藻雌,絕不應(yīng)該通過代碼直接調(diào)用。其雖與load相似斩个,但卻有幾個非常重要的微妙區(qū)別胯杭。首先,它是”惰性調(diào)用的“受啥,也就是說做个,只有當(dāng)程序用到了相關(guān)的類時,才會調(diào)用滚局。因此居暖,如果某個類一直都沒有使用,那么其initialize方法就一直不會運行藤肢。這也就是說太闺,應(yīng)用程序無須先把每個類的initialize都執(zhí)行一遍,這與load方法不同嘁圈,對于load來說省骂,應(yīng)用程序必須則色并等著所有類的load都執(zhí)行完,才能繼續(xù)最住。
此方法與load還有個區(qū)別钞澳,就是運行期系統(tǒng)在執(zhí)行該方法時,是處于正常狀態(tài)的涨缚,因此略贮,從運行期系統(tǒng)完整度上來講,此時可以安全使用并調(diào)用任意類中的任意方法仗岖。而且逃延,運行期系統(tǒng)也能保證initialize方法一定會在”線程安全的環(huán)境“中執(zhí)行,這就是說轧拄,只有執(zhí)行initialize的那個線程可以操作類或類的實例揽祥。其他線程都要先則色,等著initialize執(zhí)行完檩电。
最后一個區(qū)別是:initialize方法與其他消息一樣拄丰,如果某個類未實現(xiàn)它,而其超類實現(xiàn)了俐末,那么就會運行超類的實現(xiàn)代碼料按。
initialize也遵循通常的繼承規(guī)則,通常都會這么來實現(xiàn)initialize方法:
+(void)initialize
{
if(self = [EOCBaseClass class])
{
NSLog(@"%@ initialized",self);
}
}
加上這條檢測語句之后卓箫,只有當(dāng)開發(fā)者所期望的那個類載入系統(tǒng)時载矿,才會執(zhí)行相關(guān)的初始化操作。
在load和initialize方法中盡量精簡代碼烹卒,在里面設(shè)置一些狀態(tài)闷盔,使本類能夠正常運作就可以了弯洗,不要執(zhí)行那種耗時太久或需要加鎖的任務(wù)。
開發(fā)者無法控制類的初始化時機逢勾。類在首次使用之前牡整,肯定要初始化,但編寫程序時不能令代碼依賴特定的時間點溺拱,否則會很危險逃贝。運行期系統(tǒng)將來更新了之后,可能會略微改變類的初始化方式迫摔,這樣的話秋泳,開發(fā)者原來如果假設(shè)某個類必定會在某個具體時間點初始化,那么現(xiàn)在這條假設(shè)可能就不成立了攒菠。
如果某個類的實現(xiàn)代碼很復(fù)雜,那么其中可能會直接或間接用到其他類歉闰。若那些類尚未初始化辖众,則系統(tǒng)會迫使其初始化。然而和敬,本類的初始化方法此時尚未運行完畢凹炸。其他類在運行其initialize方法時,有可能會依賴本類中的某些數(shù)據(jù)昼弟,而這些數(shù)據(jù)此時也許還未初始化好啤它。
若某個全局狀態(tài)無法在編譯期初始化,則可以在initialize里來做:
#import "EOCClass.h"
static const int kInterval = 10;
static NSMutableArray *kSomeObjects;
+(void)initialize
{
if(self = [EOCClass class])
{
kSomeObjects = [NSMutableArray new];
}
}
要點:
- 在加載階段舱痘,如果類實現(xiàn)了load方法变骡,那么系統(tǒng)就會調(diào)用它。分類里也可以定義此方法芭逝,類的load方法要比分類中的先調(diào)用塌碌。與其他方法不同,load方法不參與覆寫機制旬盯。
- 首次使用某個類之前台妆,系統(tǒng)會向其發(fā)送initialize消息。由于此方法遵從普通的覆寫規(guī)則胖翰,所以通常應(yīng)該在里面判斷當(dāng)前要初始化的是哪個類接剩。
- load與initialize方法都應(yīng)該實現(xiàn)得精簡一些,這有助于保持應(yīng)用程序的響應(yīng)能力萨咳,也能減少引入”依賴環(huán)“(interdependency cycle)的幾率懊缺。
- 無法在編譯器設(shè)定的全局變量,可以放在initialize方法里初始化培他。
52.別忘了NSTimer回保留其目標對象
計時器要和“運行循環(huán)”(run loop)相關(guān)聯(lián)桐汤,運行循環(huán)到時候會觸發(fā)任務(wù)而克。創(chuàng)建NSTimer時,可以將其“預(yù)先安排”在當(dāng)前的運行循環(huán)中怔毛,也可以先創(chuàng)建好员萍,然后由開發(fā)者自己來調(diào)度。無論采用哪種方式拣度,只有把計時器放在運行循環(huán)里碎绎,它才能正常觸發(fā)任務(wù)。
例如下面這個方法可以創(chuàng)建計時器抗果,并將其預(yù)先安排在當(dāng)前運行循環(huán)中:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
要點:
- NSTimer對象會保留其目標筋帖,直到計時器本身失效為止,調(diào)用invalidate方法可令計時器失效冤馏,另外日麸,一次性的計時器在觸發(fā)完任務(wù)之后也會失效。
- 反復(fù)執(zhí)行任務(wù)的計時器逮光,很容易引入保留環(huán)代箭,如果這種計時器的目標對象又保留了計時器本身,那肯定會導(dǎo)致保留環(huán)涕刚。這種環(huán)狀保留關(guān)系嗡综,可能是直接發(fā)生的,也可能是通過對象圖里的其他對象間接發(fā)生的杜漠。
- 可以擴充NSTimer的功能极景,用“塊”來打破保留環(huán)。不過驾茴,除非NSTimer將來在公共接口里提供此功能盼樟,否則必須創(chuàng)建分類,將相關(guān)實現(xiàn)代碼加入其中锈至。
寫在最后:
到此恤批,終于讀完了《Effective Objective-C 2.0》這本書,其實從去年就計劃讀這本書了裹赴,因為趕項目就沒抽出時間好好讀書喜庞。在閱讀過程中發(fā)現(xiàn),書中的有些知識點是自己知道的棋返;有些是自己了解一點點并知道如何用延都,但是不知道為什么可以這么用的;還有些是自己平常沒注意或者沒用過的知識點睛竣。有時候讀到某個知識點晰房,然后聯(lián)想到自己曾經(jīng)使用過程中遇到的問題或者不理解的地方,就有種豁然開朗的感覺,這種感覺真的很棒殊者、很興奮与境。這種興奮的感覺可以激發(fā)自己繼續(xù)閱讀下去的欲望,真是不得不佩服作者在iOS開發(fā)上的造詣猖吴。同時摔刁,也認識到了自己的不足,以后還得更加努力海蔽。最后共屈,非常感覺作者Matt Galloway為我們分享了他的這些開發(fā)經(jīng)驗。
轉(zhuǎn)載請注明出處:第七章 系統(tǒng)框架
參考:《Effective Objective-C 2.0》