iOS之RunTime淺談

首先說一下什麼是runtime:
RunTime簡稱運行時。就是系統在運行的時候的一些機制,其中最主要的是消息機制。對於C語言,函數的調用 在編譯的時候會決定調用哪個函數( C語言的函數調用請看這裏 )。編譯完成之後直接順序執行,無任何二義性。OC的函數調用成爲消息發送。屬於動態調用過程。在編譯的時候並不能決定真正調用哪個函數(事實證明,在編 譯階段,OC可以調用任何函數,即使這個函數並未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。只有在真正運行的時候纔會根據函數的名稱找 到對應的函數來調用。OC的代碼要創造並跑起來必不可少的是XCode的IDE和運行時框架。
舉例說明:
比如你[obj makeText];
則運行時就這樣的:首先,編譯器將代碼[obj makeText];轉化爲objc_msgSend(obj, @selector (makeText));,在objc_msgSend函數中。首先通過obj的isa指針找到obj對應的class。在Class中先去cache中 通過SEL查找對應函數method(猜測cache中method列表是以SEL爲key通過hash表來存儲的,這樣能提高函數查找速度),若 cache中未找到。再去methodList中查找,若methodlist中未找到,則取superClass中查找。若能找到,則將method加 入到cache中,以方便下次查找,並通過method中的函數指針跳轉到對應的函數中去執行。

Runtime就是運行時,一個程序開發的過程通常可以分爲以下階段,編輯-預編譯-編譯-連接-運行,運行時可以說就是我們的程序再運行的階段發生的一些事情,在這個階段程序通常會把一些OC的代碼轉化成C語言的代碼,從而提高執行的效率,在這個階段我們也可以動態的爲某個對象的屬性賦值,而對象的屬性具體是什麼類型也會在這個階段進行確定(NSString *str = [NSData data]; 其中str在編譯的時候是NSString類型,運行的時候是NSData類型)。系統也提供了Runtime的類庫,讓我們可以直接調用一些運行時把OC代碼轉化C之後的代碼比如:objc_msgSend();同樣也可以通過運行時,爲分類添加屬性,需要用到objc_getAssociatedObject和objc_setAssociatedObject函數

總結起來,iOS中的RunTime的作用有以下幾點:

1.發送消息(obj_msgSend)

2.方法交換(method_exchangeImplementations)

3.消息轉發

4.動態添加方法

5.給分類添加屬性

6.獲取到類的成員變量及其方法

7.動態添加類

下面我想通過一個小demo說一下方法交換:

首先我覺得既然運行時是可以實現方法的交換我覺得類似於切面編程中的切面處理邏輯(即在某一個執行過程時刻執行你的邏輯),這種情況可能需要在你擴充一些系統現有的方法的時候用到(把系統的方法擴展爲自己的方法,自己的方法可以既有系統的方法還可以自己擴充一些自定義的東西)。另外一個是假如你在一個比較多邏輯的app中大量用到了同一個方法的邏輯塊,突然你想要換掉這塊邏輯,而依次換是不現實的,這時候你只需要知道方法的名字就可以利用運行時執行替換的方法來把原來的方法邏輯換成新的。

下面看代碼:

首先建立一個項目(我們都知道頁面在退出的時候或者生命週期喲啊結束的時候是會調用它的dealloc方法的,我們爲了說明方法的替換就把系統的dealloc方法替換成我們自己的方法):

 

我覺得要實現方法的交換就要找好交換點,也就是那個切面的東西,這裏好像一般的runtime都會選擇寫在你要交換方法的類的類目裏,這裏新建一個UIViewController的類目文件爲UIViewController+MyExtension,.m文件裏的實現如下:

複製代碼

#import "UIViewController+MyExtension.h"
#import <objc/runtime.h>
@implementation UIViewController (MyExtension)
+ (void)load{
    Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
    Method method2 = class_getInstanceMethod(self, @selector(my_dealloc));
    method_exchangeImplementations(method1, method2);
}
- (void)my_dealloc{
    NSLog(@"%@頁面已經退出,這是我的實現...",self);
}

複製代碼

因爲所有的頁面都會走load方法 所以選擇切面點爲重寫的load方法中,class_getInstanceMethod爲獲得實例方法(就是減號方法),如果要獲得類方法需要用class_getClassMethod,此時我們在UIViewController的類目裏寫方法,那麼每個UIViewController都會走這個類目裏重寫的load方法,我們在頁面銷燬的時候就會走my_dealloc方法,先看一下程序的運行效果:

 

這個是MainController頁面,點擊下一頁會推出FirstViewController:

 

此時點擊back回退,那個這個時候FirstViewController頁面就會被銷燬,系統此時會自動釣魚dealloc方法,而我們此時替換了dealloc方法,那麼我們就會在控制檯得到:

 

此時我們就實現了交換方法的目的,當然此時你在調用我自定義的方法的時候就恰好是執行的系統的方法,因爲發生了交換。

下面再通過一個小demo說一下給分類添加屬性

首先還是建立一個項目:

 

本示例目的在於通過調用button類目裏的一個block實現button的點擊事件,需要建立一個類目文件:
UIButton+aa.h文件:

複製代碼

#import <UIKit/UIKit.h>
//定義一個block 參數爲NSIndexPath類型 返回值爲void
typedef void(^ButtonAction) (NSIndexPath *index);
@interface UIButton (aa)

@property (nonatomic,strong) NSIndexPath *index;

//ButtonAction類型的block變量buttonAction
@property (nonatomic,copy) ButtonAction buttonAction;

@end

複製代碼

UIButton+aa.m文件:

複製代碼

#import "UIButton+aa.h"
#import <objc/runtime.h>

@interface UIButton()
@end

const void *key = "key";
const void *key1 = "key1";

@implementation UIButton (aa)
//set get方法自己寫
@dynamic index;
@dynamic buttonAction;

//Index屬性的set方法
- (void)setIndex:(NSIndexPath *)index{
    
    //給誰綁定 key值  綁定的值 //語義修飾
    objc_setAssociatedObject(self, key, index, OBJC_ASSOCIATION_RETAIN);
}

//Index屬性的get方法
- (NSIndexPath *)index{
    return objc_getAssociatedObject(self, key);
}

//ButtonAction這個block屬性的set方法
- (void)setButtonAction:(ButtonAction)buttonAction{
    
    //這裏在UIButton的類目裏在block的set方法裏給當前的self(UIButton對象)添加一個addTarget,當UIButton類型的對象調用這個ButtonAction的block時候相當於就是實現了一個點擊事件
    [self addTarget:self action:@selector(selfAction) forControlEvents:UIControlEventTouchUpInside];
    
    //給誰綁定 key值  綁定的值  語義修飾
    objc_setAssociatedObject(self, key1, buttonAction, OBJC_ASSOCIATION_COPY);
}

//ButtonAction這個block屬性的get方法
- (ButtonAction)buttonAction{
    return objc_getAssociatedObject(self, key1);
}

//實現點擊事件
- (void)selfAction{
    if (self.buttonAction) {//判斷block有沒有被初始化
        self.buttonAction([NSIndexPath indexPathForRow:1 inSection:3]);
    }
}

@end


複製代碼

 

在ViewController裏調用的時候:

複製代碼

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *but = [UIButton buttonWithType:(UIButtonTypeSystem)];
    but.index = [NSIndexPath indexPathForRow:0 inSection:1];
    but.buttonAction = ^(NSIndexPath *index){
        NSLog(@"%@",index);
    };

    but.frame = CGRectMake(100, 100, 100, 100);
    but.backgroundColor = [UIColor redColor];
    [self.view addSubview:but];
}

