【凤凰社】iOS底层原理(五)Runtime(下)

动态方法解析

如果消息发送阶段不成功,那么就会进入到动态方法解析阶段

【第一步】 我们还是先从objc源码里找到函数resolveMethod_locked来看,分别对应着类对象和元类对象做了不同的调用处理

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {

    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    // 不是元类对象
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else { // 是元类对象
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        
        // 这句印证了在元类对象里找类方法找不到,会去类对象里找同名的对象方法
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

注意: 可以调用到这里,说明已经找到基类的元类对象了,如果还是没有,那么就会去基类的类对象里找同名的对象方法,正好印证了之前分析的元类对象的superclass指针指向类对象的原理

【第二步】 如果是类对象则会进入resolveInstanceMethod函数中,会走消息发送的流程去查找是否有实现resolveInstanceMethod方法,如果没有实现则返回;如果有实现就发送消息调用resolveInstanceMethod方法,而且再次走消息发送流程查找是否有实现对应的实例方法

static void resolveInstanceMethod(id inst, SEL sel, Class cls) {

    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    // 发送消息的容错处理
    // 会走消息发送的流程去找是否有实现resolveInstanceMethod方法,如果有实现才会往下Run
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    // Beacuse已经有实现resolveInstanceMethod,So发送消息调用resolveInstanceMethod
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    // 再次查找是否有实现对应的实例方法
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
    
    // 拿到resolveInstanceMethod的调用返回值,只是为了是否打印,无其他意义
    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));
        }
    }
}

下面是resolveInstanceMethod的一些详细调用解析

1.多次调用lookUpImpOrNilTryCache的方法实现,内部会再次调用_lookUpImpTryCache函数

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

2.在_lookUpImpTryCache中,会先去缓存中查找,如果没有还是会走消息发送流程的调用函数lookUpImpOrForward,详细分析请查看上篇文章

static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

    // 查看是否有缓存
    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

【第二步】 如果是元类对象则会进入resolveClassMethod函数中,同类对象的动态方法解析大体相似;先会走消息发送的流程去查找是否有实现resolveClassMethod方法,如果没有实现则返回;如果有实现就发送消息调用

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        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);

    // 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 = lookUpImpOrNilTryCache(inst, sel, cls);

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

为了验证源码实现,我们还是先创建示例代码来看

1.创建一个Person类,添加test对象方法,并实现resolveInstanceMethod方法,然后动态添加一个other函数

@interface Person : NSObject

- (void)test;
@end

@implementation Person

- (void)other
{
    NSLog(@"%s", __func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 获取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));

        // 动态添加test方法的实现
        class_addMethod(self, sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

知识点: Method的底层实现struct method_t类型,So可以理解为等价于struct method_t *

2.在main函数中调用[person test],运行程序可以发现,控制台会打印other函数已经被调用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person test];
    }
    return 0;
}
总结

从示例代码我们可以知道,利用Runtime的消息发送机制来动态增加一些方法的实现和调用

整个动态方法分析的流程可以用下图表述

-w796

消息转发

如果没有进行动态分析的代码实现,就会进入到消息转发阶段

消息转发的源码由于是不开源的,So我们只可以通过一些其他的方法来分析其内部的实现

实现步骤

1.首先我们先通过方法崩溃的日志打印来查看消息转发都对应调用了哪些方法

我们注释掉Person.m文件里的resolveInstanceMethod实现,再次运行程序发现,程序崩溃并打印经典错误信息

-[Person test]: unrecognized selector sent to instance 0x1018331d0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x1018331d0'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff204a16af __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007fff201d93c9 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff20523c85 -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff2040906d ___forwarding___ + 1467
	4   CoreFoundation                      0x00007fff20408a28 _CF_forwarding_prep_0 + 120
	
	6   libdyld.dylib                       0x00007fff2034a631 start + 1
	7   ???                                 0x0000000000000001 0x0 + 1
)

从上面的调用栈打印信息我们可以看出,系统会先去调用CoreFoundation框架中的___forwarding___

