[編寫高品質iOS代碼的52個有效方法](十一)系統架構

來源:互聯網
上載者:User

標籤:

[編寫高品質iOS代碼的52個有效方法](十一)系統架構

參考書籍:《Effective Objective-C 2.0》 【英】 Matt Galloway

先睹為快

47.熟悉系統架構

48.多用塊枚舉,少用for迴圈

49.對自訂其記憶體管理語義的容器使用無縫橋接

50.構建緩衝時選用NSCache而非NSDictionary

51.精簡initialize與load的實現代碼

52.別忘了NSTimer會保留其目標對象

目錄

  • 編寫高品質iOS代碼的52個有效方法十一系統架構
    • 先睹為快
    • 目錄
    • 第47條熟悉系統架構
    • 第48條多用塊枚舉少用for迴圈
    • 第49條對自訂其記憶體管理語義的容器使用無縫橋接
    • 第50條構建緩衝時選用NSCache而非NSDictionary
    • 第51條精簡initialize與load的實現代碼
    • 第52條別忘了NSTimer會保留其目標對象

第47條:熟悉系統架構

將一系列代碼封裝為動態庫,並在其中放入描述其介面的標頭檔,這樣做出來的東西就叫架構。

開發人員會碰到的主要架構就是Foundation,像是NSObject、NSArray、NSDictionary等類都在其中。Foundation架構中的類都使用NS首碼(表示NeXTSTEP作業系統,Mac OS X的基礎)

還有個與Foundation相伴的架構,叫CoreFoundation。其中有很多對應Foundation架構中功能的C語言API。CoreFoundation中的C語言資料結構可以與Foundation架構中的Objective-C對象無縫橋接。

除此之外還有以下常用架構:

CFNetwork 提供C語言層級的網路通訊能力

CoreAudio 操作裝置音頻硬體的C語言API

AVFoundation 提供Objective-C對象來回訪並錄製音頻及視頻

CoreData 提供Objective-C介面將對象放入資料庫,便於持久儲存

CoreText 可以高效執行文字排版及渲染操作的C語言介面

AppKit/UIKit Mac OS X/iOS應用程式的UI架構

用純C語言寫成的架構與用Objective-C寫成的一樣重要,若想成為優秀的Objective-C開發人員,應該掌握C語言的核心概念。

第48條:多用塊枚舉,少用for迴圈

在編程中經常需要列舉容器中的元素,當前Objective-C語言有多種辦法實現此功能,首先是老式的for迴圈。

