iOS-Runtime原理及使用

一.Runtime原理

1.Runtime簡稱運(yùn)行時(shí).OC就是運(yùn)行時(shí)機(jī)制,(就是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制)其中最主要的是消息機(jī)制.對(duì)于C語言摘投,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù).對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過程,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來調(diào)用.

2.它是一個(gè)主要使用C和匯編寫的庫,為C添加了面相對(duì)象的能力并創(chuàng)造了Objective-C.這就是說它在類信息(Class information)中被加載逞力,完成所有的方法分發(fā),方法轉(zhuǎn)發(fā),等等.Objective-C runtime 創(chuàng)建了所有需要的結(jié)構(gòu)體,讓Objective-C 的面相對(duì)象編程變?yōu)榭赡?

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

4.因?yàn)镺bjc是一門動(dòng)態(tài)語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí).也就是說只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng)(runtime system)來執(zhí)行編譯后的代碼.這就是 Objective-C Runtime 系統(tǒng)存在的意義,它是整個(gè)Objc運(yùn)行框架的一塊基石.

5.Mac和iPhone開發(fā)者關(guān)心的有兩個(gè)runtime:Modern Runtime(現(xiàn)代的 Runtime)和 Legacy Runtime(過時(shí)的Runtime).Modern Runtime:覆蓋所有 64 位的 Mac OS X 應(yīng)用和所有 iPhone OS 的應(yīng)用.Legacy Runtime:覆蓋其他的所有應(yīng)用(所有32位的 Mac OS X 應(yīng)用)Method有2種基本類型的方法.Instance Method(實(shí)例方法).Class Method(類方法)

二.Runtime相關(guān)的頭文件

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
/**
 應(yīng)用2:NSCoding歸檔和解檔
 獲取屬性\成員列表另外一個(gè)重要的應(yīng)用就是進(jìn)行歸檔和解檔凭语,其原理和上面的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對(duì)應(yīng)的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]; // 設(shè)置key對(duì)應(yīng)的value
        }
    }
    return self;
}

/**
 3_3.類\對(duì)象的關(guān)聯(lián)對(duì)象
 關(guān)聯(lián)對(duì)象不是為類\對(duì)象添加屬性或者成員變量(因?yàn)樵谠O(shè)置關(guān)聯(lián)后也無法通過ivarList或者propertyList取得) 抄肖,而是為類添加一個(gè)相關(guān)的對(duì)象斯棒,通常用于存儲(chǔ)類信息没龙,例如存儲(chǔ)類的屬性列表數(shù)組链韭,為將來字典轉(zhuǎn)模型的方便排霉。 例如窍株,將屬性的名稱存到數(shù)組中設(shè)置關(guān)聯(lián)
 */