2.我们可以通过逆向工具HopperCoreFoundation框架进行反汇编,通过一系列操作,可以得到__forwarding_prep_0___的伪代码

源码分析

下面是伪代码的实现

// 伪代码的实现
int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            if (isStret == 1) {
                int ret;
                objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
                return ret;
            }
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 僵尸对象
    const char *className = class_getName(receiverClass);
    const char *zombiePrefix = "_NSZombie_";
    size_t prefixLen = strlen(zombiePrefix); // 0xa
    if (strncmp(className, zombiePrefix, prefixLen) == 0) {
        CFLog(kCFLogLevelError,
              @"*** -[%s %s]: message sent to deallocated instance %p",
              className + prefixLen,
              selName,
              receiver);
        <breakpoint-interrupt>
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature) {
            BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
            if (signatureIsStret != isStret) {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
                      selName,
                      signatureIsStret ? "" : not,
                      isStret ? "" : not);
            }
            if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
                NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

                [receiver forwardInvocation:invocation];

                void *returnValue = NULL;
                [invocation getReturnValue:&value];
                return returnValue;
            } else {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
                      receiver,
                      className);
                return 0;
            }
        }
    }

    SEL *registeredSel = sel_getUid(selName);

    // selector 是否已经在 Runtime 注册过
    if (sel != registeredSel) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
              sel,
              selName,
              registeredSel);
    } // doesNotRecognizeSelector
    else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }
    else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
              receiver,
              className);
    }

    // The point of no return.
    kill(getpid(), 9);
}

1.首先会查看是否实现forwardingTargetForSelector方法,如果实现了该方法而且返回值不为nil,那么会调用objc_msgSend进行消息发送流程;如果该方法未实现或者返回值为nil,就会调用方法签名methodSignatureForSelector

2.如果methodSignatureForSelector的返回值不为nil,那么就会调用forwardInvocation,如果该方法未实现或者返回值为nil,那么就会调用doesNotRecognizeSelectordoesNotRecognizeSelector里就会进行崩溃报错

3.如果forwardInvocation未实现,也会进行崩溃报错

4.为了验证上述源码分析,在增加一个Cat类,并实现test方法

@interface Cat: NSObject

- (void)test;
@end

@implementation Cat

- (void)test {
    NSLog(@"%s", __func__);
}
@end

Person.mm文件里对应实现这三个函数,并分别返回nil或者注释掉函数实现,然后运行程序发现,和上述分析相同

@implementation Person

//+ (BOOL)resolveInstanceMethod:(SEL)sel
//{
//    class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
//}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"%s", __func__);

    if (aSelector == @selector(test)) {
        // objc_msgSend([[Cat alloc] init], aSelector)
        return nil;
        //[[Cat alloc] init];
        //[[NSObject alloc] init];
    }

    return [super forwardingTargetForSelector:aSelector];
}

// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s", __func__);
    
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包含:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"%s", __func__);
//    anInvocation.target = [[Cat alloc] init];
//    [anInvocation invoke]; Run方法

    [anInvocation invokeWithTarget:[[Cat alloc] init]];
   // [anInvocation invokeWithTarget:[[NSObject alloc] init]];
}

@end

方法签名methodSignatureForSelector返回值的多种写法

// 省略types类型字节大小
[NSMethodSignature signatureWithObjCTypes:"i@:i"]

// 用默认的aSelector传递
[[[Cat alloc] init] methodSignatureForSelector:aSelector];

forwardInvocation会通过NSInvocation类型的参数拿到整个方法的调用者、方法名以及方法参数

// 更改调用者对象
[anInvocation invokeWithTarget:[[Cat alloc] init]];

// 拿到参数信息,传递的是地址值
int age;
[anInvocation getArgument:&age atIndex:2];

// 拿到返回值信息
int ret;
[anInvocation getReturnValue:&ret];

注意:

  • forwardingTargetForSelector的返回值改为NSObject对象,发现也一样会崩溃报错;说明了如果返回值的类型也找不到对应的函数实现,会重新走消息发送的流程然后最后崩溃报错
  • forwardInvocation的实现里可以做任何事,只要实现了该函数,就不会崩溃报错;前提是不会进行未实现方法的invokeWithTarget对象调用
  • 以上方法都有对象方法、类方法2个版本
