運行時

很多人都喜歡研究底層的東西, 因個人比較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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章