-(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方法的參數(shù)解釋:
    //第一個(gè)參數(shù)id object, 當(dāng)前對(duì)象
    //第二個(gè)參數(shù)const void *key, 關(guān)聯(lián)的key,可以是任意類型
    //第三個(gè)參數(shù)id value, 被關(guān)聯(lián)的對(duì)象
    //第四個(gè)參數(shù)objc_AssociationPolicy policy關(guān)聯(lián)引用的規(guī)則,取值有以下幾種:
//    enum {
//       OBJC_ASSOCIATION_ASSIGN = 0,
//       OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
//       OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
//       OBJC_ASSOCIATION_RETAIN = 01401,
//       OBJC_ASSOCIATION_COPY = 01403
//    };
    
    //如果想要獲取已經(jīng)關(guān)聯(lián)的對(duì)象球订,通過key取得即可
//    NSArray *pList = objc_getAssociatedObject(Person, propertiesKey);
}
#pragma mark 封裝可以將以上兩種操作封裝起來,為Person類增加一個(gè)properties類方法,封裝上面的操作后裸,用于方便獲取類的屬性列表。
+ (NSArray *)properties {
    // 如果已經(jīng)關(guān)聯(lián)了冒滩,就依據(jù)key取出被關(guān)聯(lián)的對(duì)象并返回
    NSArray *pList = objc_getAssociatedObject(self, propertiesKey);
    if (pList != nil) {
        return pList;
    }
    // 如果沒有關(guān)聯(lián)微驶,則設(shè)置關(guān)聯(lián)對(duì)象,并將對(duì)象返回
    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. 堅(jiān)決胁艰;決定要做的事
 
 3_4.動(dòng)態(tài)添加方法款筑,攔截未實(shí)現(xiàn)的方法 移步Person
 每個(gè)類都有一下兩個(gè)類方法(來自NSObject)
 + (BOOL)resolveClassMethod:(SEL)sel
 + (BOOL)resolveInstanceMethod:(SEL)sel
 以上兩個(gè)一個(gè)使用于類方法,一個(gè)適用于對(duì)象方法腾么。在代碼中調(diào)用沒有實(shí)現(xiàn)的方法時(shí)奈梳,也就是sel標(biāo)識(shí)的方法沒有實(shí)現(xiàn) 都會(huì)現(xiàn)調(diào)用這兩個(gè)方法中的一個(gè)(如果是類方法就調(diào)用第一個(gè),如果是對(duì)象方法就調(diào)用第二個(gè))攔截哮翘。 通常的做法是在resolve的內(nèi)部指定sel對(duì)應(yīng)的IMP颈嚼,從而完成方法的動(dòng)態(tài)創(chuàng)建和調(diào)用兩個(gè)過程,也可以不指定IMP打印錯(cuò)誤信息后直接返回饭寺。
 假如在Person類中沒有-sayHi這個(gè)方法阻课,如果對(duì)象p使用[p performSelector:@selector(sayHi) withObject:nil];那么就會(huì)必須經(jīng)過Person類的resolveInstanceMethod:(SEL)sel方法,在這里為-sayHi指定實(shí)現(xiàn)艰匙。
 */
void abc(id self, SEL _cmd){
    NSLog(@"%@說了hello", [self name]);
}

//動(dòng)態(tài)添加方法:在resolve中添加相應(yīng)的方法限煞,注意是類方法還是對(duì)象方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if ([NSStringFromSelector(sel) isEqualToString:@"sayHi"]) {
        class_addMethod(self,sel,abc,"v@:"); //為sel指定實(shí)現(xiàn)為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

先導(dǎo)入頭文件

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

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

1.動(dòng)態(tài)為Category擴(kuò)展加屬性

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>//包含消息機(jī)制
#import <objc/runtime.h>//包含對(duì)類旺上、成員變量、屬性糖埋、方法的操作
@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
方法實(shí)現(xiàn)
-(void)exchangeAttribute{
    Person *p = [[Person alloc]init];
    p.height = @"178";
    NSLog(@"身高==%@",p.height);
}

打印結(jié)果

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

2.動(dòng)態(tài)控制變量

-(void)controlVariables{
    Person * p = [Person new];
    p.name = @"wym";
    NSLog(@"%@",[p name]);
    unsigned int count;
    //Ivar表示類中的實(shí)例變量宣吱。Ivar其實(shí)就是一個(gè)指向objc_ivar結(jié)構(gòu)體指針,它包含了變量名(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);
}

打印結(jié)果

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

3.利用runtime動(dòng)態(tài)遍歷一個(gè)類的全部成員變量

-(void)getAttribute{
    //1.導(dǎo)入頭文件 <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];
        //根據(jù)objc_property_t 獲取所有屬性的名稱--->C語言的字符串
        const char *name = property_getName(property);
        NSString *attributeName = [NSString stringWithUTF8String:name];
        NSLog(@"%d-----%@",i,attributeName);
    }
}

打印結(jié)果

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.動(dòng)態(tài)添加方法

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];
}

打印結(jié)果

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

5.動(dòng)態(tài)交換方法實(shí)現(xiàn)

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

打印結(jié)果

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.動(dòng)態(tài)調(diào)取方法

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

打印結(jié)果

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杭攻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子疤坝,更是在濱河造成了極大的恐慌兆解,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跑揉,死亡現(xiàn)場離奇詭異锅睛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)历谍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門衣撬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扮饶,你說我怎么就攤上這事≌Ч梗” “怎么了甜无?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哥遮。 經(jīng)常有香客問我岂丘,道長,這世上最難降的妖魔是什么眠饮? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任奥帘,我火速辦了婚禮,結(jié)果婚禮上仪召,老公的妹妹穿的比我還像新娘寨蹋。我一直安慰自己,他們只是感情好扔茅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布已旧。 她就那樣靜靜地躺著,像睡著了一般召娜。 火紅的嫁衣襯著肌膚如雪运褪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天玖瘸,我揣著相機(jī)與錄音秸讹,去河邊找鬼。 笑死雅倒,一個(gè)胖子當(dāng)著我的面吹牛璃诀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屯断,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼文虏,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼侣诺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起氧秘,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤年鸳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后丸相,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搔确,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年灭忠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膳算。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弛作,死狀恐怖涕蜂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情映琳,我是刑警寧澤机隙,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站萨西,受9級(jí)特大地震影響有鹿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谎脯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一葱跋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧源梭,春花似錦娱俺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脑溢,卻和暖如春僵朗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屑彻。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工验庙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人社牲。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓粪薛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搏恤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子违寿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容