OC是动态语言,只有在运行时才会根据方法名去调用方法,称为给方法发送消息,是因为当调用[demoObj setTest]方法时,编译器会转化为objc_msgSend(demoObj, @selector(setTest))。如果携带参数,如[demoObj setTest:str],会转化为objc_msgSend(demoObj, @selector(setTest), str)。
unrecognized selector sent to instance
unrecognized selector sent to instance是开发中经常遇到的异常,诸如点击事件没有实现,调用了只声明未实现的方法,向NSArray调用了NSMutableArray的方法等等。
我们定义类DemoObject,在.h中声明方法- (void)setTest;,.m中不写方法实现。然后调用:
1 | DemoObject *demoObj = [[DemoObject alloc] init]; |
程序会很听话的崩掉,并抛出异常:
1 | -[DemoObject setTest]: unrecognized selector sent to instance 0x60000000bda0 |
在程序崩溃之前,消息会经过下面几个方法转发:
1 | + (BOOL)resolveInstanceMethod:(SEL)sel { |
消息会经过上述方法传递,最终未果才会崩溃。在上述方法中我们有三次机会操作消息传递,防止崩溃。
- 动态添加方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel或+ (BOOL)resolveClassMethod:(SEL)sel - 方法重定向:
- (id)forwardingTargetForSelector:(SEL)aSelector - 消息转发:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector和- (void)forwardInvocation:(NSInvocation *)anInvocation
动态添加方法
实例方法
在类DemoObject中引入runtime:#import <objc/runtime.h>,使用class_addMethod动态添加方法实现。
1 | void resolveTest(id self, SEL _cmd) { |
方法class_addMethod:
1 | OBJC_EXPORT BOOL |
参数意义如下:
- cls:消息接收者
- name:SEL方法名
- imp:要动态添加方法的IMP指针
- types:参数和返回值的符号字符串,查看格式文档
类方法
解析类方法如下:
1 | + (BOOL)resolveClassMethod:(SEL)sel { |
这里的区别在于cls:消息接收者,解析实例方法使用的[self class],解析类方法使用的object_getClass(self)。
当self为实例对象时,[self class]与object_getClass(self)等价,因为前者会调用后者。object_getClass([self class])得到元类。
当self为类对象时,[self class]返回值为自身,还是self,所以上面解析实例方法将[self class]换成self也可以。object_getClass(self)与object_getClass([self class])等价。
方法重定向
实例方法
重写- (id)forwardingTargetForSelector:(SEL)aSelector方法,可以将消息的接收者替换成其他对象。
新创建一个类DemoNewObject,将类DemoObject未实现的方法- (void)setTest,在.m中实现,无需在.h中暴露方法:
1 | - (void)setTest { |
方法重定向操作:
1 | - (id)forwardingTargetForSelector:(SEL)aSelector { |
类方法
类方法重定向需要重写+ (id)forwardingTargetForSelector:(SEL)aSelector方法,注意是+开头的类方法。
同样在新类DemoNewObject中实现类方法:
1 | + (void)setTestClass { |
重写重定向方法:
1 | + (id)forwardingTargetForSelector:(SEL)aSelector { |
方法重定向就是将当前类未实现的方法,重定向到一个实现该方法的新类中,调用新类中的方法实现。实例方法中返回实例对象,类方法中返回类对象。
消息转发
消息转发是通过方法- (void)forwardInvocation:(NSInvocation *)anInvocation实现的,它可以将不能处理的消息转发给其他对象处理,参数anInvocation是通过方法- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector产生的。
所以需要重写两个方法,- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector和- (void)forwardInvocation:(NSInvocation *)anInvocation:
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { |
参考资料