這是Objective-C系列的第5篇。數組
將一系列代碼封裝爲動態庫(dynamic library),並在其中放入描述其接口的頭文件,這樣作出來的東西就叫作框架。有時爲iOS平臺構建的第三方框架所使用的是靜態庫(static library),這是由於iOS應用程序不容許在其中包括動態庫。這些東西嚴格來說,並非真正的框架,然而也常常視爲框架。不過,全部iOS平臺的系統框架仍然使用動態庫。緩存
請記住:用純C寫成的框架與用Objective-C寫成的同樣重要,若要想成爲優秀的Objective-C的開發者,應該掌握C語言的核心概念。安全
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'
}
複製代碼
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。網絡
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
快速遍歷,語法最簡單,且效率最高,可是咱們遇到的狀況是常常會用到下標,這就無能爲力了。框架
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
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;
複製代碼
在使用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。
關於緩存部分,準備寫系列的總結:在這兒先佔個坑:緩存(三)NSCache
實現自動緩存是應選用NSCache而非NSDictionary對象,由於NSCache能夠提供優雅的自動刪減功能,並且是「線程安全的」,此外,它與字典不一樣,並不會copy鍵;
編者按:所以,NSCache的鍵不須要實現NSCopying協議。
能夠給NSCache設置上限,用以限制緩存中的對象總個數及「總成本」,而這些尺度定義了緩存刪減其中對象的時機,可是絕對不要把這些尺度當初可靠的「硬限制」。它們僅僅對NSCache起指導做用;
將NSPureableData與NSCache搭配使用,可實現自動清除數據的功能,也就是說,當NSPureableData對象所佔內存爲系統所丟棄是,該對象那自身也會從緩存中移除;
若是緩存使用得當,那麼應用程序的響應就能提高。只有那種「從新計算起來很費事」的數據,才值得放入緩存,好比那些須要從網絡獲取或從磁盤讀取的數據;
有時候,類必須先執行某些初始化操做,而後才能正常使用。
+ (void)load;
複製代碼
對於加入運行期的每一個類(class)及分類(category)來講,一定會調用此方法,並且僅調用一次;
當包含類或分類的程序載入系統時,就會執行此方法,而這一般就是指應用程序啓動的時候,如果iOS程序確定會在此時執行。Mac程序則更自由一些,它們可使用「動態加載」之類的特性,等應用程序啓動好以後再去加載;
不遵循繼承規則:
load的問題
(1)執行load時,運行期系統處於「脆弱狀態(fragile state)」。在執行子類的load時,必會先調用全部超類的load方法,而若是代碼還依賴其餘程序庫,那麼程序庫裏類的load也一定會先執行。然而,根據某個給定的程序庫,卻沒法判斷出其中各個類的載入順序。所以,在load方法中調用其餘類是不安全的。
(2)load方法執行時,會阻塞當前應用程序。
綜合,出於帶來的負面影響,load方法務必要精簡,不要作過多的事情。要麼就是脆弱的調用而產生崩潰,要麼就是繁雜的代碼邏輯使得程序長時間無響應。因此,load方法在當前寫程序,用處通常不大。
+ (void)initialize;
複製代碼
對於每一個類來講,該方法會在程序首次使用該類以前調用,且只調用一次。它是由運行期系統來調用的,毫不應該經過代碼直接調用。與load很是類似,卻略有區別:
編者按:只調用一次,其實並不許確,可能會調用屢次。
惰性調用:程序用到該類時,再調用;
運行期在執行該方法時,是正常狀態的,能夠調用任何類的任意方法。其次,initialize方法必定會在「線程安全的環境」中執行,也就是說,只有執行initialize的那個線程能夠操做類或類實例,其餘線程都要先阻塞,等着initialize執行完;
initialize方法與其餘消息同樣,若是某個類沒實現它,而超類實現了,就會調用超類的實現代碼,這個跟load也有區別;
initialize若是不精簡的問題
(1)對於某個類來講,任何線程均可能是初次使用到該類的線程,假如恰好UI線程用到該類,那麼UI線程會一直阻塞,直到initialize執行完畢。
(2)若是在initialize中過多的邏輯,就會使得不少操做依賴於初始化時間,而一個類的初始化有時候會依賴系統的,假如系統優化後,初始化時機更改,那麼可能會存在潛在的隱患。即,代碼依賴於特定的時間點是很危險的事。
(3)若是在initialize中複雜的邏輯,致使A類的initialize中調用B類的方法,而B類的方法又調用A類的數據這種循環調用時,代碼就沒法正常運行;
綜合以上,initialize應該只用來設置內部數據,不該該調用其餘類的方法,甚至本類的方法也最好不要調用。
要點: