關(guān)于Runtime的概念網(wǎng)上很多資料可供參考,本文不做介紹。文章將重點(diǎn)放在核心源碼的分析及梳理消息轉(zhuǎn)發(fā)機(jī)制,所以本文需要一定的Runtime基礎(chǔ)闪朱。
一、 對(duì)象及方法本質(zhì)
1. 首先我們用最簡(jiǎn)單的對(duì)象調(diào)用方法來(lái)一步一步深入解析:
#import <Foundation/Foundation.h>
#include <objc/runtime.h>
#import "Person.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Person*p = [[Person alloc]init];
[p run];
return 0;
}
}
2. 通過(guò)下面的命令將.m文件轉(zhuǎn)換為C++文件:
clang -rewrite-objc main.m -o test.c++
3. 編譯的文件有98906行钻洒,最終main的核心文件如下:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person*p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
return 0;
}
}
- 3.1 由此可得奋姿,對(duì)象調(diào)用方法的本質(zhì)是發(fā)送消息:
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
//(void *)objc_msgSend)((id)p 消息接受者
// sel_registerName("run") 方法編號(hào)
- 3.2 對(duì)象的本質(zhì)是結(jié)構(gòu)體:
#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
二、Runtime核心源碼解析
1. 通過(guò)在objc源碼中搜索arm64架構(gòu)下的_objc_msgSend,在ENTRY _objc_msgSend,首先會(huì)進(jìn)行緩存檢查和類型判斷航唆,LNilOrTagged此處會(huì)判斷是否為nil或taggedPoint類型胀蛮,如果是,則直接返回糯钙。taggedPoint是用來(lái)存儲(chǔ)小值類型粪狼,其地址中包含值和類型數(shù)據(jù),可以進(jìn)行快速的訪問(wèn)數(shù)據(jù)任岸,提高性能再榄。
LGetIsaDone:
CacheLookup NORMAL
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
2. 如果不為nil,通過(guò)匯編指令b LGetIsaDone跳轉(zhuǎn)到CacheLookup,來(lái)對(duì)緩存進(jìn)行快速的查找享潜,如果有緩存就直接返回困鸥,由于這一步是通過(guò)匯編執(zhí)行,所以是快速查找剑按,效率很高疾就,查找過(guò)程如下圖:
3. 如果緩存沒(méi)有,將會(huì)進(jìn)入慢速查找過(guò)程艺蝴,核心方法為MethodTableLookup
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
//方法列表
MethodTableLookup
br x17
END_ENTRY __objc_msgSend_uncached
4. 通過(guò)MethodTableLookup方法的調(diào)用猬腰,會(huì)從匯編跳轉(zhuǎn)到C/C++,所以說(shuō)runtime是由匯編猜敢,C/C++組成的一套API
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
5. 接下來(lái)看下lookUpImpOrForward核心代碼:
retry:
runtimeLock.assertReading();
// 如果緩存中有IMP姑荷,直接返回
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 如果沒(méi)有,先找自己的IMP,找到加入方法緩存
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 自己沒(méi)有缩擂,找父類鼠冕,一直找到NSObject,因?yàn)镹SObject的父類為nil,下面的條件是curClass != nil
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// 內(nèi)存溢出相關(guān)
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 先從父類緩存中找IMP
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 在父類中找到IMP,加入緩存
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 沒(méi)有IMP,調(diào)用一次resolver動(dòng)態(tài)方法解析,通過(guò)triedResolver變量來(lái)控制該方法只走一次
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// 沒(méi)有IMP胯盯,也沒(méi)有resolver,調(diào)用forwarding進(jìn)行消息轉(zhuǎn)發(fā)
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
-
5.1 過(guò)程就是先找自己懈费,如果自己沒(méi)有IMP,然后找父類的緩存,如果沒(méi)有博脑,循環(huán)查找父類的IMP憎乙,一直找到NSObject薄坏,如果還是沒(méi)有,接下來(lái)就開始動(dòng)態(tài)方法解析寨闹,如果動(dòng)態(tài)方法解析沒(méi)有實(shí)現(xiàn),接下來(lái)再調(diào)用消息轉(zhuǎn)發(fā)君账,流程如下圖:
6. 動(dòng)態(tài)方法解析調(diào)用如下
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
NSLog(@"===== 對(duì)象方法解析 ========");
SEL myRunSEL = @selector(myRun);
Method myRunSELM= class_getInstanceMethod(self, myRunSEL);
IMP myRunImp = method_getImplementation(myRunSELM);
const char *type = method_getTypeEncoding(myRunSELM);
return class_addMethod(self, sel, myRunImp, type);
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(run)) {
NSLog(@" ====== 類方法解析 =======");
SEL myRunSEL = @selector(myRun);
//打印hellowordM1和hellowordM會(huì)發(fā)現(xiàn)地址一樣
//說(shuō)明類方法在元類中是以實(shí)例方法的形式存在的
// Method hellowordM1= class_getClassMethod(self, hellowordSEL);
Method myRunM= class_getInstanceMethod(object_getClass(self), myRunSEL);
IMP myRunImp = method_getImplementation(myRunM);
const char *type = method_getTypeEncoding(myRunM);
NSLog(@"%s",type);
return class_addMethod(object_getClass(self), sel, myRunImp, type);
}
return [super resolveClassMethod:sel];
}
7. 消息轉(zhuǎn)發(fā)調(diào)用如下:
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
// if (aSelector == @selector(run)) {
// return [Person new];
// }
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(run)) {
// forwardingTargetForSelector 沒(méi)有實(shí)現(xiàn) 就只能方法簽名了
Method method = class_getInstanceMethod(object_getClass(self), @selector(readBook));
const char *type = method_getTypeEncoding(method);
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s",__func__);
NSLog(@"------%@-----",anInvocation);
anInvocation.selector = @selector(readBook);
[anInvocation invoke];
}
8. 消息轉(zhuǎn)發(fā)流程圖如下:
三繁堡、使用runtime實(shí)現(xiàn)萬(wàn)能跳轉(zhuǎn)
1. 首先模擬簡(jiǎn)單的后臺(tái)返回?cái)?shù)據(jù),下面的QinzVC為項(xiàng)目中不存在的控制器:
self.dataArr = @[
@{@"class":@"SecondVC",
@"data":@{@"name":@"小明"}},
@{@"class":@"QinzVC",
@"data":@{@"name":@"我是動(dòng)態(tài)創(chuàng)建的控制器"}},
];
2. 針對(duì)存在的控制器和不存在的控制器進(jìn)行跳轉(zhuǎn)乡数,下面代碼中有詳細(xì)注釋:
- (void)pushToAnyVCWithData:(NSDictionary *)dataDict{
//1.獲取類名
const char *clsName = [dataDict[@"class"] UTF8String];
//2.通過(guò)類型獲取類
Class cls = objc_getClass(clsName);
//3. 如果不存在椭蹄,使用runtime動(dòng)態(tài)創(chuàng)建類
if (!cls) {
Class superClass = [UIViewController class];
cls = objc_allocateClassPair(superClass, clsName, 0);
//添加屬性
class_addIvar(cls, "name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
class_addIvar(cls, "showLB", sizeof(UILabel *), log2(sizeof(UILabel *)), @encode(UILabel *));
//注冊(cè)類
objc_registerClassPair(cls);
//添加方法
Method method = class_getInstanceMethod([self class], @selector(myInstancemethod));
IMP methodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
BOOL result = class_addMethod(cls, @selector(viewDidLoad), methodIMP, types);
if (result) {
NSLog(@"=== 方法添加成功 =====");
}
}
// 實(shí)例化對(duì)象
id instance = nil;
@try {
//先嘗試從SB中加載
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
instance = [sb instantiateViewControllerWithIdentifier:dataDict[@"class"]];
} @catch (NSException *exception) {
//SB中沒(méi)有直接初始化
instance = [[cls alloc] init];
} @finally {
NSLog(@"控制器實(shí)例化完成");
}
//獲取后臺(tái)返回的數(shù)據(jù),給屬性賦值
NSDictionary *dict = dataDict[@"data"];
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 檢測(cè)是否存在key的屬性
if (class_getProperty(cls, [key UTF8String])) {
[instance setValue:obj forKey:key];
}
// 檢測(cè)是否存在key的變量
else if (class_getInstanceVariable(cls, [key UTF8String])){
[instance setValue:obj forKey:key];
}
}];
[self.navigationController pushViewController:instance animated:YES];
}
- (void)myInstancemethod {
[super viewDidLoad];
[self setValue:[UIColor orangeColor] forKeyPath:@"view.backgroundColor"];
[self setValue:[[UILabel alloc] initWithFrame:CGRectMake(100, 300, 200, 44)] forKey:@"showLB"];
UILabel *showTextLB = [self valueForKey:@"showLB"];
[[self valueForKey:@"view"] addSubview:showTextLB];
//設(shè)置屬性
showTextLB.text = [self valueForKey:@"name"];
showTextLB.font = [UIFont systemFontOfSize:14];
showTextLB.textColor = [UIColor blackColor];
showTextLB.textAlignment = NSTextAlignmentCenter;
showTextLB.backgroundColor = [UIColor whiteColor];
}
3. 實(shí)現(xiàn)萬(wàn)能界面跳轉(zhuǎn)的演示如下:
總結(jié):runtime是由匯編净赴,C/C++組成的一套API绳矩,蘋果在發(fā)送消息做了很多優(yōu)化處理,目的是使消息的發(fā)送更加有效率玖翅,同時(shí)runtime的應(yīng)用也很廣泛翼馆,如實(shí)現(xiàn)上面的萬(wàn)能跳轉(zhuǎn),當(dāng)然還可以使用runtime實(shí)現(xiàn)頁(yè)面統(tǒng)計(jì)金度,全局改變字體大小应媚,逆向中進(jìn)行Hook等。
我是Qinz,希望我的文章對(duì)你有幫助猜极。