iOS Runtime

? 前言:OC是一門動(dòng)態(tài)性比較強(qiáng)的語言盈滴,它的動(dòng)態(tài)性就是由Runtime支撐和實(shí)現(xiàn)的嘉蕾。本文先介紹了Runtime的概念画切,然后詳細(xì)地介紹了OC的消息轉(zhuǎn)發(fā)機(jī)制荚恶,最后介紹了幾種Runtime常見的使用場(chǎng)景姜贡,并配上具體的代碼說明试吁。

一、Runtime概念:

1楼咳、Runtime概念:

? Runtime是一套C語言的API熄捍,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)。OC是一門動(dòng)態(tài)性比較強(qiáng)的語言母怜,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行余耽,OC的動(dòng)態(tài)性就是由Runtime支撐和實(shí)現(xiàn)的,平時(shí)編寫的OC代碼糙申,底層都是轉(zhuǎn)換成Runtime API進(jìn)行調(diào)用宾添。

2船惨、OC的消息機(jī)制:

? OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)化成了objc_msgSend函數(shù)調(diào)用,給receiver(方法調(diào)用者)發(fā)送一條消息(selector方法名)缕陕,objc_msgSend有三個(gè)階段:消息發(fā)送階段(當(dāng)前類粱锐、父類中查找)、動(dòng)態(tài)方法解析階段扛邑、消息轉(zhuǎn)發(fā)階段怜浅。

二、探尋消息轉(zhuǎn)發(fā)機(jī)制:

1蔬崩、OC中方法調(diào)用過程:

? 如果需要了解OC對(duì)象的內(nèi)部結(jié)構(gòu)恶座,請(qǐng)點(diǎn)擊OC對(duì)象的本質(zhì)

? 一個(gè)普通的對(duì)象方法[objc method]沥阳,編譯時(shí)轉(zhuǎn)成消息發(fā)送objc_msgSend(objc, method)跨琳,Runtime執(zhí)行時(shí)流程如下:

? 1)通過objc的ISA指針找到它的class;

? 2)在它的class里methods找到method方法桐罕;

? 3)如果它的class沒有該method脉让,那么就去父類中查找;

? 4)一旦找到該method功炮,那么就執(zhí)行它的實(shí)現(xiàn)IMP溅潜;

? 5)如果在父類中還找不到,那么進(jìn)入動(dòng)態(tài)方法解析+(BOOL)resolveInstanceMethod:(SEL)sel(經(jīng)測(cè)試薪伏,如果直接返回YES或者NO都會(huì)執(zhí)行后續(xù)的forward方法)滚澜;

? 6)如果動(dòng)態(tài)解析沒有添加方法,那么進(jìn)入到消息轉(zhuǎn)發(fā)階段-(id)forwardingTargetForSelector:(SEL)aSelector嫁怀;

? 7)如果消息轉(zhuǎn)發(fā)的對(duì)象還找不到方法设捐,就拋出經(jīng)典異常unrecognized selector,找不到該方法眶掌;

? 在第一步objc通過ISA指針找到它的class之后挡育,嚴(yán)格來說先從它的class中的緩存中查詢方法method,如果沒有則繼續(xù)在methods方法列表進(jìn)行查詢朴爬,后續(xù)操作一致。

代碼驗(yàn)證如下:創(chuàng)建一個(gè)Dog類

@interface Dog : NSObject
- (void)wangWang; //正常調(diào)用
- (void)wangWangResolve; //方法解析
- (void)wangWangForward_YES; //消息轉(zhuǎn)發(fā)YES
- (void)wangWangForward_NO; //消息轉(zhuǎn)發(fā)NO
- (void)wangWangForward_All; //完整的消息轉(zhuǎn)發(fā)
@end

// .m文件
#import "Dog.h"
#import "Cat.h"
#import <objc/runtime.h>
@implementation Dog
// 直接實(shí)現(xiàn)方法
- (void)wangWang {
    NSLog(@"dog wangWang");
}

// 動(dòng)態(tài)解析實(shí)例方法
//+ (BOOL)resolveClassMethod:(SEL)sel //解析類方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(wangWangResolve))
    {
          class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:sel];
}

// C語言函數(shù)
void dynamicMethodIMP(id self, SEL _cmd)
{
    NSLog(@"dog wangWangResolve");
}

// 轉(zhuǎn)發(fā)備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(wangWangForward_YES)) {
        return [Cat new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

//簽名橡淆,進(jìn)入forwardInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(wangWangForward_All)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"]; 
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;

    Cat *cat = [Cat new];
    if([cat respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:cat];
    } else {
        [self doesNotRecognizeSelector:sel];
    }
}
@end

創(chuàng)建一個(gè)Cat類:

@interface Cat : NSObject
- (void)wangWangForward_YES; //消息轉(zhuǎn)發(fā)YES
- (void)wangWangForward_All; //完整的消息轉(zhuǎn)發(fā)
@end

// .m文件
@implementation Cat
- (void)wangWangForward_YES {
    NSLog(@"cat wangWangForward_YES");
}

- (void)wangWangForward_All {
    NSLog(@"cat wangWangForward_All");
}
@end

