iOS-Runtime原理及使用

一.Runtime原理

1.Runtime簡稱運行時.OC就是運行時機制,(就是系統在運行的時候的一些機制)其中最主要的是消息機制.對于C語言,函數的調用在編譯的時候會決定調用哪個函數.對于OC的函數,屬于動態調用過程,在編譯的時候并不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用.

2.它是一個主要使用C和匯編寫的庫,為C添加了面相對象的能力并創造了Objective-C.這就是說它在類信息(Class information)中被加載,完成所有的方法分發,方法轉發,等等.Objective-C runtime 創建了所有需要的結構體,讓Objective-C 的面相對象編程變為可能.

3.是一套比較底層的純C語言API,屬于1個C語言庫, 包含了很多底層的C語言API.在我們平時編寫的OC代碼中,程序運行過程時,其實最終都是轉成了runtime的C語言代碼,runtime算是OC的幕后工作者.

4.因為Objc是一門動態語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運行時.也就是說只有編譯器是不夠的,還需要一個運行時系統(runtime system)來執行編譯后的代碼.這就是 Objective-C Runtime 系統存在的意義,它是整個Objc運行框架的一塊基石.

5.Mac和iPhone開發者關心的有兩個runtime:Modern Runtime(現代的 Runtime)和 Legacy Runtime(過時的Runtime).Modern Runtime:覆蓋所有 64 位的 Mac OS X 應用和所有 iPhone OS 的應用.Legacy Runtime:覆蓋其他的所有應用(所有32位的 Mac OS X 應用)Method有2種基本類型的方法.Instance Method(實例方法).Class Method(類方法)

二.Runtime相關的頭文件

sr/include/objc中的頭文件
runtime.png

三.Runtime使用

runtime-use.png
Person.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject{
    @private
    float _height;
}
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int age;
@end

NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
#import <objc/runtime.h>
const char* propertiesKey = "propertiesKey";
@implementation Person
/**
 應用2:NSCoding歸檔和解檔
 獲取屬性\成員列表另外一個重要的應用就是進行歸檔和解檔,其原理和上面的kvc基本上一樣,這里只是展示一些代碼:
 */
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(self.class, &count);
    for (int i = 0; i < count; i++) {
        const char *cname = ivar_getName(ivars[I]);
        NSString *name = [NSString stringWithUTF8String:cname];
        NSString *key = [name substringFromIndex:1];
        
        id value = [self valueForKey:key]; // 取出key對應的value
        [aCoder encodeObject:value forKey:key]; // 編碼
    }
}
- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(self.class, &count);
        for (int i = 0; i < count; i++) {
            const char *cname = ivar_getName(ivars[I]);
            NSString *name = [NSString stringWithUTF8String:cname];
            NSString *key = [name substringFromIndex:1];
            
            id value = [aDecoder decodeObjectForKey:key]; // 解碼
            [self setValue:value forKey:key]; // 設置key對應的value
        }
    }
    return self;
}

/**
 3_3.類\對象的關聯對象
 關聯對象不是為類\對象添加屬性或者成員變量(因為在設置關聯后也無法通過ivarList或者propertyList取得) ,而是為類添加一個相關的對象,通常用于存儲類信息,例如存儲類的屬性列表數組,為將來字典轉模型的方便。 例如,將屬性的名稱存到數組中設置關聯
 */
-(void)ws_relevanceObjAction{
    const char *propertiesKey = "propertiesKey";
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Person class], &count);
    NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
    for (int i = 0; i < count; i++) {
//        Ivar pty = ivars[I];
//        printf("ivar===%p\n",pty);
        const char *cname = ivar_getName(ivars[I]);
        NSString *name = [NSString stringWithUTF8String:cname];
        NSString *key = [name substringFromIndex:1]; // 去掉_
        [arrayM addObject:key];
    }
    free(ivars);
    objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
    NSLog(@"%@", arrayM);//(age,height,name)
    //objc_setAssociatedObject方法的參數解釋:
    //第一個參數id object, 當前對象
    //第二個參數const void *key, 關聯的key,可以是任意類型
    //第三個參數id value, 被關聯的對象
    //第四個參數objc_AssociationPolicy policy關聯引用的規則,取值有以下幾種:
//    enum {
//       OBJC_ASSOCIATION_ASSIGN = 0,
//       OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
//       OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
//       OBJC_ASSOCIATION_RETAIN = 01401,
//       OBJC_ASSOCIATION_COPY = 01403
//    };
    
    //如果想要獲取已經關聯的對象,通過key取得即可
//    NSArray *pList = objc_getAssociatedObject(Person, propertiesKey);
}
#pragma mark 封裝可以將以上兩種操作封裝起來,為Person類增加一個properties類方法,封裝上面的操作,用于方便獲取類的屬性列表。
+ (NSArray *)properties {
    // 如果已經關聯了,就依據key取出被關聯的對象并返回
    NSArray *pList = objc_getAssociatedObject(self, propertiesKey);
    if (pList != nil) {
        return pList;
    }
    // 如果沒有關聯,則設置關聯對象,并將對象返回
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
    
    for (unsigned int i = 0; i < count; ++i) {
//        Ivar pty = ivars[I];
        const char *cname = ivar_getName(ivars[I]);
        NSString *name = [NSString stringWithUTF8String:cname];
        NSString *key = [name substringFromIndex:1];
        [arrayM addObject:key];
    }
    free(ivars);
    objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
    return arrayM.copy;
}

/**
 resolve [ri'z?lv] vt. 決定;溶解;使……分解;決心要做……;[主化]解析 vi. 解決;決心;分解 n. 堅決;決定要做的事
 
 3_4.動態添加方法,攔截未實現的方法 移步Person
 每個類都有一下兩個類方法(來自NSObject)
 + (BOOL)resolveClassMethod:(SEL)sel
 + (BOOL)resolveInstanceMethod:(SEL)sel
 以上兩個一個使用于類方法,一個適用于對象方法。在代碼中調用沒有實現的方法時,也就是sel標識的方法沒有實現 都會現調用這兩個方法中的一個(如果是類方法就調用第一個,如果是對象方法就調用第二個)攔截。 通常的做法是在resolve的內部指定sel對應的IMP,從而完成方法的動態創建和調用兩個過程,也可以不指定IMP打印錯誤信息后直接返回。
 假如在Person類中沒有-sayHi這個方法,如果對象p使用[p performSelector:@selector(sayHi) withObject:nil];那么就會必須經過Person類的resolveInstanceMethod:(SEL)sel方法,在這里為-sayHi指定實現。
 */
void abc(id self, SEL _cmd){
    NSLog(@"%@說了hello", [self name]);
}

//動態添加方法:在resolve中添加相應的方法,注意是類方法還是對象方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if ([NSStringFromSelector(sel) isEqualToString:@"sayHi"]) {
        class_addMethod(self,sel,abc,"v@:"); //為sel指定實現為abc
//        [self performSelector:@selector(ws_testAction)];
//        [[[Person alloc] init] performSelector:@selector(ws_testAction)];
    }
    return YES;
}


-(void)ws_testAction{
    NSLog(@"ws_testAction");
}

-(NSString *)description{
    return [NSString stringWithFormat:@"{ name=%@, age=%d, height=%f }",self.name,self.age,self->_height];
}
@end

先導入頭文件

#import "RuntimeViewController.h"
#import "Person+Runtime.h"
#import "Person.h"
#import <objc/message.h>//包含消息機制
#import <objc/runtime.h>//包含對類、成員變量、屬性、方法的操作
@interface RuntimeViewController ()
@end
@implementation RuntimeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setTitle:@"runTime"];//動態添加屬性,修改屬性值(類別"Person+Runtime.h")
    [self exchangeAttribute];//
//    [self performSelector:@selector(likePlay)];
    objc_msgSend(self,@selector(likePlay));// 動態調用方法(在 LLVM 6.0 中增加了一個 OBJC_OLD_DISPATCH_PROTOTYPES,默認配置在 Apple LLVM 6.0 - Preprocessing 中的 Enable Strict Checking of objc_msgSend Calls 中為Yes 改成NO;)
    [self getAttribute];// 利用runtime遍歷一個類的全部成員變量
    [self controlVariables];// 動態控制變量
    [self addMethod];//動態添加方法
    [self exchangeMethod];//動態交換方法實現
}

1.動態為Category擴展加屬性

Person+Runtime.h
#import "Person.h"

