一:消息轉(zhuǎn)發(fā) - 方法的實(shí)現(xiàn)沒有實(shí)現(xiàn)
為什說消息轉(zhuǎn)發(fā)呢? 從上一篇方法查找流程中可以看到,如果找不到方法的實(shí)現(xiàn),則app會(huì)崩潰,為了不讓APP崩潰,所以我們來研究下方法的實(shí)現(xiàn)沒有實(shí)現(xiàn)的情況下:
/*
///1.慢速流程
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
*/
/*
///2.方法有聲明,但沒有實(shí)現(xiàn)
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// 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;
}
*/
/*
///3.動(dòng)態(tài)方法決議 (分對象方法和類方法)
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 對象方法 決議
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 類方法 決議
_class_resolveClassMethod(cls, sel, inst);
//判斷
if (!lookUpImpOrNil(cls, sel, inst,
NO, YES, NO))
{
// 對象方法 決議
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
*/
可以看到最后都走了_class_resolveInstanceMethod(cls, sel, inst);這個(gè)函數(shù)
///動(dòng)態(tài)方法決議
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//容錯(cuò)處理
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 系統(tǒng)給你一次機(jī)會(huì) - 你要不要針對 sel 來操作一下下
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//可以看到在這里走了這個(gè)
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//容錯(cuò)處理
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
以上可以看出來在獲取imp之前走了 bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
所以走了SEL_resolveInstanceMethod中的resolveInstanceMethod方法,我們來搜索一下resolveInstanceMethod


從這里可已看到NSObject實(shí)現(xiàn)了resolveInstanceMethod這個(gè)方法,所以如果我們重寫這個(gè)方法,做些處理是否可以避免崩潰呢,來看下邊
二:消息轉(zhuǎn)發(fā) - 對象的resolveInstanceMethod重寫
///4.對象方法動(dòng)態(tài)決議(此方法寫在沒有實(shí)現(xiàn)的類的.m文件里)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"來了對象方法:%s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(saySomething)) {
NSLog(@"說話了");
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
運(yùn)行下app,可以看到

從上可以看到這樣是可行的,那么類方法的呢?
三:消息轉(zhuǎn)發(fā) - 類的class_resolveClassMethod重寫
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 類方法 決議
_class_resolveClassMethod(cls, sel, inst);
//判斷
if (!lookUpImpOrNil(cls, sel, inst,
NO, YES, NO))
{
// 對象方法 決議
_class_resolveInstanceMethod(cls, sel, inst);
}
}
從上邊可以看到先走的 _class_resolveClassMethod(cls, sel, inst);所以搜索下:
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
可以看到這個(gè)方法跟對象的resolveInstanceMethod非常想
//重點(diǎn)
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
所以搜索下resolveClassMethod


這個(gè)又是系統(tǒng)給實(shí)現(xiàn)了,所以我們可以在里面做一番操作
///5.類方法動(dòng)態(tài)決議
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"來了類方法:%s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(sayLove)) {
NSLog(@"說- 說你你愛我");
IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
Method sayHMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
const char *sayHType = method_getTypeEncoding(sayHMethod);
// 類方法在元類 objc_getMetaClass("LGStudent")
return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayHIMP, sayHType);
}
return [super resolveClassMethod:sel];
}
運(yùn)行下app

可以看到?jīng)]有崩潰
總結(jié):
/*
類方法 如果找不到 - 動(dòng)態(tài)方法決議
-> 類 -> 元類 -> NSObject
1: resolveClassMethod 你是否處理 -> 只要注意元類
2: resolveClassMethod 沒有處理 -> resolveInstanceMethod
*/
四:方法崩潰下走了哪些
2020-03-18 17:57:19.053411+0800 LGTest[20115:862215] -[LGStudent saySomething]: unrecognized selector sent to instance 0x1006a82e0
2020-03-18 17:57:19.054268+0800 LGTest[20115:862215] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGStudent saySomething]: unrecognized selector sent to instance 0x1006a82e0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3c9ca8ab __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff72c84805 objc_exception_throw + 48
2 CoreFoundation 0x00007fff3ca49b61 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff3c92eadf ___forwarding___ + 1427
4 CoreFoundation 0x00007fff3c92e4b8 _CF_forwarding_prep_0 + 120
5 LGTest 0x0000000100000dc6 main + 70
6 libdyld.dylib 0x00007fff73ff27fd start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
或崩潰的堆棧

