先把Method的内存模型摆出来:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name;
char* method_types;
IMP method_imp;
}
1,SEL-选择器
typedef struct objc_selector * SEL;
runtime并没有给出SEL的具体结构。实际上SEL是对方法名的一种映射。它出现的需求在于,方法的执行首先要找到方法Method结构体,在method的链表中,怎样去定位一个结构体。我们看Method的内存结构,一共三个字段。不用分析,只能通过method_name来定位到这个结构体,它是一个SEL,它是由方法名转化得来的。所以,SEL是用来定位Method的,进而执行Method的method_imp。
Objective-C的这种设计决定了Objective-C这门语言不支持重载。
SEL的相关操作:
const char * sel_getName ( SEL sel );
2,IMP-函数指针
id (*IMP)(id, SEL, ...)
3,Method的操作
3.1 执行一个方法
id method_invoke ( id receiver, Method m, ... );
3.2 获取方法名
SEL method_getName ( Method m );
3.3 获取Method的IMP
IMP method_getImplementation ( Method m );
3.4 获取Method的类型编码
const char * method_getTypeEncoding ( Method m );
3.5 获取方法返回值的类型字符串
char * method_copyReturnType ( Method m );
3.6 设置Method的imp
IMP method_setImplementation ( Method m, IMP imp );
3.7 交换Method的imp
void method_exchangeImplementations ( Method m1, Method m2 );
4,消息发送
Objective-C中的方法调用都是在runtime动态调用的,实际上是执行了objc_msgSend方法。
objc_msgSend(receiver, selector)
如果有参数:
objc_msgSend(receiver, selector, arg1, arg2, ...)
这个方法会动态查询方法的imp,class_getMethodImplementation,如果正常流程没有查询到,就会返回msg_forward的imp,就会走消息转发流程。
具体细节是这样的:
当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,首先会在类的结构体的cache里面查找selector,如果没有查找到,就去方法链表里面查找方法的selector。如果当前类没有找到selector,则通过类结构体中的superClass指针找到其父类,在父类中依旧是先查找cache,然后查找方法链表,如果找不到,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实现。另外,在方法链表里面查询到的方法会被放到cache里面,提高selector的查找速度。如果最终还是找不到,就走消息转发流程。
5,消息转发
一般我们不确定一个实例对象是否响应某个方法的时候,我们会使用
- (BOOL)respondsToSelector:(SEL)aSelector;来确认一下,避免报unrecognized selector exception。
这里要讲的是,如果不调用respondsToSelector加保护,出现selector定位不到的时候怎么办,它的机制是怎么样的。
这个机制一共分三步,这一套机制NSObject里面提供了接口:
1,动态方法解析。--给没有实现的selector提供一种实现。
+ (BOOL)resolveInstanceMethod:(SEL)sel;//给实例selector提供一种实现
+ (BOOL)resolveClassMethod:(SEL)sel;//给类selector提供一种实现。
eg.
void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
2,备用接受者。--更换消息接受者。
- (id)forwardingTargetForSelector:(SEL)aSelector; //提供一个新的消息接受者。
基于runtime的这个机制,我们可以做一些松耦合的东西。比如做一个中心类,client调用中心类的方法,中心类通过这一机制进行方法分发。
3,消息重新转发。--这一步不仅可以更改消息接受者,还可以更改消息的参数。
- (void)forwardInvocation:(NSInvocation *)anInvocation;
此外需要重写:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
eg.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([ARespondClass instancesRespondToSelector:aSelector]) {
signature = [ARespondClass instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([ARespondClass instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:ARespondClassObject];
}
}
在这个例子里面并没有修改NSInvocation,没有修改它的参数。在这一步里面,实际上可以做更加灵活的消息转发,必须追加一个消息参数等等。
组合和继承是面向对象的一个重要话题。runtime的消息转发机制提供了一种优雅的组合实现机制。对外,client调用中心类的方法,通过performSelector,对内,中心类对消息进行分发。
网友评论