複製代碼

此時頁面運行效果爲:

 

 

下面通過一段代碼來演示獲取類所有實例變量和所有方法:

複製代碼

//獲取類所有實例變量
    unsigned int ivarCount = 0;
    Ivar *ivarArray = class_copyIvarList([UIViewController class], &ivarCount);
    for (int i = 0; i < ivarCount; i++) {
        Ivar var = ivarArray[i];
        NSLog(@"%s",ivar_getName(var));
        NSLog(@"%s",ivar_getTypeEncoding(var));
    }

複製代碼

複製代碼

//獲取類所有方法
    unsigned int mCount = 0;
    Method *methodArray = class_copyMethodList([UIViewController class], &mCount);
    for (int i = 0; i< mCount; i++) {
        Method method = methodArray[i];
        SEL aSelector = method_getName(method);
        NSLog(@"%s",sel_getName(aSelector));
    }

複製代碼

字符串和類名的相互轉換:

複製代碼

 //字符串和類名的相互轉換
    NSString *className = NSStringFromClass([UIViewController class]);
    Class ThisClass = NSClassFromString(className);
    
    id tc1 = [ThisClass new];
    NSLog(@"%@",tc1);

複製代碼

用runtime實現歸檔反歸檔的簡單寫法:

我們這裏以實現Person類的歸檔反歸檔爲例,一般情況下我們要實現Person類的歸檔和反歸檔就要在類的實現裏繼承<NSCoding>協議並重寫兩個方法:- (instancetype)initWithCoder:(NSCoder *)coder 和 - (void)encodeWithCoder:(NSCoder *)coder方法。比較麻煩,而且也是隻能對本Person類型起作用。我們知道所有的類的基類是NSObject類,所以我們根據runtime的思想,我們可以定義一個NSObject的類目AutoEncoding來實現在基類層面實現歸檔反歸檔:

