1. 程式人生 > >iOS開發之Runtime初探

iOS開發之Runtime初探

一:基礎概念

RunTime簡稱執行時,就是系統在執行的時候的一些機制,其中最主要的是訊息機制。
對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式,編譯完成之後直接順序執行,無任何二義性。
OC的函式呼叫成為訊息傳送。屬於動態呼叫過程。在編譯的時候並不能決定真正呼叫哪個函式(事實證明,在編 譯階段,OC可以呼叫任何函式,即使這個函式並未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。
只有在真正執行的時候才會根據函式的名稱找 到對應的函式來呼叫。

Objective-C 從三種不同的層級上與 Runtime 系統進行互動,分別是:
1、通過 Objective-C 原始碼;
2、通過 Foundation 框架的NSObject類定義的方法;
3、通過對 runtime 函式的直接呼叫。
大部分情況下你就只管寫你的Objc程式碼就行,runtime 系統自動在幕後辛勤勞作著。

二:runtime的具體實現

我們寫的oc程式碼,它在執行的時候也是轉換成了runtime方式執行的,更好的理解runtime,也能幫我們更深的掌握oc語言。每一個oc的方法,底層必然有一個與之對應的runtime方法。image

當我們用OC寫下這樣一段程式碼
[tableView cellForRowAtIndexPath:indexPath];
在編譯時RunTime會將上述程式碼轉化成[傳送訊息]
objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);

三:常見方法

1、獲取屬性列表
//People.h
@interface People : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) NSUInteger age; @end //People.m @interface People() { NSString *aaa; } @property (nonatomic, strong) NSString *fatherName; @end @implementation People { NSString *bbb; } @end
//獲取所有屬性
-(void)getAllProperty { unsigned int count = 0; objc_property_t *propertyList = class_copyPropertyList([People class], &count); for (unsigned int i=0; i<count; i++) { const char *propertyName = property_getName(propertyList[i]); NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]); } }

列印結果:

獲取屬性列表

結論:
1)、不管是在.h檔案中定義的屬性還是在.m檔案中定義的屬性,都可以通過獲取屬性列表方法來進行獲取;
2)、成員變數不同於屬性,不能通過該方法來獲取;
3)、先輸出的是.m檔案中的屬性,然後才是.h檔案中的屬性,並且是按照屬性定義的先後順序來儲存。

2、獲取方法列表
//People.h
@interface People : NSObject

@property (nonatomic, strong) NSString *name;

-(void)iPrintName;
+(void)cPrintName;
@end

//People.m
@interface People() {
    NSString *aaa;
}

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People {
    NSString *bbb;
}

//-(void)printName {
//    
//}

+(void)cPrintName {

}

-(void)printAge {

}

@end
//獲取方法(不包括類方法)列表
-(void)getAllMethod {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList([People class], &count);
    for (unsigned int i=0; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
    }
}

獲取方法列表

結論:
1)、類方法不能通過這個函式去獲取到;
2)、只有在.m檔案中實現了的方法才能被獲取到,在.h檔案中定義了,但是.m中沒有實現的並不能獲取到;
3)、對於使用@property定義的屬性,會自動生成setter和getter方法,同樣能被這個方法獲取到;
4)、.m實現中還隱藏了一個.cxx_destruct也就是oc中常見delloc方法;
5)、儲存順序是優先儲存使用者在.m檔案中實現的,其次是.m屬性自動生成的getter和setter方法,然後是隱藏的delloc方法,最後是.h屬性自動生成的getter和setter方法。

3、獲取成員變數列表
//People.h
@interface People : NSObject {
    NSString *cccc;
}

@property (nonatomic, strong) NSString *name;

@end

//People.m
@interface People() {
    NSString *aaa;
}

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People {
    NSString *bbb;
}

@end
-(void)getAllIvar{
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList([People class], &count);
    for (unsigned int i=0; i<count; i++) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
    }
}

列印結果:
獲取成員變數列表

結論:
1)、成員變數的儲存是從.h檔案開始,然後才是.m檔案中的成員變數;
2)、用@property 定義的屬性,會自動生成以_開頭的成員變數,也是先儲存.h檔案生成的,再儲存.m檔案生成的。


4、獲取協議列表
//People.h
@protocol PeopleDelegate <NSObject>

-(void)people;

@end

@interface People : NSObject

@property (nonatomic, strong) NSString *name;
//@property (nonatomic, weak) id <PeopleDelegate> delegate;
@end

//People.m
@interface People()

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People

@end

//ViewController.m
@interface ViewController ()<PeopleDelegate,UITabBarDelegate,UITableViewDataSource>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self getProtocal];
}
@end
//獲取協議列表
-(void)getProtocal {
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);  //這裡變成了self
    for (unsigned int i = 0; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }
}

