iOS小知识之如何挽救类方法

882 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

我们先看下方法动态决议中,类对象的resolveClassMethod方法。

static void resolveClassMethod(id inst, SEL sel, Class cls) { 
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized()); 
    ASSERT(cls->isMetaClass());
    
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) { 
        return; 
    }
    
    Class nonmeta; 
    { 
        mutex_locker_t lock(runtimeLock); 
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst); 
        
        if (!nonmeta->isRealized()) { 
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized", nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; 
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel); 
    
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); 
}
  • 入参:
    • inst:类对象
    • sel:找不到的类方法
    • cls:元类
  • 调用lookUpImpOrNilTryCache函数,内部调用_lookUpImpTryCache函数,对当前类对象的resolveClassMethod方法进行消息慢速查找
    • 在NSObject中,该方法已默认实现
    • 继承自NSObject的类对象,不会被return拦截
  • 调用getMaybeUnrealizedNonMetalClass函数,验证当前类对象和元类的关系,返回一个普通类
  • 系统使用objc_msgSend,发送resolveClassMethod消息
    • 消息接收者为类对象
    • 消息主体中的SELresolveClassMethod
    • 参数为找不到的类方法
  • 调用lookUpImpOrNilTryCache函数,对之前找不到的类方法进行消息慢速查找
    • 如果在resolveInstanceMethod成功处理,返回处理后的imp
    • 如果找依然找不到方法,返回_objc_msgForward_impcache函数地址,进入消息转发流程 找到getMaybeUnrealizedNonMetaClass函数的定义
static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) { 
    static int total, named, secondary, sharedcache, dyld3;
    runtimeLock.assertLocked();
    ASSERT(metacls->isRealized()); 
    
    total++; 
    
    if (!metacls->isMetaClass()) return metacls; 
    
    if (metacls->ISA() == metacls) { 
        Class cls = metacls->getSuperclass();
        ASSERT(cls->isRealized()); 
        ASSERT(!cls->isMetaClass());
        ASSERT(cls->ISA() == metacls); 
        if (cls->ISA() == metacls) return cls; 
    } 
    
    if (inst) {
        Class cls = remapClass((Class)inst); 
        
        while (cls) { 
            if (cls->ISA() == metacls) { 
                ASSERT(!cls->isMetaClassMaybeUnrealized()); 
                return cls;
            }
            cls = cls->getSuperclass(); 
        } 
#if DEBUG 
        _objc_fatal("cls is not an instance of metacls");
#else 
        // release build: be forgiving and fall through to slow lookups 
#endif
    } 
    
    ... 
}
  • 判断cls,如果非元类,直接返回
  • 如果cls为元类,且isa指向自己,证明当前cls为根元类,获取其父类NSObject并返回
  • 遍历当前类及其父类,找到isa指向元类的所属类
  • 如果均为找到,DEBUG模式下,错误提示:当前类对象不是该元类的实例
  • Release模式下,按以下流程查找该元类的类对象
    • 查看元类是否存在指向其非元类的指针,存在直接返回
    • 按照元类的mangledName查找类对象,如果存在且isa指向元类,将其返回
    • 在全局Map中查找类对象,存在将其返回
    • 在dyld的closure table中查找类对象,存在将其返回
    • 在共享缓存中查找类对象,存在将其返回
    • 以上流程均未找到,错误提示:没有指向该元类的类 案例 在LGPerson.h中,声明sayNB类方法
#import <Foundation/Foundation.h>

@interface LGPerson : NSObject 

+(void)sayNB; 

@end

在LGPerson.m中,实现say666实例方法和resolveInstanceMethod类方法,未实现sayNB实例方法

#import "LGPerson.h" 
#import <objc/runtime.h> 

@implementation LGPerson 

-(void)say666{ 
    NSLog(@"实例方法-say666");
} 

+ (BOOL)resolveClassMethod:(SEL)sel{ 

    if(sel==@selector(sayNB)){ 
        NSLog(@"resolveClassMethod:%@,%@", self, NSStringFromSelector(sel)); 
        
        IMP imp = class_getMethodImplementation(self, @selector(say666));
        Method methodSay666 = class_getInstanceMethod(self, @selector(say666)); 
        const char *type = method_getTypeEncoding(methodSay666); 
        const char * c = NSStringFromClass(self).UTF8String;
        return class_addMethod(objc_getMetaClass(c), @selector(sayNB), imp, type); 
    }
    
    return [super resolveClassMethod:sel];
} 

@end
  • 如果调用的类方法为sayNB,动态添加sayNB方法,并将imp填充为say666的函数地址
  • 由于需要添加的sayNB是类方法,所以需要在元类中添加

在main函数中,调用LGPerson的sayNB类方法

int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
        [LGPerson sayNB]; 
    }
    return 0; 
} 

------------------------- 

//输出结果: 
实例方法-say666
  • 自动进入resolveClassMethod方法