NSObject類目AutoEncoding,.h文件:

複製代碼

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface NSObject (AutoEncoding)<NSCoding>

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;

@end

複製代碼

NSObject類目AutoEncoding,.m文件:

複製代碼

#import "NSObject+AutoEncoding.h"

@implementation NSObject (AutoEncoding)

- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int ivarCount = 0;
    Ivar *ivarArray = class_copyIvarList([self class], &ivarCount);
    for (int i = 0; i<ivarCount; i++) {
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivarArray[i])];
        
        id value = [self valueForKey:key];
        
        //歸檔
        [aCoder encodeObject:value forKey:key];
    }
    free(ivarArray);
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [self init];
    if(self){
        //RunTime獲取類所有實例變量
        unsigned int ivarCount = 0;
        Ivar *ivarArray = class_copyIvarList([self class],&ivarCount);//這裏的self指的是子類
        for (int i = 0; i<ivarCount; i++) {   
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivarArray[i])];
            //decoder根據實例變量名進行反歸檔
            id value = [aDecoder decodeObjectForKey:key];
            
            //KVC賦值
            [self setValue:value forKey:key];
        }
        free(ivarArray);//釋放內存
    }
    return self;
}
@end

複製代碼

RunTime裏有動態方法解析:

RunTime的動態方法解析其實是掩耳盜鈴式的 執行不存在的方法的時候調用,其實就是在調用不存在的方法的時候在那個切面時刻替換成自己的自定義方法:

 

Person *p = [Person new];
[p performSelector:@selector(sssss)];

這裏sssss這個名字的方法不存在,在Person.m裏捕捉到這個執行點:

複製代碼