列印結果:

獲取協議列表

結論:
1)、只要宣告遵循該協議,在引用的時候,就能獲取到該類包含的協議列表,哪怕你沒有指定代理的物件,也沒有去實現協議的方法

5、獲取類方法與例項方法以及方法交換
//People.h
@interface People : NSObject

@property (nonatomic, strong) NSString *name;

-(void)printInstanceMethod;
+(void)printClassMethod;

@end

//People.m
@interface People()

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People
-(void)printInstanceMethod{
    NSLog(@"我是例項方法");
}

+(void)printClassMethod {
    NSLog(@"我是類方法");
}
@end

示例

-(void)getMethod {

    People * p1 = [[People alloc] init];

    Method m1 = class_getInstanceMethod([p1 class], @selector(printInstanceMethod));
    Method m2 = class_getClassMethod([People class], @selector(printClassMethod));
    NSLog(@"測試前:");
    [p1 printInstanceMethod];
    [People printClassMethod];

    method_exchangeImplementations(m1, m2);
    NSLog(@"測試後:");
    [p1 printInstanceMethod];
    [People printClassMethod];
}

列印結果

獲取方法以及交換方法

6、新增方法

當專案中,需要繼承某一個類(subclass),但是父類中並沒有提供我需要的呼叫方法,而我又不清楚父類中某些方法的具體實現;或者,我需要為這個類寫一個分類(category),在這個分類中,我可能需要替換/新增某個方法(注意:不推薦在分類中重寫方法,而且也無法通過 super 來獲取所謂父類的方法)。大致在這兩種情況下,我們可以通過 class_addMethod 來實現我們想要的效果。參考

//Car+MyCar.h
#import "Car+MyCar.h"
#import <objc/runtime.h>

void startEngine(id self, SEL _cmd, NSString *brand) {
    NSLog(@"my %@ car starts the engine", brand);
}

@implementation Car (MyCar)

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(drive)) {
        class_addMethod([self class], sel, (IMP)startEngine, "[email protected]:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

不習慣C語言程式碼可以替換成以下程式碼:

 - (void)startEngine:(NSString *)brand {
    NSLog(@"my %@ car starts the engine", brand);
 }

 + (BOOL)resolveInstanceMethod:(SEL)sel {
     if (sel == @selector(drive)) {
       class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(startEngine:)), "[email protected]:@");
       return YES;
     }
     return [super resolveInstanceMethod:sel];
 }

@end

呼叫:

-(void)addMethod {
    Car *c = [[Car alloc] init];
    [c performSelector:@selector(drive) withObject:@"BMW"];
}

解釋:
在 Objective-C 中,正常的呼叫方法是通過訊息機制(message)來實現的,那麼如果類中沒有找到傳送的訊息方法,系統就會進入找不到該方法的處理流程中,如果在這個流程中,我們加入我們所需要的新方法,就能實現執行過程中的動態添加了。這個流程或者說機制,就是 Objective-C 的 Message Forwarding 。
這個機制中所涉及的方法主要有兩個:

+ (BOOL)resolveInstanceMethod:(SEL)sel;//例項方法
+ (BOOL)resolveClassMethod:(SEL)sel;//類方法

這個函式在 runtime 環境下,如果沒有找到該方法的實現的話就會執行。第一行判斷的是傳入的 SEL 名稱是否匹配,接著呼叫 class_addMethod 方法,傳入相應的引數。其中第三個引數傳入的是我們新增的 C 語言函式的實現,也就是說,第三個引數的名稱要和新增的具體函式名稱一致。第四個引數指的是函式的返回值以及引數內容。

結果:

新增方法

裝逼:

一開始我以為class_addMethod和class_replaceMethod就等同於method_exchangeImplementations,但是看了下面的程式碼才發現,其實這倆的適用條件是不一樣的,當方法沒有實現的時候才能使用class_addMethod和class_replaceMethod套裝,但是當方法已存在的時候,就需要使用method_exchangeImplementations,這點從if (didAddMethod)這個判定就可見一斑。

+(void)load{
    NSString *className = NSStringFromClass(self.class);
    NSLog(@"classname %@", className);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //要特別注意你替換的方法到底是哪個性質的方法
        // When swizzling a Instance method, use the following:
        //        Class class = [self class];

        // When swizzling a class method, use the following:
        Class class = object_getClass((id)self);

        SEL originalSelector = @selector(systemMethod_PrintLog);
        SEL swizzledSelector = @selector(ll_imageName);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

Runtime博大精深,看的越深入,感覺越懵 T_T,淺顯的認知,歡迎大家提意見

Demo


資料
runtime詳解
runtime奇技淫巧系列