Objective-C Runtime

ebamboo發表於2021-01-16

一、Objective-C Runtime 簡介

Objective-C Runtime 是一個執行時庫。它可以在程式執行時改變程式的結構如:新增屬性、新增方法、交換方法等。

二、物件、類的結構和關係

每個物件都有個 isa 屬性指向物件所屬類;有個 super_class 屬性指向所屬類的父類;
類也是物件,它的 isa 指向類的元類;類的元類的父類等於類的父類的元類。
例項的屬性儲存在例項中,例項的方法儲存在所屬類中,類方法儲存在元類中。
類中存放著例項方法列表,在這個方法列表中 SEL 作為 key,IMP 作為 value。

三、OC 方法呼叫過程

1.檢測 SEL 是否應該被忽略。
2.檢測傳送的 target 是否為 nil ,如果是則忽略該訊息。
3.當呼叫例項方法時,通過 isa 指標找到例項對應的 class 並且在其中的快取方法列表以及方法列表中進行查詢,如果找不到則根據 super_class 指標在父類中查詢,直至根類(NSObject 或 NSProxy)。
當呼叫類方法時,通過 isa 指標找到例項對應的 metaclass 並且在其中的快取方法列表以及方法列表中進行查詢,如果找不到則根據 super_class 指標在父類中查詢,直至根類(NSObject 或 NSProxy)。

四、runtime 使用場景

1、給分類新增“屬性”

// 在分類的 .h 檔案中宣告“屬性”

@property (nonatomic) NSInteger age;

// 在分類的 .m 實現以下兩個方法

- (void)setAge:(NSInteger)age{
    // 使用執行時關聯物件,Person物件self強引用NSNumber物件@(age),並且設定標記為"age"(可以根據該標記來獲取引用的物件age,標記可以為任意字元,只要setter和getter中的標記一致就可以)
    // 引數1:源物件
    // 引數2:關聯時用來標記屬性的key(因為可能要新增很多屬性)
    // 引數3:關聯的物件
    // 引數4:關聯策略。assign,retain,copy對應的列舉值
    objc_setAssociatedObject(self, "age", @(age), OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSInteger)age{
    // 根據"age"標識取Person物件self強引用的NSNumber物件@(age)
    // 引數1:源物件
    // 引數2:關聯時用來標記屬性的key(因為可能要新增很多屬性)
    return [objc_getAssociatedObject(self, "age") integerValue];
}
2、動態交換兩個方法的實現
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originMethod = class_getInstanceMethod([self class], @selector(originFunction));
        Method customMethod = class_getInstanceMethod([self class], @selector(customFunction));
        // 嘗試自定義方法實現新增到系統方法中
        BOOL addSuccess = class_addMethod([self class], @selector(originFunction), method_getImplementation(customMethod), method_getTypeEncoding(customMethod));
        if (addSuccess) {
            // 新增成功後,系統方法實現設定為自定義方法中
            class_replaceMethod([self class], @selector(customFunction), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        } else {
            method_exchangeImplementations(originMethod, customMethod);
        }
    });
}
3、動態新增方法如:自定義定時器的 target,動態新增與控制器相同的方法。
Method targetMethod = class_getInstanceMethod([aTarget class], aSelector);
if (!class_addMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod))) {
    class_replaceMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
}
4、動態獲取屬性,可以用在 NSCoding 歸檔中,不用一個一個設定屬性的解歸檔。
- (void)encodeWithCoder:(NSCoder *)encoder {
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Movie class], &count);

    for (int i = 0; i < count; i++) {
        // 取出 i 位置對應的成員變數
        Ivar ivar = ivars[I];
        // 檢視成員變數
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        // 歸檔
        [encoder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (id)initWithCoder:(NSCoder *)decoder {
    if (self = [super init]) {
        
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        
        for (int i = 0; i < count; i++) {
            // 取出i位置對應的成員變數
            Ivar ivar = ivars[I];
            // 檢視成員變數
            const char *name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [decoder decodeObjectForKey:key];
            // 設定到成員變數身上
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}

相關文章