久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

第七章 系統(tǒng)框架

 快樂成群m5ct4t 2017-09-23

第七章 系統(tǒng)框架

          
作者 Code_Ninja 關(guān)注
                

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)庫,,其中包括但不限于下面列出的這些:

  • 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è)備上的音頻文件,。這個框架屬于比較難用的,因為音頻處理本身就很復雜,。所幸由這套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í)行速度。當然,,由于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ò)功能。

要點:

  • 許多系統(tǒng)框架都可以直接使用,。其中最重要的是Foundation與CoreFoundation,,這兩個框架提供了構(gòu)建應(yīng)用程序所需的許多核心功能。
  • 很多常見任務(wù)都能用框架來做,,例如音頻與視頻處理,、網(wǎng)絡(luò)通信、數(shù)據(jù)管理等,。
  • 請記?。河眉僀寫成的框架與用Objective-C寫成的一樣重要,若想要成為優(yōu)秀的Objective-C開發(fā)者,,應(yīng)該掌握C語言的核心概念,。

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中的每個元素都可能會放在塊里運行一遍,,這種做法通常會大幅度簡化編碼過程,。

  • 使用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)遍歷操作更為高效了,。

  • 基于塊的遍歷方式

在當前的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í)行各輪迭代,也就是說,,如果當前系統(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*,,并且想令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)存管理語義:

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)存,而分配器負責分配及回收這些內(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。當前編程時總是取這個值,,不過將來蘋果公司也許會修改此結(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ù)的類型定義如下:

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;
}

這么寫只是把將加入字典中的值照原樣返回。于是,,如果用它充當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之處在于,,當系統(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)該考慮采用這個尺度,。若計算過程很復雜,那么照這種方式來使用緩存就達不到最佳效果了,。

下面演示了緩存的用法:

//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é)議所定義的接口,。這就是說,,當系統(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改寫如下:

-(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ù)及“總成本“,,而這些尺度則定義了緩存刪減其中對象的時機。但是絕對不要把這些尺度當成可靠的”硬限制“(hard limit),,它們僅對NSCache起指導作用,。
  • 將NSPurgeableData于NSCache搭配使用,可實現(xiàn)自動清除數(shù)據(jù)的功能,,也就是說,,當NSPurgeableData對象所占內(nèi)存為系統(tǒ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)用一次,。當包含類或分類的程序庫載入系統(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)用可能會加鎖的方法,??傊懿蛔龅氖虑榫蛣e做,。

想執(zhí)行與類相關(guān)的初始化操作,,還有個辦法,就是覆寫下列方法:

+ (void)initialize;

對于每個類來說,,該方法會在程序首次用該類之前調(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方法:

+(void)initialize
{
    if(self = [EOCBaseClass class])
    {
        NSLog(@"%@ initialized",self);
    }
}

加上這條檢測語句之后,只有當開發(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里來做:

  #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)該在里面判斷當前要初始化的是哪個類。
  • 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時,,可以將其“預先安排”在當前的運行循環(huán)中,也可以先創(chuàng)建好,,然后由開發(fā)者自己來調(diào)度。無論采用哪種方式,,只有把計時器放在運行循環(huán)里,,它才能正常觸發(fā)任務(wù)。

例如下面這個方法可以創(chuà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ù)之后也會失效。
  • 反復執(zhí)行任務(wù)的計時器,,很容易引入保留環(huán),,如果這種計時器的目標對象又保留了計時器本身,那肯定會導致保留環(huán),。這種環(huán)狀保留關(guān)系,,可能是直接發(fā)生的,也可能是通過對象圖里的其他對象間接發(fā)生的,。
  • 可以擴充NSTimer的功能,,用“塊”來打破保留環(huán)。不過,,除非NSTimer將來在公共接口里提供此功能,,否則必須創(chuàng)建分類,將相關(guān)實現(xiàn)代碼加入其中。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多