Objective-C(五)系統框架

這是Objective-C系列的第5篇。數組

1、最佳實踐

  • 多用Block枚舉,少用for循環;
  • NSDictionary的鍵值內存語義不符合要求,能夠自定義實現;
  • 構建緩存時選用NSCache而非NSDictionary
    • 實現自動緩存是應選用NSCache而非NSDictionary對象,由於NSCache能夠提供優雅的自動刪減功能,並且是「線程安全的」,此外,它與字典不一樣,並不會copy鍵;
    • 能夠給NSCache設置上限,用以限制緩存中的對象總個數及「總成本」,而這些尺度定義了緩存刪減其中對象的時機,可是絕對不要把這些尺度當初可靠的「硬限制」。它們僅僅對NSCache起指導做用;
    • 將NSPureableData與NSCache搭配使用,可實現自動清除數據的功能,也就是說,當NSPureableData對象所佔內存爲系統所丟棄是,該對象那自身也會從緩存中移除;
    • 若是緩存使用得當,那麼應用程序的響應就能提高。只有那種「從新計算起來很費事」的數據,才值得放入緩存,好比那些須要從網絡獲取或從磁盤讀取的數據;
  • 精簡initalize與load的實現代碼

2、實踐詳解

2.1 熟悉系統框架

將一系列代碼封裝爲動態庫(dynamic library),並在其中放入描述其接口的頭文件,這樣作出來的東西就叫作框架。有時爲iOS平臺構建的第三方框架所使用的是靜態庫(static library),這是由於iOS應用程序不容許在其中包括動態庫。這些東西嚴格來說,並非真正的框架,然而也常常視爲框架。不過,全部iOS平臺的系統框架仍然使用動態庫。緩存

請記住:用純C寫成的框架與用Objective-C寫成的同樣重要,若要想成爲優秀的Objective-C的開發者,應該掌握C語言的核心概念。安全

2.2 多用塊枚舉,少用for循環

2.2.1 for循環

NSArray *array = [NSArray array];
for (int i = 0; i < array.count; i++) {
    id object = array[i];
    //Do somethins with 'object'
}

NSDictionary *dic = [NSDictionary dictionary];
NSArray *keys = [dic allKeys];
for (int i = 0; i < keys.count; i++) {
    id key = keys[i];
    id object = dic[key];
    //Do somethins with 'key' and 'values'

}

NSSet *set = [NSSet set];
NSArray *objects = [set allObjects];
for (int i = 0; i < objects.count; i++) {
    id object = objects[i];
    //Do somethins with 'object'
}
複製代碼

2.2.2 使用Objective-C 的 NSEnumerator遍歷

NSArray *array = [NSArray array];
NSEnumerator *enumerator = [array objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
    //Do somethins with 'object'

}

NSDictionary *dic = [NSDictionary dictionary];
NSEnumerator *keyEnume = [dic keyEnumerator];
id key;
while ((key = [keyEnume nextObject]) != nil) {
    id value = dic[key];
    //Do somethins with 'key' and 'values'
}
複製代碼

這種遍歷方式,風格比較統一,並且針對不一樣的collection提供不一樣的enumerator。bash

還提供了倒序的enumerator:reverseObjectEnumerator。網絡

2.2.3 快速遍歷

Objective-C 2.0引入了快速遍歷。爲for增長了in這個關鍵字。併發

NSArray *array = [NSArray array];
for (id object in array) {
    //Do somethins with 'object'
}
複製代碼

支持快速遍歷的對象須要實現NSFastEnumeration協議,該協議只有一個方法:- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len;,關於該方法的實現細節能夠去找資料。app

快速遍歷,語法最簡單,且效率最高,可是咱們遇到的狀況是常常會用到下標,這就無能爲力了。框架

2.2.4 基於Block的遍歷方式

NSArray *array = [NSArray array];
//1.
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
    //*stop = YES;
    //就會跳出遍歷
}];
//2.
[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
}];
複製代碼

這種方式,最爲方便,代碼優雅。函數

第2個方法,還能夠指定NSEnumerationOptions掩碼,NSEnumerationConcurrent能夠底層GCD執行併發各輪迭代,NSEnumerationReverse反向遍歷。post

2.3 對自定義其內存管理語義的collection使用無縫橋接

Foundation框架定義了collection及其各類各類collection所對應的Objective-C類。與之類似,CoreFoundation框架也定義一套C語言API。好比NSArray對應於CFArray,這兩種建立數組的方式也許有區別,然而能夠在二者之間平滑轉換,就是「無縫橋接」。

//__bridge:NSArray -> CFArrayRef
//__bridge不會轉換全部權,ARC仍然具有這個Objective-C對象全部權

NSArray *aNSArray = @[@1,@2,@3];
CFArrayRef aCFArray = (__bridge CFArrayRef)aNSArray;
NSLog(@"size of array = %li",CFArrayGetCount(aCFArray));

//__bridge_retained:NSArray -> CFArrayRef
//__bridge_retained會轉換全部權,ARC失去全部權,並且在不使用時,必須釋放。

