iOS小知识之如何挽救实例方法

263 阅读2分钟

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

当调用实例对象未实现的方法时,会报出一个经典错误unrecognized selector sent to instance,在消息处理机制中,当系统找不到方法,最先进入方法动态决议的流程:

  • 系统提供给开发者的挽救机会
  • 判断当前cls是否为元类
  • 如果不是元类,调用类对象的resolveInstanceMethod方法
  • 否则,是元类,调用类对象的resolveClassMethod方法
  • 如果未能解决,调用类对象所属元类的resolveInstanceMethod方法

实例方法\

static void resolveInstanceMethod(id inst, SEL sel, Class cls) { 
    runtimeLock.assertUnlocked(); 
    ASSERT(cls->isRealized()); 
    SEL resolve_sel = @selector(resolveInstanceMethod:); 
    
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) { 
        return; 
    } 
    
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; 
    bool resolved = msg(cls, resolve_sel, sel);
    
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); 
}
  • 入参:
    • inst:实例对象
    • sel:找不到的实例对象
    • cls:类对象
  • 调用lookUpImpOrNilTryCache函数,内部调用_lookUpImpTryCache函数,对当前类对象的resolveInstanceMethod方法进行消息慢速查找
    • 在NSObject中,该方法已默认实现
    • 继承自NSObject的类对象,不会被return拦截
  • 系统使用objc_msgSend,发送resolveInstanceMethod消息
    • 消息接收者为类对象
    • 消息主体中的SELresolveInstanceMethod
    • 参数为找不到的实例方法
  • 调用lookUpImpOrNilTryCache函数,对之前找不到的实例方法进行消息慢速查找
    • 如果在resolveInstanceMethod成功处理,返回处理后的imp
    • 如果依然找不到方法,返回_objc_msgForward_impcache函数地址,进入消息转发流程 案例
      在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)resolveInstanceMethod:(SEL)sel{ 
    
    if(sel==@selector(sayNB)){ 
        NSLog(@"resolveInstanceMethod:%@,%@", self, NSStringFromSelector(sel)); 
        
        IMP imp = class_getMethodImplementation(self, @selector(say666)); 
        Method methodSay666 = class_getInstanceMethod(self, @selector(say666));
        const char *type = method_getTypeEncoding(methodSay666); 
        
        return class_addMethod(self, @selector(sayNB), imp, type); 
    }
    
    return [super resolveInstanceMethod:sel]; 
}

@end
  • 如果调用的实例方法为sayNB,动态添加sayNB方法,并将imp填充为say666的函数地址 在main函数中,调用实例对象per的sayNB方法
int main(int argc, const char * argv[]) { 
    @autoreleasepool {
        LGPerson *per= [LGPerson alloc]; 
        [per sayNB]; 
    }
    return 0; 
}

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

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