第七章 系統(tǒng)框架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ǔ)核心功能,而且還提供了字符串處理這樣的復雜功能,。比方說,,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ù)是用某些相當復雜的代碼實現(xiàn)出來的,,這些代碼可以使運行期系統(tǒng)把CoreFoundation框架中的對象視為普通的Objective-C對象。 除了Foundation與CoreFoundation之外,,還有很多系統(tǒng)庫,,其中包括但不限于下面列出的這些:
Objective-C編程時會經(jīng)常需要使用底層的C語言級API。用C語言來實現(xiàn)API的好處是,,可以繞過Objective-C的運行期系統(tǒng),,從而提升執(zhí)行速度。當然,,由于ARC只負責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ò)功能。 要點:
48.多用塊枚舉,,少用for循環(huán)在編程中經(jīng)常需要列舉collection中的元素,,當前的Objective-C語言有很多種辦法實現(xiàn)此功能,可以用標準的C語言循環(huán),,也可以用Objective-C 1.0的NSEnumerator以及Objective-C 2.0的快速遍歷,。語言中引入“塊”這一特性后,又多出來幾種新的遍歷方式,,采用這幾種新方式遍歷collection時,,可以傳入塊,而collection中的每個元素都可能會放在塊里運行一遍,,這種做法通常會大幅度簡化編碼過程,。
NSEnumerator是個抽象基類,其中只定義了兩個方法,,供其具體子類來實現(xiàn):
其中關(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é)議只定義了一個方法:
該方法允許類實例同時返回多個對象,這就使得循環(huán)遍歷操作更為高效了,。
在當前的Objective-C語言中,,最新引入的一種做法就是基于塊來遍歷。NSArray中定義了下面這個方法,,它可以實現(xiàn)最基本的遍歷功能:
此方法提供了一種優(yōu)雅的機制,,用于終止遍歷操作,開發(fā)者可以通過設(shè)定stop變量值來實現(xiàn),。
NSEnumerationOptions類型是個enum,,其各種取值可用“按位或”連接,用以表明遍歷方式,。例如,,開發(fā)者可以請求以并發(fā)方式執(zhí)行各輪迭代,也就是說,,如果當前系統(tǒng)資源狀況允許,,那么執(zhí)行每次迭代所用的塊就可以并行執(zhí)行了。通過NSEnumerationConcurrent選項即可開啟此功能,。如果使用此選項,,那么底層會通過GCD來處理并發(fā)執(zhí)行事宜,具體實現(xiàn)時很可能會用到dispatch group,。反向遍歷是通過NSEnumerationReverse選項來實現(xiàn)的,。 總體來看,塊枚舉法擁有其他遍歷方式都具備的又是,,而且還能帶來更多好處,。與快速遍歷法相比,它更多用一些代碼,,可是卻能提供遍歷時所針對的下標,,在遍歷字典時也能同時提供鍵與值,而且還有選項可以開啟并發(fā)迭代功能,。 要點:
49.對自定義其內(nèi)存管理語義的collection使用無縫橋接使用“無縫橋接”技術(shù),可以在定義于Foundation框架中的Objective-C類和定義與CoreFoundation框架中的C數(shù)據(jù)結(jié)構(gòu)之間互相轉(zhuǎn)換,。 下列代碼演示了簡單的無縫橋接:
轉(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*,,并且想令ARC獲得對象所有權(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)存管理語義:
首個參數(shù)表示將要使用的內(nèi)存分配器,。CoreFoundation對象里的數(shù)據(jù)結(jié)構(gòu)需要占用內(nèi)存,而分配器負責分配及回收這些內(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)體如下:
version參數(shù)目前應(yīng)設(shè)為0。當前編程時總是取這個值,,不過將來蘋果公司也許會修改此結(jié)構(gòu)體,,所以要預留該值以表示版本號。這個參數(shù)可以用于檢測新版與舊版數(shù)據(jù)結(jié)構(gòu)之間是否兼容,。結(jié)構(gòu)體中的其余成員都是函數(shù)指針,,它們定義了各種事件發(fā)生時應(yīng)該采用哪個函數(shù)來執(zhí)行相關(guān)任務(wù)。比方說,,如果字典中加入了新的鍵與值,,那么就會調(diào)用retain函數(shù)。此參數(shù)的類型定義如下:
由此可見retain是個函數(shù)指針,,其所指向的函數(shù)接受兩個參數(shù),,其類型分別是CFAllocatorRef與const void 。傳給此函數(shù)的value參數(shù)表示即將加入字典中的鍵或值,。而返回的void則表示要加到字典里的最終值,。開發(fā)者可以用下列代碼實現(xiàn)這個回調(diào)函數(shù):
這么寫只是把將加入字典中的值照原樣返回。于是,,如果用它充當retain回調(diào)函數(shù)來創(chuàng)建字典,,那么該字典就不會“保留”鍵與值了。將此種寫法與無縫橋接搭配起來,,就可以創(chuàng)建出特殊的NSDictionary對象,,而其行為與用Objective-C創(chuàng)建出來的普通字典不同。 下面的代碼演示了這種字典的創(chuàng)建步驟:
在設(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í)行“保留”而非“拷貝”操作了。 要點:
50.構(gòu)建緩存時選用NSCache而非NSDictionaryNSCache是Foundation框架專為處理緩存任務(wù)而設(shè)計的。 NSCache勝過NSDictionary之處在于,,當系統(tǒng)資源將要耗盡時,,它可以自動刪減緩存。如果采用普通的字典,,那么就要自己編寫掛鉤,,在系統(tǒng)發(fā)出“低內(nèi)存”(low memory)通知時手工刪減緩存。而NSCache則會自動刪減,,由于其是NSFoundation框架的一部分,,所以與開發(fā)者相比,它能在更深的層面上插入掛鉤,。此外,,NSCache還會先行刪減”最久未使用的”對象。若想自己編寫代碼來為字典添加此功能,,則會十分復雜,。 NSCache并不會“拷貝”鍵,而是會“保留”它,。此行為用NSDictionary也可以實現(xiàn),然而需要編寫相當復雜的代碼,。NSCache對象不拷貝鍵的原因在于:很多時候,,鍵都是由不支持拷貝操作的對象來充當?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ā)者在將對象加入緩存時,,可為其指定”開銷值“,。當對象總數(shù)或總開銷超過上限時,緩存就可能會刪減其中的對象了,,在可用的系統(tǒng)資源趨于緊張時,,也會這么做。然而要注意,,是”可能“會刪減某個對象,,而不是”一定“會刪減某個對象。刪減對象時所遵照的順序,,由具體實現(xiàn)來定,。 向緩存中添加對象時,只有在能很快計算出”開銷值“的情況下,,才應(yīng)該考慮采用這個尺度,。若計算過程很復雜,那么照這種方式來使用緩存就達不到最佳效果了,。 下面演示了緩存的用法:
本例中,,下載數(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é)議所定義的接口,。這就是說,,當系統(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,,那么當該對象為系統(tǒng)所丟棄時,也會自動從緩存中移除,。通過NSCache的evictsObjectsWithDiscardedContent屬性,,可以開啟或關(guān)閉此功能。 剛才那個例子可以用NSPurgeableData改寫如下:
注意,,創(chuàng)建好NSPurgeableData對象之后,,其”purge引用計數(shù)“會多1,所以無須再調(diào)用beginContentAccess了,,然而氣候必須調(diào)用endContentAccess,,將多出來的這個”1“抵消掉,。 要點:
51.精簡initialize與load的實現(xiàn)代碼有時候,,類必須先執(zhí)行某些初始化操作,,然后才能正常使用。NSObject類有兩個方法,,可用來實現(xiàn)這種初始化操作,。 load方法原型:
對于加入運行期系統(tǒng)中的每個類及分類來說,必定會調(diào)用此方法,,而且僅調(diào)用一次,。當包含類或分類的程序庫載入系統(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方法中使用其他類是不安全的。 比方說:
此處使用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)用可能會加鎖的方法,??傊懿蛔龅氖虑榫蛣e做,。 想執(zhí)行與類相關(guān)的初始化操作,,還有個辦法,就是覆寫下列方法:
對于每個類來說,,該方法會在程序首次用該類之前調(diào)用,且只調(diào)用一次,。它是由運行期系統(tǒng)來調(diào)用的,,絕不應(yīng)該通過代碼直接調(diào)用。其雖與load相似,,但卻有幾個非常重要的微妙區(qū)別,。首先,它是”惰性調(diào)用的“,,也就是說,,只有當程序用到了相關(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方法:
加上這條檢測語句之后,只有當開發(fā)者所期望的那個類載入系統(tǒng)時,,才會執(zhí)行相關(guān)的初始化操作,。 在load和initialize方法中盡量精簡代碼,在里面設(shè)置一些狀態(tài),,使本類能夠正常運作就可以了,,不要執(zhí)行那種耗時太久或需要加鎖的任務(wù)。 開發(fā)者無法控制類的初始化時機,。類在首次使用之前,,肯定要初始化,但編寫程序時不能令代碼依賴特定的時間點,,否則會很危險,。運行期系統(tǒng)將來更新了之后,可能會略微改變類的初始化方式,,這樣的話,,開發(fā)者原來如果假設(shè)某個類必定會在某個具體時間點初始化,那么現(xiàn)在這條假設(shè)可能就不成立了,。 如果某個類的實現(xiàn)代碼很復雜,,那么其中可能會直接或間接用到其他類。若那些類尚未初始化,,則系統(tǒng)會迫使其初始化,。然而,本類的初始化方法此時尚未運行完畢,。其他類在運行其initialize方法時,,有可能會依賴本類中的某些數(shù)據(jù),而這些數(shù)據(jù)此時也許還未初始化好,。 若某個全局狀態(tài)無法在編譯期初始化,,則可以在initialize里來做:
要點:
52.別忘了NSTimer回保留其目標對象計時器要和“運行循環(huán)”(run loop)相關(guān)聯(lián),運行循環(huán)到時候會觸發(fā)任務(wù),。創(chuàng)建NSTimer時,,可以將其“預先安排”在當前的運行循環(huán)中,也可以先創(chuàng)建好,,然后由開發(fā)者自己來調(diào)度。無論采用哪種方式,,只有把計時器放在運行循環(huán)里,,它才能正常觸發(fā)任務(wù)。 例如下面這個方法可以創(chuàng)建計時器,,并將其預先安排在當前運行循環(huán)中:
要點:
|
|
來自: 快樂成群m5ct4t > 《待分類》