NSArray *array = /* ... */;for (int i = 0; i < array.count; i++) {    id object = array[i];    // Do something with ‘object‘}NSDictionary *dictionary = /* ... */;NSArray *keys = [dictionary allKeys];for (int i = 0; i < keys.count; i++) {    id key = keys[i];    id value = dictionary[key];    // Do something with ‘key‘ and ‘value‘}

這是最基本的方法,因而功能非常有限。由於字典和set都是無序的,所以遍曆它們需要額外建立一個數組(本例中為keys)。

第二種方法是使用NSEnumerator抽象基類來遍曆

NSArray *array = /* ... */;NSEnumerator *enumerator = [array objectEnumerator];id object;while ((object = [enumerator nextObject]) != nil) {        // Do something with ‘object‘}NSDictionary *dictionary = /* ... */;NSEnumerator *enumerator = [dictionary keyEnumerator];id key;while ((key = [enumerator nextObject]) != nil) {    id value = dictionary[key];    // Do something with ‘key‘ and ‘value‘}

這種方法與標準for迴圈相比,優勢在於無論遍曆哪種容器,文法都十分類似,如果需要反向遍曆,也可以擷取反向列舉程式。

NSArray *array = /* ... */;NSEnumerator *enumerator = [array reverseObjectEnumerator];

Objective-C 2.0引入了快速遍曆。與使用NSEnumerator類似,而文法更簡潔,它為for迴圈開始了in關鍵字。

NSArray *array = /* ... */;for (id object in array){    // Do something with ‘object‘}NSDictionary *dictionary = /* ... */;for (id key in dictionary){    id value = dictionary[key];    // Do something with ‘key‘ and ‘value‘}

如果某個類的對象支援快速對象,只需要遵守NSFastEnumeration協議,該協議只定義了一個方法:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerarionState*)state object:(id*)stackbuffer count:(NSUInteger)length

由於NSEnumerator也實現了NSFastEnumeration協議,所以反向遍曆可以這樣實現:

NSArray *array = /* ... */;for (id object in [array reverseObjectEnumerator]){    // Do something with ‘object‘}

這種方法允許類執行個體同時返回多個對象,使迴圈更高效。但缺點有兩個,一是遍曆字典時不能同時擷取鍵和值,需要多一步操作,二是此方法無法輕鬆擷取當前遍曆操作所針對的下標(有可能會用到)。

最後一種方法是基於塊的遍曆,也是最新的方法

NSArray *array;[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {    // Do something with ‘object‘    if (shouldStop) {        *stop = YES;    }}];NSDictionary *dictionary;[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {    // Do something with ‘key‘ and ‘value‘    if (shouldStop) {        *stop = YES;    }}];

此方式的優勢在於,遍曆時可以直接從塊裡擷取更多資訊,並且能夠通過修改塊的方法名,避免進行類型轉換操作。若已知字典中的對象必為字串:

NSDictionary *dictionary;[dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {    // Do something with ‘key‘ and ‘value‘}];

當然,此方法也可以傳入選項掩碼來執行反向遍曆

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {        // Do something with ‘object‘    }];

在options處傳入NSEnumerationConcurrent,可開啟並存執行功能,通過底層GCD來實現並處理。

第49條:對自訂其記憶體管理語義的容器使用無縫橋接

無縫橋接可以實現Foundation架構中的類和CoreFoundation架構中的資料結構之間的互相轉換。下面是一個簡單的無縫橋接:

NSArray *aNSArray = @[@1,@2,@3];CFArrayRef aCFArray = (__bridge CFArrayRef)aNSArray;CFRelease(aCFArray);

進行轉換操作的修飾符共有3個:

__bridge // 不改變對象的原所有權__bridge_retained // ARC交出對象的所有權,手動管理記憶體__bridge_transfer // ARC獲得對象的所有權,自動管理記憶體

手動管理記憶體的對象需要用CFRetain與CFRelease來保留或釋放。

第50條:構建緩衝時選用NSCache而非NSDictionary

開發iOS程式時,有些程式員會將網際網路上下載的圖片儲存到字典中,這樣的話稍後使用就無須再次下載了,其實用NSCache類更好,它是Foundation架構專門為處理這種任務而設計的。

NSCache勝於NSDictionary之處在於,當系統資源將要耗盡時,它可以自動刪除最久未使用的緩衝。NSCache並不會拷貝鍵,而是保留它,在鍵不支援拷貝操作的情況下,使用更方便。另外NSCache是安全執行緒的,不需要編寫加鎖代碼的情況下,多個線程也可以同時訪問NSCache。

下面是緩衝的用法

#import <Foundation/Foundation.h>// 網路資料擷取器類typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data);@interface EOCNetworkFetcher : NSObject- (id)initWithURL:(NSURL*)url;- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler;@end// 使用擷取器及緩衝結果的類@interface EOCClass : NSObject@end@implementation EOCClass{    NSCache *_cache;}- (id)init{    if ((self = [super init])) {        _cache = [NSCache new];        // 設定緩衝的對象數目上限為100,總開銷上限為5MB        _cache.countLimit = 100;        _cache.totalCostLimit = 5 * 1024 * 1024;    }    return self;}- (void)downloadDataForURL:(NSURL*)url{    // NSPurgeableData為NSMutableData的子類,採用與記憶體管理類似的引用計數,當引用計數為0時,該對象佔用的記憶體可以根據需要隨時丟棄    NSPurgeableData *cacheData = [_cache objectForKey:url];    if (cacheData) {        // 快取命中        // 引用計數+1        [cacheData beginContentAccess];        // 使用快取資料        [self useData:cacheData];        // 引用計數-1        [cacheData endContentAccess];    }else{        // 緩衝未命中        EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];        [fetcher startWithCompletionHandler:^(NSData *data) {            // 建立NSPurgeableData對象,引用計數+1            NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];            [_cache setObject:purgeableData forKey:url cost:purgeableData.length];            // 使用快取資料            [self useData:cacheData];             // 引用計數-1            [purgeableData endContentAccess];        }];    }}@end
第51條:精簡initialize與load的實現代碼

有時候類必須先執行某些初始化操作,然後才能正常使用。在Objective-C中,絕大多數類都繼承自NSObject這個根類,而該類有兩個方法可以用來實現這種初始化操作。首先是load方法:

+ (void)load

加入運行期系統中的每個類及分類,都會調用此方法,而且僅調用一次。在iOS中,這類方法會在應用程式啟動時執行(Mac OS X中可以使用動態載入,程式啟動之後再載入)。在執行load方法時,是先執行超類的load方法,再執行子類的,先執行類的,再執行其所屬分類的。如果代碼還依賴了其他程式庫,則會有限執行該程式庫中的load方法。但在給定的某個程式庫中,無法判斷出各個類的載入順序。

#import <Foundation/Foundation.h>#import "EOCClassA.h" // 來自同一個庫@interface EOCClassB : NSObject@end@implementation EOCClassB+ (void)load{    NSLog(@"Loading EOCClassB");    EOCClassA *object = [EOCClassA new];    // ues object}@end

這段代碼不安全,因為無法確定EOCClassA已在執行EOCClassB load方法時已經載入好了。

load方法不遵從普通方法的繼承規則,如果某個類本身沒實現load方法,那麼不管其超類是否實現此方法,系統都不會調用。

load方法應該盡量精簡,因為整個程式執行load方法時都會阻塞。不要在裡面等待鎖,也不要調用可能會加鎖的方法。總之,能不做的事情就別做。

想要執行與類相關的初始化操作,還有個方法,就是重寫下列方法

+ (void)initialize

對於每個類來說,該方法會在程式首次調用該類之前調用,而且只調用一次。initialize與load方法主要有3個區別:
1. initialize方法只有當程式用到了相關類才會調用,而load不同,程式必須阻塞並等所有類的load都執行完畢,才能繼續。
2. 運行期系統執行initialize方法時,處於正常狀態,而不是阻塞狀態。為保證安全執行緒,只會阻塞其他動作該類或類執行個體的線程。
3. 如果某個類未實現initialize方法,而超類實現了它,那麼就會運行超類的方法。

initialize方法也應當盡量精簡,只需要在裡面設定一些狀態,使本類能夠正常運作就可以了,不要執行那種耗時太久或需要加鎖的任務,也盡量不要在其中調用其他方法,即使是本類的方法。

若某個全域狀態無法在編譯期初始化,則可以放在initialize裡來做。

// EOCClass.h#import <Foundation/Foundation.h>@interface EOCClass : NSObject@end// EOCClass.m#import "EOCClass.h"static const int kInterval = 10;static NSMutableArray *kSomeObjects;@implementation EOCClass+ (void)initialize{    // 判斷類的類型,防止在子類中執行    if(self == [EOCClass class]){        kSomeObjects = [NSMutableArray new];    }}@end

整數可以在編譯期定義,然而可變數組不行,下面這樣建立對象會報錯。

static NSMutableArray *kSomeObjects = [NSMutableArray new];
第52條:別忘了NSTimer會保留其目標對象

NSTimer(計時器)是一種很方便很有用的對象,計時器要和運行迴圈相關聯,運行迴圈到時候會觸發任務。只有把計時器放到運行迴圈裡,它才能正常觸發任務。例如,下面這個方法可以建立計時器,並將其預先安排在當前運行迴圈中:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

此方法建立出來的計時器會在指定的間隔時間之後執行任務。也可以令其反覆執行任務,直到開發人員稍後將其手動關閉為止。target和selector表示在哪個對象上調用哪個方法。執行完任務後,一次性計時器會失效,若repeats為YES,那麼必須調用invalidate方法才能使其停止。

重複執行模式的計時器,很容易引入保留環:

@interface EOCClass : NSObject- (void)startPolling;- (void)stopPolling;@end@implementation EOCClass{    NSTimer *_poliTimer;}- (id) init{    return [super init];}- (void)dealloc{    [_poliTimer invalidate];}- (void)stopPolling{    [_poliTimer invalidate];    _poliTimer = nil;}- (void)startPolling{    _poliTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(p_doPoll) userInfo:nil repeats:YES];}- (void)p_doPoll{    // code}

如果建立了本類執行個體,並調用了startPolling方法。建立計時器的時候,由於目標對象是self,所以要保留此執行個體。然而,因為計時器是用執行個體變數存放的,所以執行個體也保留了計數器,於是就產生了保留環。

調用stopPolling方法或令系統將執行個體回收(會自動調用dealloc方法)可以使計時器失效,從而打破迴圈,但無法確保startPolling方法一定調用,而由於計時器儲存著執行個體,執行個體永遠不會被系統回收。當EOCClass執行個體的最後一個外部參考移走之後,執行個體仍然存活,而計時器對象也就不可能被系統回收,除了計時器外沒有別的引用再指向這個執行個體,執行個體就永遠丟失了,造成記憶體流失。

解決方案是採用塊為計時器添加新功能

@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();    }}

再修改stopPolling方法:

- (void)startPolling{    __weak EOCClass *weakSelf = self;    _poliTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{        EOCClass *strongSelf = weakSelf;        [strongSelf p_doPoll];    } repeats:YES];}

這段代碼先定義了一個弱引用指向self,然後用塊捕獲這個引用,這樣self就不會被計時器所保留,當塊開始執行時,立刻產生strong引用,保證執行個體在執行器繼續存活。

[編寫高品質iOS代碼的52個有效方法](十一)系統架構

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.