Objective-C 使用运行时机制,即在程序运行时才决定执行的方法对方法进行调用,其中最主要的是一套消息发送机制。使用C语言实现,方法包含在头文件#import <objc/objc-runtime.h>中,里面包含很多运行时方法。
1.如我们常规的一条方法调用:[object addDanmicMethod],会在编译时被翻译成objc_msgSend(object,@selector(addDanmicMethod));每一个class包含一个is指针,还有一个dispatch_table(分发表)存放所有成员方法的入口。
这个方法先去查找 self 这个对象或者其父类是否响应 @selector(addDanmicMethod),如果从这个类的方法分发表或者 cache 里面找到了,就调用它对应的函数指针。如果找不到,那就会执行一些其他的东西。步骤如下:
- 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain, release 这些函数了。
- 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
- 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
- 如果 cache 找不到就找一下方法分发表。
- 如果还找不到就要开始消息转发逻辑了。
这种消息转发机制是 Runtime 非常重要的一个特性,大概的步骤如下:
1.查找该类及其父类的 cahce 和方法分发表,在找不到的情况下执行2。
2.执行 + (BOOL) resolveInstanceMethod:(SEL)aSEL 方法。这就给了程序员一次机会,可以告诉 runtime 在找不到改方法的情况下执行什么方法。举个例子:
-(NSString *)setaddFunction:(NSString *)string1 withValue:(NSString *)string2
{
return [string1 stringByAppendingString:string2];
}
NSString* myDaynamicIMP(id self,SEL _cmd)
{
NSLog(@"result:%@",[self setaddFunction:@"this is an" withValue:@"daynamic method"]);
return [self setaddFunction:@"this is an" withValue:@" daynamic method"];
}
NSString* myNewDaynamicIMP(id self,SEL _cmd)
{
NSLog(@"result:%@",[self setaddFunction:@"this is an" withValue:@"daynamic replace method"]);
return [self setaddFunction:@"this is an" withValue:@" daynamic replace method"];
}
-(IBAction)clickChangeBehavior:(id)sender
{
SEL oriFunction = @selector(myDaynamicIMP);
IMP orginIMP = [NSObject instanceMethodForSelector:oriFunction];
IMP imp2 = class_replaceMethod([self class],oriFunction,(IMP)myNewDaynamicIMP,NULL);
_textView.text = [self performSelector:@selector(myDaynamicIMP)];
}
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if(sel == @selector(addDanmicMethod))
{
class_addMethod([self class], sel, (IMP)myDaynamicIMP, "N@:");//动态添加一个实现方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
3.接下来 Runtime 会调用 – (id)forwardingTargetForSelector:(SEL)aSelector 方法。这就给了程序员第二次机会,如果你没办法在自己的类里面找到替代方法,你就重载这个方法,然后把消息转给其他的Object。
-(id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(addDanmicMethod))
{
return [self setaddFunction:@"this is an" withValue:@" daynamic forward method"]
}
return [self forwardingTargetForSelector:aSelector];
}
4.最后,Runtime 会调用 – (void)forwardInvocation:(NSInvocation *)anInvocation 这个方法。NSInvocation 其实就是一条消息的封装。如果你能拿到 NSInvocation,那你就能修改这条消息的 target, selector 和 arguments。举个例子:
-(void)forwardInvocation:(NSInvocation *)invocation{
SEL invSEL = invocation.selector;
if([altObject respondsToSelector:invSEL]) {
[invocation invokeWithTarget:altObject];
} else {
[self doesNotRecognizeSelector:invSEL];
}
}
默认情况下 NSObject 对 forwardInvocation 的实现就是简单地执行 -doesNotRecognizeSelector: 这个方法,所以如果你想真正的在最后关头去转发消息你可以重载这个方法。