NSArray *bNSArray = @[@1,@2,@3];
CFArrayRef bCFArray = (__bridge_retained CFArrayRef)bNSArray;
CFRelease(bCFArray);

//__bridge_transfer:CFArrayRef -> NSArray

NSString *values[] = {@"hello", @"world"};
CFArrayRef cCFArray = CFArrayCreate(kCFAllocatorDefault,  (void *)values, (CFIndex)2, NULL);
NSArray *cNSArray = (__bridge_transfer NSArray*)cCFArray;
複製代碼
  • __bridge:NSArray -> CFArrayRef,不會轉換全部權,ARC仍然具有這個Objective-C對象全部權;
  • __bridge_retained:NSArray -> CFArrayRef,ARC失去全部權,並且在不使用Core Foundation時,必須釋放;
  • __bridge_transfer:CFArrayRef -> NSArray。

在使用Foundation框架中的字典對象時,會遇到一個大問題,那就是其鍵的內存管理語義爲「copy」,其值的內存管理語義爲「retain」。除非使用更強大的無縫橋接,下面是關於如何建立一個Core Foundation字典的過程:

下面是一個CF字典的定義:

CFMutableDictionaryRef CFDictionaryCreateMutable(
	CFAllocatorRef allocator, 		//要使用的內存分配器,分配器負責分配及回收內存,一般使用NULL,表示採用默認的分配
	CFIndex capacity, 				//字典的初始大小
	const CFDictionaryKeyCallBacks *keyCallBacks, 
	const CFDictionaryValueCallBacks *valueCallBacks
);
複製代碼

後面兩個回調函數集,用於指示字典中的鍵和值在遇到各類事件時應該執行何種操做。這兩個參數都是指向結構體的指針,對應的結構體以下:

typedef struct {
    CFIndex				version;			//版本號,目前爲0,蘋果可能會修改這個結構體,因此標識版原本區分
    CFDictionaryRetainCallBack		retain;
    CFDictionaryReleaseCallBack		release;
    CFDictionaryCopyDescriptionCallBack	copyDescription;
    CFDictionaryEqualCallBack		equal;
    CFDictionaryHashCallBack		hash;
} CFDictionaryKeyCallBacks;

typedef struct {
    CFIndex				version;
    CFDictionaryRetainCallBack		retain;
    CFDictionaryReleaseCallBack		release;
    CFDictionaryCopyDescriptionCallBack	copyDescription;
    CFDictionaryEqualCallBack		equal;
} CFDictionaryValueCallBacks;
複製代碼

其中參數聲明以下:

typedef const void *	(*CFDictionaryRetainCallBack)(CFAllocatorRef allocator, const void *value);			//函數指針
typedef void		(*CFDictionaryReleaseCallBack)(CFAllocatorRef allocator, const void *value);
typedef CFStringRef	(*CFDictionaryCopyDescriptionCallBack)(const void *value);
typedef Boolean		(*CFDictionaryEqualCallBack)(const void *value1, const void *value2);
typedef CFHashCode	(*CFDictionaryHashCallBack)(const void *value);
複製代碼

下面自定義實現這些函數:

const void* JSRetainCallBack(CFAllocatorRef allocator ,const void *value)
{
    return CFRetain(value);
}

void JSReleaseCallBack(CFAllocatorRef allocator ,const void *value)
{
    CFRelease(value);
}

CFDictionaryKeyCallBacks keyCallbacks = {
    0,
    JSRetainCallBack,
    JSReleaseCallBack,
    NULL,			//採用默認
    CFEqual,		//與NSMutableDictionary一致,最終會調用NSObject的「isEqual:」
    CFHash			//與NSMutableDictionary一致,最終會調用「hash」
};

CFDictionaryValueCallBacks valueCallbacks = {
    0,
    JSRetainCallBack,
    JSReleaseCallBack,
    NULL,
    CFEqual
};
複製代碼

在實現這些函數後,即可以建立自定義的CF字典對象:

CFMutableDictionaryRef aCFMutableDic = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &valueCallbacks);
    NSMutableDictionary *anNSDictionary = (__bridge_transfer NSMutableDictionary *)aCFMutableDic;
複製代碼

那麼對象aCFMutableDic的鍵值的內存管理語義都是「retain」了。

這樣就避免了,在NSMutableDictionary中加入鍵和值時,字典會自動「copy」而「retain」值。若是做爲鍵的對象不支持拷貝操做,就會致使下面運行期錯誤: ​ ​ *** Terminating app due to uncaught exception ​ 'NSInvalidArgumentException',reason:'-[JSPerson copyWithZone:]: unrecognized selector sent to instance 0x7fd069c080b0

  • 經過無縫橋接技術,能夠在Foundation和Core Foundation中的對象之間來回轉換;

  • 在Core Foundation層面建立collection時,能夠指定許多回調函數,這些函數表示此collection應如何處理這些函數。而後,可運用無縫橋接技術,將其轉換成具有特殊內存管理語義的Objective-C collection。

