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 { |
参考资料