很多人都喜歡研究底層的東西, 因個人比較low, 只能講講runtime在實際工作中的應用.
- 應用1 動態獲取類的屬性
// 獲取成員變量列表, 第三方框架使用此方法居多
// 參數1: 要copy的類
// 參數2: 屬性計數指針
class_copyIvarList(__unsafe_unretained Class cls, unsigned int *outCount);
// 獲取方法列表
class_copyMethodList(__unsafe_unretained Class cls, unsigned int *outCount);
// 獲取屬性列表
class_copyPropertyList(__unsafe_unretained Class cls, unsigned int *outCount);
// 獲取協議列表
class_copyProtocolList(__unsafe_unretained Class cls, unsigned int *outCount)
舉例: 這種方式可以動態獲取一個類中的屬性; 可以避免字典和模型中屬性不匹配時造成崩潰
+ (NSArray *)loadProperties{
unsigned int count = 0;
// 返回所有屬性的數組
// C語言中, 指向數組第一個元素的是一個指針
objc_property_t *list = class_copyPropertyList([self class], &count);
NSLog(@"屬性數量 %u", count);
// 遍歷數組
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; ++i) {
// C語言中沒有對象的概念,一般不需要使用 `*`
objc_property_t pty = list[i];
// 屬性名稱
const char *cname = property_getName(pty);
// 添加到數組
[arrayM addObject:[NSString stringWithUTF8String:cname]];
}
NSLog(@"%@", arrayM);
// 釋放對象
// 用C語言方法創建對象, 用到copy/new/retain/create等時要釋放對象, 具體使用什麼釋放, 看文檔
free(list);
return arrayM.copy;
}
- 應用2 關聯對象
使用: 在第三方框架使用特別多, 目的是讓分類解耦, 動態給某些類增加屬性做緩存
上面的例子可以解決動態添加屬性的問題, 但是每一次調用loadProperty方法時都會進來一次; 而程序運行起來, 類的屬性不會再變, 爲了提升性能, 我們可以考慮只提取一次; 因此我們需要使用到關聯對象
const char *kPropertiesKey = "kPropertiesKey";
+ (NSArray *)loadProperties{
// 利用`關聯`對象,給`類`添加屬性,OC中的類,本身就是一個特殊對象
/**
獲取關聯對象
1. 對象,屬性關聯到的對象
2. key,屬性的 key
*/
NSArray *pList = objc_getAssociatedObject(self, kPropertiesKey);
if (pList != nil) {
return pList;
}
unsigned int count = 0;
// 返回所有屬性的數組
// C語言中, 指向數組第一個元素的是一個指針
objc_property_t *list = class_copyPropertyList([self class], &count);
NSLog(@"屬性數量 %u", count);
// 遍歷數組
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; ++i) {
// C語言中沒有對象的概念,一般不需要使用 `*`
objc_property_t pty = list[i];
// 屬性名稱
const char *cname = property_getName(pty);
// 添加到數組
[arrayM addObject:[NSString stringWithUTF8String:cname]];
}
NSLog(@"%@", arrayM);
// 釋放對象
// 用C語言方法創建對象, 用到copy/new/retain/create等時要釋放對象, 具體使用什麼釋放, 看文檔
free(list);
// 設置關聯對象對象
/**
設置關聯對象屬性,運行時機制中,在OC開發的應用,關聯對象使用的頻率最高!
1. 屬性關聯的對象
2. key
3. 值
4. 引用關係
*/
objc_setAssociatedObject(self, kPropertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return objc_getAssociatedObject(self, kPropertiesKey);
}
- 應用3 交換方法–攔截系統或其他框架的方法
原理: 方法在內存中調用是通過函數指針, 系統有一個函數映射表, 裏面記錄所有函數的地址, 當調用某個函數時, 函數指針指向這個函數的地址;
應用: AFN中有一個方法 resume表示開始網絡任務, 我想要在網絡任務開啓後發一個通知, 這樣就可以監聽到網絡任務開啓了;
但是resume是蘋果封裝好的, 我們夠不到蘋果底層東西. 怎麼辦?
我們定義一個方法 如 AF_Resume 開啓網絡任務同時發送通知; 但是我們開發一個框架時, 不可能告訴每個人都使用AF_Resume;
這時我們就需要用到交換方法; 函數映射表中記錄着resume和AF_Resume兩個函數的地址, 直接交換兩個函數的地址; 交換後再調用resume時, 其實已經去執行 AF_Resume了.
- 給分類添加成員變量
創建一個分類UIImageView+WebImage, 設置一個URLString屬性, 在implementation中重寫set和get方法.
// MARK: - 運行時關聯對象
const void *HMCurrentURLStringKey = "HMCurrentURLStringKey";
- (void)setCurrentURLString:(NSString *)currentURLString {
objc_setAssociatedObject(self, HMCurrentURLStringKey, currentURLString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)currentURLString {
return objc_getAssociatedObject(self, HMCurrentURLStringKey);
}