2.4 構建緩存時選用NSCache而非NSDictionary

關於緩存部分,準備寫系列的總結:在這兒先佔個坑:緩存(三)NSCache

  • 實現自動緩存是應選用NSCache而非NSDictionary對象,由於NSCache能夠提供優雅的自動刪減功能,並且是「線程安全的」,此外,它與字典不一樣,並不會copy鍵;

    編者按:所以,NSCache的鍵不須要實現NSCopying協議。

  • 能夠給NSCache設置上限,用以限制緩存中的對象總個數及「總成本」,而這些尺度定義了緩存刪減其中對象的時機,可是絕對不要把這些尺度當初可靠的「硬限制」。它們僅僅對NSCache起指導做用;

  • 將NSPureableData與NSCache搭配使用,可實現自動清除數據的功能,也就是說,當NSPureableData對象所佔內存爲系統所丟棄是,該對象那自身也會從緩存中移除;

  • 若是緩存使用得當,那麼應用程序的響應就能提高。只有那種「從新計算起來很費事」的數據,才值得放入緩存,好比那些須要從網絡獲取或從磁盤讀取的數據;

2.5 精簡initalize與load的實現代碼

​ 有時候,類必須先執行某些初始化操做,而後才能正常使用。

2.5.1 load

+ (void)load;
複製代碼
  • 對於加入運行期的每一個類(class)及分類(category)來講,一定會調用此方法,並且僅調用一次;

  • 當包含類或分類的程序載入系統時,就會執行此方法,而這一般就是指應用程序啓動的時候,如果iOS程序確定會在此時執行。Mac程序則更自由一些,它們可使用「動態加載」之類的特性,等應用程序啓動好以後再去加載;

  • 不遵循繼承規則:

    • (1)分類和類都定義了load方法,那麼先調用類的,再調用分類裏面的;
    • (2)若是某個類自己未實現load方法,那麼無論其父類是否實現此方法,系統都不會調用該類此方法。

load的問題

  • (1)執行load時,運行期系統處於「脆弱狀態(fragile state)」。在執行子類的load時,必會先調用全部超類的load方法,而若是代碼還依賴其餘程序庫,那麼程序庫裏類的load也一定會先執行。然而,根據某個給定的程序庫,卻沒法判斷出其中各個類的載入順序。所以,在load方法中調用其餘類是不安全的。

  • (2)load方法執行時,會阻塞當前應用程序。

  • 綜合,出於帶來的負面影響,load方法務必要精簡,不要作過多的事情。要麼就是脆弱的調用而產生崩潰,要麼就是繁雜的代碼邏輯使得程序長時間無響應。因此,load方法在當前寫程序,用處通常不大。

2.5.2 initialize

+ (void)initialize;
複製代碼

​ 對於每一個類來講,該方法會在程序首次使用該類以前調用,且只調用一次。它是由運行期系統來調用的,毫不應該經過代碼直接調用。與load很是類似,卻略有區別:

編者按:只調用一次,其實並不許確,可能會調用屢次。

  • 惰性調用:程序用到該類時,再調用;

  • 運行期在執行該方法時,是正常狀態的,能夠調用任何類的任意方法。其次,initialize方法必定會在「線程安全的環境」中執行,也就是說,只有執行initialize的那個線程能夠操做類或類實例,其餘線程都要先阻塞,等着initialize執行完;

  • initialize方法與其餘消息同樣,若是某個類沒實現它,而超類實現了,就會調用超類的實現代碼,這個跟load也有區別;

initialize若是不精簡的問題

  • (1)對於某個類來講,任何線程均可能是初次使用到該類的線程,假如恰好UI線程用到該類,那麼UI線程會一直阻塞,直到initialize執行完畢。

  • (2)若是在initialize中過多的邏輯,就會使得不少操做依賴於初始化時間,而一個類的初始化有時候會依賴系統的,假如系統優化後,初始化時機更改,那麼可能會存在潛在的隱患。即,代碼依賴於特定的時間點是很危險的事。

  • (3)若是在initialize中複雜的邏輯,致使A類的initialize中調用B類的方法,而B類的方法又調用A類的數據這種循環調用時,代碼就沒法正常運行;

  • 綜合以上,initialize應該只用來設置內部數據,不該該調用其餘類的方法,甚至本類的方法也最好不要調用。

要點:

  • 在加載階段,若是類實現了load方法,那麼系統就會調用它。分類裏也能夠定義此方法,類的load方法要比分類中的先調用。與其餘方法不一樣,load方法不參與覆寫機制。
  • 首次使用某個類以前,系統會向其發送initialize消息。因爲此方法聽從普通的覆寫規則,因此一般應該在裏面判斷當前要初始化的是哪個類;
  • load和initialize方法都應該實現的精簡一些,這有助於保持應用的響應能力,也能減小引入「依賴環」的概率;
  • 沒法在編譯器設定的全局常量,能夠放在initialize方法裏初始化。
相關文章
相關標籤/搜索