從上邊可以看到崩潰之前有走forwarding, _CF_forwarding_prep_0
然后我們想一下怎么更清晰的分析下
1. _class_lookupMethodAndLoadCache3
2. lookUpImpOrForward
3.log_and_fill_cache
都走這個(gè)方法
//log和填充緩存
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
//打印消息
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
///消息發(fā)送
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
// objcMsgLogEnabled 如果是TRUE則開啟日志
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
///flag
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
可以看到objcMsgLogEnabled 如果是TRUE則開啟日志,所以外部調(diào)用修改一下,就會(huì)將生成的日志存入/tmp/msgSends中
///1.函數(shù)方法的擴(kuò)充
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [LGStudent alloc] ;
// 動(dòng)態(tài)方法決議
// 對象方法
// 類方法
instrumentObjcMessageSends(true);//開啟日志
[student saySomething];
instrumentObjcMessageSends(false);//關(guān)閉日志
}
return 0;
}
運(yùn)行下程序生成的日志在
在主屏幕空白處 command + shift + g 輸入以下地址
地址為: /private/tmp

打開里面可以看到

有此可知系統(tǒng)崩潰前走了這些方法
///對象
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
///類
- LGStudent NSObject class
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
///系統(tǒng)的報(bào)錯(cuò)方法(傳0不報(bào)錯(cuò),有方法報(bào)錯(cuò))
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
///以下為崩潰的系統(tǒng)信息了
- LGStudent NSObject class
- OS_xpc_serializer OS_xpc_object dealloc
- OS_object NSObject dealloc
+ OS_xpc_payload NSObject class
- OS_xpc_payload OS_xpc_payload dealloc
- NSObject NSObject dealloc
所以我們可以在奔潰前在這些方法里做處理,已防止奔潰,以下崩潰前的消息轉(zhuǎn)發(fā)(特殊處理)
//第一種,動(dòng)態(tài)解析,在里邊處理(上一章7講過)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
return [super resolveInstanceMethod:sel];
}
//第二種
// 別人可能有?(1不處理走2)
// 快速流程 - 交給一個(gè)對象來處理
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
//第三種
// 慢速轉(zhuǎn)發(fā) --> 漂流瓶 (這兩步是一起使用的)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//漂流瓶
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"漂流瓶: %s",__func__);
//事情 - 事務(wù) - 秘書 - 失效
//系統(tǒng)本質(zhì)
SEL aSelector = [anInvocation selector];
//判斷
if ([[LGTeacher alloc] respondsToSelector:aSelector]) {
//交給別人處理
[anInvocation invokeWithTarget:[LGTeacher alloc]];
}
else {
//系統(tǒng)
[super forwardInvocation:anInvocation];
}
}
總結(jié):
/*
imp = 方法名 + 簽名
*/
/*
1.方法查找流程:
1: objc_msgSend -> 查找imp -> 緩存
2: 慢速遞歸 -> 沒有 -> 即將奔潰 -> 開始特殊處理
*/
/*
2.崩潰前的特殊處理->消息轉(zhuǎn)發(fā):
1: 你是不是有特殊處理
2: 有沒有你交給別人處理
3: 意味著你不想處理 : forwardingTargetForSelector
1: methodSignatureForSelector
2: forwardInvocation : anInvocation
3: 在里面處理就OK
4: 簡單的不喜歡這個(gè)方法而已 : 系統(tǒng)就不會(huì)分發(fā)這個(gè)事務(wù)
4: doesNotRecognizeSelector
*/
五:最后介紹一種iOS逆向生成iOS偽代碼的方式
先找到CoreFoundation


然后找一個(gè)逆向編碼工具,將CoreFoundation拖進(jìn)去
推薦工具:
hopper