总结

整个消息转发分析的流程可以用下图表述

面试题

1.下面这段代码的self和super分别对应着是谁,说下原理

@interface Person : NSObject

- (void)run;
@end

@implementation Person

- (void)run {
    NSLog(@"%s", __func__);
}
@end

@interface Student: Person

@end

@implementation Student

- (instancetype)init {

    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]); // Student
        NSLog(@"[self superclass] = %@", [self superclass]); // Person

        NSLog(@"[super class] = %@", [super class]); // Student
        NSLog(@"[super superclass] = %@", [super superclass]); // Person
    }
    return self;
}

- (void)run {

    [super run];
}
@end

将这段代码转成C++文件后可以发现,run方法的底层会调用objc_msgSendSuper函数

static void _I_MJStudent_run(MJStudent * self, SEL _cmd) {
//    objc_msgSendSuper(__rw_objc_super { self, [Person class]) },
//                      sel_registerName("run")
//                      );
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_MJStudent_69ea3c_mi_0);
}

我们还发现其中一个参数是__rw_objc_super的结构体类型,里面的两个成员正好对着selfclass_getSuperclass(objc_getClass("Student")),也就是Student对象父类Person对象

struct __rw_objc_super { 
	struct objc_object *object; 
	struct objc_object *superClass; 
	__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

我们在objc源码objc-msg-arm64.s文件里可以查找到关于objc_msgSendSuper函数的实现

ENTRY _objc_msgSendSuper
	UNWIND _objc_msgSendSuper, NoFrame

	ldp	p0, p16, [x0]		// p0 = real receiver, p16 = class
	b L_objc_msgSendSuper2_body

	END_ENTRY _objc_msgSendSuper

	// no _objc_msgLookupSuper

	ENTRY _objc_msgSendSuper2
	UNWIND _objc_msgSendSuper2, NoFrame

#if __has_feature(ptrauth_calls)
	ldp	x0, x17, [x0]		// x0 = real receiver, x17 = class
	add	x17, x17, #SUPERCLASS	// x17 = &class->superclass
	ldr	x16, [x17]		// x16 = class->superclass
	AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS
LMsgSendSuperResume:
#else
	ldp	p0, p16, [x0]		// p0 = real receiver, p16 = class
	ldr	p16, [x16, #SUPERCLASS]	// p16 = class->superclass
#endif
L_objc_msgSendSuper2_body:
	CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached

	END_ENTRY _objc_msgSendSuper2	

发现最终会调用_objc_msgSendSuper2,会根据当前类型的superclass指针去查找父类方法来调用;而传进来的第一个结构体变量的真实类型是objc_super2

struct objc_super2 {
    id receiver;
    Class current_class;
};

我们可以在objc源码NSObject.mm中查看class方法的实现,就是获取当前调用者的类型,那传进去的self就是当前调用者,So不论是[self class]还是[super class]得到的都是当前调用者的类型,也就是Student类型

+ (Class)class {
    return self;
}

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

superclass方法的实现就是获取当前调用者类型的父类类型,So[self superclass]还是[super superclass]得到的都是Person类型

+ (Class)superclass {
    return self->getSuperclass();
}

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

2.以下代码可以不可以Run成功?如果可以,打印结果是什么?

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;
- (void)print;
@end

@implementation Person

- (void)print
{
    NSLog(@"my name is %@", self->_name);
    // 可以调用成功,输入结果为 my name is <ViewController: 0x7fcfc2a04720>
}
@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
	[super viewDidLoad];
    
   	id cls = [Person class];

   	void *obj = &cls;

   	[(__bridge id)obj print];
}

@end

将上述代码用下图来表示关系可以看出,创建一个Person对象Person *person = [[Person alloc] init]等同于obj指向的[Person class],Soobj可以直接调用print函数

然后我们知道viewDidLoad中的几个局部变量的内存地址都是从大到小的排列,So可以用下图来表示

[super viewDidLoad]的本质就是调用objc_msgSendSuper2的函数,函数的第一个参数就是一个类似于下面代码的结构体变量

struct objc_super2 {   self,   [ViewController class]};

So在内存中的分布也就是如下图所示

Person结构体的本质如下面代码所示,里面的成员变量也是由低到高来排列;Soself->_name就是在结构体里跳过isa指针找到_name成员变量,也就相等于obj跳过cls找到结构体变量里的self,那么取得值就是ViewController的内存地址

struct Person_IMPL
{
   Class isa;
   NSString *_name;
};

3.Runtime的具体应用

利用关联对象(AssociatedObject)给分类添加属性遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)交换方法实现(交换系统的方法,监听按钮多次点击事件)利用消息转发机制解决方法找不到的异常问题
1.替换类