//類方法動態解析 調用類方法的時候就會走這個方法
+ (BOOL)resolveClassMethod:(SEL)sel{
    //return [super resolveClassMethod:sel];
    
    void(^methodBlock)() = ^(){
        NSLog(@"%@的%s類方法沒有實現",self,sel_getName(sel));
    };
    if(sel == @selector(sssss)){
        //當遇到要執行ssss方法的時候需要替換成block方法(imp_implementationWithBlock把block變成指針)
        class_addMethod([[self class]class], sel, imp_implementationWithBlock(methodBlock),"v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
//實例方法動態解析 調用實例方法的時候就會走這個方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    void(^methodBlock)() = ^(){
        NSLog(@"%@的%s實例方法沒有實現",self,sel_getName(sel));
    };
    if(sel == @selector(sssss)){
        //當遇到要執行ssss方法的時候需要替換成block方法(imp_implementationWithBlock把block變成指針)
        class_addMethod([self class], sel, imp_implementationWithBlock(methodBlock),"v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

複製代碼

 RunTime實現消息轉發(多代理模式):

新建一個類MultiTaskDelegate文件:

在.h文件裏:

複製代碼

#import <Foundation/Foundation.h>

@protocol CKDelegate <NSObject>

- (void)justForFun;

@end

@interface MultiTaskDelegate : NSObject

//添加代理
- (void)addDelegate:(id)delegate Queue:(NSOperationQueue *)queue;

//移除代理
- (void)removeDelegate:(id)delegate;

@end

複製代碼

在.m文件裏:

複製代碼

#import "MultiTaskDelegate.h"
#import <objc/runtime.h>

NSString *const CKAssociatedKey = @"MultiDelegateKey";//存代理
NSString *const CKQueueKey = @"QueueKey";//存Queue

@implementation MultiTaskDelegate

//2.重寫方法實現消息轉發
//獲取方法選標籤名(重寫系統的方法)一旦重寫了此方法程序就會崩潰 所以要內部實現一個doNothing的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    //代理
    NSArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey));
    for (id delegate in delegateArray) {
        if(delegate){
            //返回對象後執行forwardInvocation方法
            return [delegate methodSignatureForSelector:aSelector];
        }
    }
    //沒有delegate就執行doNothing方法
    return [self methodSignatureForSelector:@selector(doNothing)];
}
//3.實現消息轉發(重寫系統的方法)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //取出來
    NSArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey));
    for(id delegate in delegateArray){
        NSOperationQueue *queue = objc_getAssociatedObject(self, (__bridge const void *)(CKQueueKey));
        //執行代理方法
        [queue addOperationWithBlock:^{
            [anInvocation invokeWithTarget:delegate];//指定代理去執行方法
        }];
    }
}

//什麼都不做的方法
- (void)doNothing{
  
}

//1.添加代理
- (void)addDelegate:(id)delegate Queue:(NSOperationQueue *)queue{
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey));
    if(!delegateArray){
        delegateArray = [NSMutableArray new];
    }
    [delegateArray addObject:delegate];
    
    //添加代理
    objc_setAssociatedObject(self, (__bridge const void *)CKAssociatedKey,delegateArray,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    //添加線程隊列
    objc_setAssociatedObject(self, (__bridge const void *)(CKQueueKey), queue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//移除代理
- (void)removeDelegate:(id)delegate{
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey));
    if([delegateArray containsObject:delegate]){
        [delegateArray removeObject:delegate];
    }
}

複製代碼

調用處:

複製代碼

#import "ViewController.h"
#import <objc/runtime.h>
#import "Person.h"
#import "MultiTaskDelegate.h"

@interface ViewController ()<CKDelegate>//實現一個協議

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
#pragma mark 消息轉發 //多代理
    MultiTaskDelegate *mObject = [MultiTaskDelegate new];
    [mObject addDelegate:self Queue:[NSOperationQueue mainQueue]];//指定代理 可以一直添加
    
    Person *p = [Person new];
    [mObject addDelegate:p Queue:[NSOperationQueue mainQueue]];//添加多個代理 這裏Person類也繼承了協議CKDelegate
 [mObject performSelector:@selector(justForFun)];//此時就讓ViewController執行了代理方法} 

//實現代理方法 - (void)justForFun{ NSLog(@"%@執行代理方法",self); 
}

@end

複製代碼

發佈了100 篇原創文章 · 獲贊 216 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章