分別運(yùn)行注釋代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Dog *dog = [Dog new];
    [dog wangWang];
//    [dog wangWangResolve];
//    [dog wangWangForward_YES];
//    [dog wangWangForward_NO];
//      [dog wangWangForward_All];
}

打印結(jié)果:

// [dog wangWang] 正常在.m文件實(shí)現(xiàn)方法
dog wangWang

// [dog wangWangResolve] 動(dòng)態(tài)解析添加方法
dog wangWangResolve

// [dog wangWangForward_YES] 成功消息轉(zhuǎn)發(fā)召噩,對(duì)象是cat
cat wangWangForward_YES

// [dog wangWangForward_NO] 前面階段都找不到方法,拋出異常
-[Dog wangWangForward_NO]: unrecognized selector sent to instance 0x6000031ec350

// [dog wangWangForward_All] 完整的消息轉(zhuǎn)發(fā)
cat wangWangForward_All
2逸爵、完整的消息轉(zhuǎn)發(fā):

? 如果forwardingTargetForSelector這一步還不能處理消息具滴,那唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了。首先發(fā)送一個(gè)methodSignatureForSelector消息师倔,如果有簽名返回构韵,則進(jìn)入到forwardInvocation,在該方法里進(jìn)行最后的消息轉(zhuǎn)發(fā),否則直接發(fā)送doesNotRecognizeSelector消息疲恢,程序拋出異常凶朗。

至于“v@:”具體的意思,可以查看官方文檔Type Encodings显拳。

三棚愤、Runtime應(yīng)用:

? Runtime應(yīng)用場(chǎng)景非常多,這里介紹一些項(xiàng)目中常用的場(chǎng)景:

? 1)關(guān)聯(lián)對(duì)象(AssociateObject)給分類添加的屬性實(shí)現(xiàn)setter和getter方法杂数,運(yùn)行中方法替換(method_exchangeIMP)宛畦,傳送門-Category分類;

? 2)KVO的底層實(shí)現(xiàn),通過runtime動(dòng)態(tài)創(chuàng)建一個(gè)派生類對(duì)象揍移,傳送門-KVC & KVO;

? 3)實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)次和,通過runtime遍歷Model的所有屬性,把服務(wù)器返回的JSON格式轉(zhuǎn)成字典那伐,然后通過KVC給Model賦值踏施;

? 4)實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔,通過runtime遍歷Model的所有屬性喧锦,并對(duì)屬性進(jìn)行encode和decode操作读规;

? 5)其它runtime的API調(diào)用;

1燃少、字典和模型的自動(dòng)轉(zhuǎn)換:

? 對(duì)NSObject寫一個(gè)分類束亏,添加一個(gè)initWithDict:(NSDictionary)dict分類,如下:

@interface NSObject (DictToModel)
- (instancetype)initWithDict:(NSDictionary *)dict;
@end

// .m
@implementation NSObject (DictToModel)

- (instancetype)initWithDict:(NSDictionary *)dict {
    if (self = [self init]) {
        // 獲取類的屬性及屬性對(duì)應(yīng)的類型
        NSMutableArray *keys = [NSMutableArray array];
        NSMutableArray *attributes = [NSMutableArray array];
        
        unsigned int outCount;
        objc_property_t *properties = class_copyPropertyList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            objc_property_t property = properties[i];
            //通過property_getName函數(shù)獲得屬性的名字
            NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            [keys addObject:propertyName];
            //通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
            NSString *propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
            [attributes addObject:propertyAttribute];
        }
        // 立即釋放properties指向的內(nèi)存
        free(properties);

        // 根據(jù)類型給屬性賦值
        for (NSString *key in keys) {
            if ([dict valueForKey:key] == nil) continue;
            [self setValue:[dict valueForKey:key] forKey:key];
        }
    }
    return self;
}

@end

創(chuàng)建一個(gè)Person類:

@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end

運(yùn)行代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSDictionary *dict = @{@"name": @"路飛",
                           @"age": @(15)
                            };
    Person *p = [[Person alloc] initWithDict:dict];
    NSLog(@"name = %@, age = %ld", p.name, p.age);
}

打印結(jié)果:

name = 路飛, age = 15
2阵具、NSCoding的自動(dòng)歸檔和解檔:

? 在Model的基類中重寫方法:

@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end

// .m文件
@implementation Person

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i<count; i++) {
            Ivar ivar = ivars[i]; //拿到Ivar
            const char *name = ivar_getName(ivar); //獲取到屬性的C字符串名稱
            NSString *key = [NSString stringWithUTF8String:name];
            //解檔
            id value = [coder decodeObjectForKey:key];
            // 利用KVC賦值
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    //告訴系統(tǒng)歸檔的屬性是哪些
    unsigned int count = 0; //表示對(duì)象的屬性個(gè)數(shù)
    Ivar *ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        //歸檔 -- 利用KVC
        [coder encodeObject:[self valueForKey:key] forKey:key];
    }
    free(ivars);//在OC中使用了Copy碍遍、Creat、New類型的函數(shù)阳液,需要釋放指針E戮础!(注:ARC管不了C函數(shù))
}

@end