@interface Person (Runtime)
@property(nonatomic,copy)NSString *height;// 身高
-(void)setHeight:(NSString *)height;
-(NSString *)height;
-(NSString *)addStr1:(NSString *)str1 str2:(NSString *)str2;
@end
Person+Runtime.m
#import "Person+Runtime.h"
#import <objc/message.h>//包含消息機制
#import <objc/runtime.h>//包含對類、成員變量、屬性、方法的操作
@implementation Person (Runtime)
static char * heightKey = "heightKey";
-(void)setHeight:(NSString *)height{
     objc_setAssociatedObject(self, heightKey, height, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)height{
     return objc_getAssociatedObject(self, heightKey);
}
-(NSString *)addStr1:(NSString *)str1 str2:(NSString *)str2{
    
    return [str1 stringByAppendingString:str2];
}
@end
方法實現
-(void)exchangeAttribute{
    Person *p = [[Person alloc]init];
    p.height = @"178";
    NSLog(@"身高==%@",p.height);
}

打印結果

2016-10-24 10:45:09.003 WsBlog[11181:4616582] 身高==178

2.動態控制變量

-(void)controlVariables{
    Person * p = [Person new];
    p.name = @"wym";
    NSLog(@"%@",[p name]);
    unsigned int count;
    //Ivar表示類中的實例變量。Ivar其實就是一個指向objc_ivar結構體指針,它包含了變量名(ivar_name)、變量類型(ivar_type)等信息。
    Ivar *ivar = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i ++) {
        Ivar var = ivar[I];
        const char *varName = ivar_getName(var);
        NSString *name = [NSString stringWithCString:varName encoding:NSUTF8StringEncoding];
        if ([name isEqualToString:@"_name"]) {
            object_setIvar(p, var, @"ws");
            break;
        }
    }
    NSLog(@"%@",p.name);
}

打印結果

2016-10-24 10:45:09.004 WsBlog[11181:4616582] wym
2016-10-24 10:45:09.004 WsBlog[11181:4616582] ws

3.利用runtime動態遍歷一個類的全部成員變量

-(void)getAttribute{
    //1.導入頭文件 <objc/runtime.h>
    unsigned int count = 0;
    //獲取指向該類所有屬性的指針
    objc_property_t *propeprties = class_copyPropertyList([Person class], &count);
    for (int i = 0; i < count; i++) {
        //獲得
        objc_property_t property = propeprties[I];
        //根據objc_property_t 獲取所有屬性的名稱--->C語言的字符串
        const char *name = property_getName(property);
        NSString *attributeName = [NSString stringWithUTF8String:name];
        NSLog(@"%d-----%@",i,attributeName);
    }
}

打印結果

2016-10-24 10:45:09.003 WsBlog[11181:4616582] 0-----height
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 1-----name
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 2-----gender
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 3-----age

4.動態添加方法

void goHome(id self, SEL _cmd){
    NSLog(@"回家");
}
- (void)addMethod
{
    Person *p = [Person new];
    p.name = @"ET";
    
    class_addMethod([Person class], @selector(shise), (IMP)goHome, "v@:");
    
    [p performSelector:@selector(shise) withObject:nil];
}

打印結果

2016-10-24 10:45:09.004 WsBlog[11181:4616582] 回家

5.動態交換方法實現

-(void)exchangeMethod{
    Person *p = [[Person alloc]init];
    p.name = @"ET";
    [p eat];
    [p play];
    // 實現方法交換
    Method m1 = class_getInstanceMethod([Person class], @selector(eat));
    Method m2 = class_getInstanceMethod([Person class], @selector(play));
    method_exchangeImplementations(m1, m2);
    [p eat];
    [p play];
}

打印結果

2016-10-24 10:45:09.004 WsBlog[11181:4616582] ET玩
2016-10-24 10:45:09.005 WsBlog[11181:4616582] ET吃飯
2016-10-24 10:45:09.008 WsBlog[11181:4616582] ET吃飯
2016-10-24 10:45:09.008 WsBlog[11181:4616582] ET玩

6.動態調取方法

-(void)likePlay{
    NSLog(@"喜歡玩");
}

打印結果

2016-10-24 10:45:09.003 WsBlog[11181:4616582] 喜歡玩

http://www.lxweimin.com/p/e04e66908b09

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容