@interface Person: NSObject
- (void)run;
@end

@implementation Person
- (void)run
{
    NSLog(@"%s", __func__);
}
@end

@interface Car: NSObject
- (void)run;
@end

@implementation Car
- (void)run
{
    NSLog(@"%s", __func__);
}
@end

Person *person = [[Person alloc] init];
[person run];
    
object_setClass(person, [Car class]);
[person run];

// 分别输出 [Person run],[Car run]

2.判断是否为类对象

NSLog(@"%d %d %d",
          object_isClass(person),
          object_isClass([Person class]),
          object_isClass(object_getClass([Person class]))
          );

3.动态创建类

void run(id self, SEL _cmd)
{
    NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd));
}

// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "Dog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");

// 注册类
objc_registerClassPair(newClass);

id dog = [[newClass alloc] init];
[dog setValue:@10 forKey:@"_age"];
[dog setValue:@20 forKey:@"_weight"];
[dog run];

// 在不需要这个类时释放
// objc_disposeClassPair(newClass);

注意: 由于成员变量是只读属性,So必须在注册类之前添加;为了规范,属性、成员变量、方法都最好在注册类之前添加好

4.成员变量相关的用法

// 获取成员变量信息
Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));

// 设置和获取成员变量的值
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");

Person *person = [[Person alloc] init];
object_setIvar(person, nameIvar, @"123");

// 不可以直接传基本数据类型,So先转成指针类型,再变为id类型
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
    NSLog(@"%@ %d", person.name, person.age);

// 获取所有成员变量信息
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
   // 取出i位置的成员变量
   Ivar ivar = ivars[i];
   NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);

5.方法实现的置换

void myrun()
{
    NSLog(@"---myrun");
}

Person *person = [[Person alloc] init];
    
class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
    
// class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
//     NSLog(@"123123");
// }), "v");
    
[person run];

将类里的两个方法实现交换

@implementation Person

- (void)run
{
    NSLog(@"%s", __func__);
}

- (void)test
{
    NSLog(@"%s", __func__);
}

@end

Person *person = [[Person alloc] init];
        
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);

[person run];

利用方法交换进行容错

@interface NSMutableArray (Extension)

@end

@implementation NSMutableArray (Extension)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(ll_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)ll_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self ll_insertObject:anObject atIndex:index];
}

@end

我们在objc4源码里可以看到该方法的实现,就是将两个函数的IMP交换,而且清空缓存

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    mutex_locker_t lock(runtimeLock);

    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();

    m1->setImp(imp2);
    m2->setImp(imp1);


    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?

    flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
        return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
    });

    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}

在这个函数里清空缓存数据

static void flushCaches(Class cls, const char *func, bool (^predicate)(Class))
{
    runtimeLock.assertLocked();
#if CONFIG_USE_CACHE_LOCK
    mutex_locker_t lock(cacheUpdateLock);
#endif

    const auto handler = ^(Class c) {
        if (predicate(c)) {
            // 清空数据
            c->cache.eraseNolock(func);
        }

        return true;
    };

    if (cls) {
        foreach_realized_class_and_subclass(cls, handler);
    } else {
        foreach_realized_class_and_metaclass(handler);
    }
}

0 评论

回复