運(yùn)行代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSDictionary *dict = @{@"name": @"路飛",
                           @"age": @(15)
                            };
    Person *p = [[Person alloc] initWithDict:dict];
    
    NSString *temp = NSTemporaryDirectory();
    NSString *filePath = [temp stringByAppendingPathComponent:@"archive.text"]; //注:保存文件的擴(kuò)展名可以任意取帘皿,不影響东跪。
    //NSLog(@"%@", filePath);
    // 歸檔
    [NSKeyedArchiver archiveRootObject:p toFile:filePath];
    
    // 解檔
    Person *p1 = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
    NSLog(@"name = %@, age = %ld", p1.name, p1.age);
}

打印結(jié)果:

name = 路飛, age = 15
3、其它runtime的API調(diào)用:

? 1)[super class]的底層調(diào)用鹰溜,都是通過object_getClass(self)進(jìn)行調(diào)用虽填,代碼如下:

? PS:正常情況下的super與self比較,super是從父類開始查找方法曹动,self是從本類開始斋日;

// 創(chuàng)建一個(gè)Person類
@interface Person : NSObject
@end

@implementation Person
@end

// 創(chuàng)建一個(gè)Student類繼承自Person
@interface Student : Person

@end

@implementation Student

- (id)init {
    if (self = [super init]) {
        Class cls1 = [self class]; //調(diào)用object_getClass(self)
        Class cls2 = [self superclass]; 
        Class cls3 = [super class]; //給當(dāng)前接受者發(fā)送消息,當(dāng)前接受者就是self
        Class cls4 = [super superclass]; 
        NSLog(@"cls1 = %@, cls2 = %@, cls3 = %@, cls4 = %@", cls1, cls2, cls3, cls4);
    }
    return self;
}

@end

運(yùn)行代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *stu = [Student new];
}

打印結(jié)果:

cls1 = Student, cls2 = Person, cls3 = Student, cls4 = Person

objc4源碼中的NSObject.mm文件中墓陈,class的實(shí)現(xiàn):

- (Class)class {
    return object_getClass(self);
}

- (Class)superclass {
    return [self class]->superclass;
}

? 2)isMemberOfClass與isKindOfClass的區(qū)別:

? -(BOOL)isMemberOfClass:(Class)cls:調(diào)用者是否是cls本類的實(shí)例恶守;

? -(BOOL)isKindOfClass:(Class)cls:調(diào)用者是否是cls本類的實(shí)例或者子類的實(shí)例第献;

? 查看objc4源碼,很容易理解:

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls; //是否是本類
}

- (BOOL)isKindOfClass:(Class)cls {
        // self是否是本類或者子類
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

代碼驗(yàn)證兔港,運(yùn)行代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *stu = [Student new];
    BOOL isMember = [stu isMemberOfClass:[Person class]];
    BOOL isKind = [stu isKindOfClass:[Person class]];
    BOOL instance_isMember = [stu isMemberOfClass:[Student class]];
    BOOL class_isMember = [Student isMemberOfClass:[Student class]];
    NSLog(@"isMember = %d, isKind = %d", isMember, isKind);
    NSLog(@"instance_isMember = %d, class_isMember = %d", instance_isMember, class_isMember);
}

打印結(jié)果:

isMember = 0, isKind = 1
// [Student isMemberOfClass:[Student class]] 表示Student的元類與Student類比較庸毫,結(jié)果是false
instance_isMember = 1, class_isMember = 0 

覺得寫的不錯(cuò),有些啟發(fā)或幫助押框,點(diǎn)個(gè)贊哦岔绸!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市橡伞,隨后出現(xiàn)的幾起案子盒揉,更是在濱河造成了極大的恐慌,老刑警劉巖兑徘,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刚盈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挂脑,警方通過查閱死者的電腦和手機(jī)藕漱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崭闲,“玉大人肋联,你說我怎么就攤上這事〉蠹螅” “怎么了橄仍?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)牍戚。 經(jīng)常有香客問我侮繁,道長(zhǎng),這世上最難降的妖魔是什么如孝? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任宪哩,我火速辦了婚禮,結(jié)果婚禮上第晰,老公的妹妹穿的比我還像新娘锁孟。我一直安慰自己,他們只是感情好茁瘦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布罗岖。 她就那樣靜靜地躺著,像睡著了一般腹躁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上南蓬,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天纺非,我揣著相機(jī)與錄音哑了,去河邊找鬼。 笑死烧颖,一個(gè)胖子當(dāng)著我的面吹牛弱左,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炕淮,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拆火,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了涂圆?” 一聲冷哼從身側(cè)響起们镜,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎润歉,沒想到半個(gè)月后模狭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡踩衩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年嚼鹉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驱富。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锚赤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出褐鸥,到底是詐尸還是另有隱情线脚,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布晶疼,位于F島的核電站酒贬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翠霍。R本人自食惡果不足惜锭吨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寒匙。 院中可真熱鬧零如,春花似錦、人聲如沸锄弱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)会宪。三九已至肖卧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掸鹅,已是汗流浹背塞帐。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工拦赠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人葵姥。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓荷鼠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親榔幸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子允乐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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