ObjC 中的方法调用是一个传递消息的过程,它不是直接去调用一个函数,而是通过 selector
去查找 IMP
的过程,最终查找到函数指针,然后执行具体函数实现;如果没有找到函数实现,还会继续执行消息转发流程,最后仍没有查找到函数实现,就报错崩溃。
objc_msgSend 函数 1 2 3 4 5 OBJC_EXPORT id _Nullable objc_msgSend(id _Nullable self , SEL _Nonnull op, ...) OBJC_AVAILABLE(10.0 , 2.0 , 9.0 , 1.0 , 2.0 );
ObjC 通常的方法调用实质上就是调用 objc_msgSend
函数。
如下代码:
1 2 Person *olivia = [[Person alloc] init]; [olivia sayHi];
使用命令 clang -rewrite-objc main.m
编译成 C++ 代码后如下:
1 2 Person *olivia = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass ("Person" ), sel_registerName ("alloc" )), sel_registerName ("init" )); ((void (*)(id, SEL))(void *)objc_msgSend)((id)olivia, sel_registerName ("sayHi" ));
如果是使用 super
关键字,则调用的是 objc_msgSendSuper
函数。
消息发送流程
检查 selector
是否要忽略,比如自动引用计数下 retain
、release
方法将会被忽略。
检查 target
是否为 nil
,如果为 nil
则直接返回 nil
。
通过 selector
查找具体的 IMP
,首先查找缓存列表。具体方法就是通过一个哈希函数将 SEL
映射到指定的 bucket_t
在数组中的位置。
如果换成列表没有,开始查找方法列表。对于已排序好的列表,采用 二分查找法
进行查找。对于没有排序好的列表,就进行遍历查找。如果查找到了会将对应的 IMP
放入方法缓存列表中。
如果方法列表没有,逐级查找父类缓存列表和方法列表,最终直到查找 NSObject
类。
进入动态方法解析、消息转发流程。
消息转发 动态方法解析 如果给一个对象发送消息,该对象并没有对应的方法实现,那么程序不会直接崩溃,首先会进入动态方法解析的过程。
1 2 3 4 + (BOOL )resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5 , 2.0 , 9.0 , 1.0 , 2.0 ); + (BOOL )resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5 , 2.0 , 9.0 , 1.0 , 2.0 );
动态方法解析核心是上述两个方法(都是 NSObject
的类方法),分别解决类方法和实例方法。当对象没有对应方法实现的时候,上述方法就是第一个补救措施。
对于 @dynamic
关键字到属性,系统不提供 setter
和 getter
方法实现,就需要在动态方法解析中为其动态添加两个方法实现。
动态添加方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void playIMP(id self , SEL _cmd) { NSLog (@"Game Start!" ); } void playWithTypeIMP(id self , SEL _cmd, GameType type) { switch (type) { case GameTypeOne: NSLog (@"Game Type One Start!" ); break ; case GameTypeTwo: NSLog (@"Game Type Two Start!" ); break ; case GameTypeThree: NSLog (@"Game Type Three Start!" ); break ; default : break ; } } + (BOOL )resolveInstanceMethod:(SEL)sel { if (sel == @selector (play)) { class_addMethod(self , sel, (IMP)playIMP, "v@:" ); return YES ; } else if (sel == @selector (playWith:)) { class_addMethod(self , sel, (IMP)playWithTypeIMP, "v@:i" ); return YES ; } return [super resolveInstanceMethod:sel]; }
在 resolveInstanceMethod
方法中,使用 class_addMethod
函数为指定的类动态添加方法。
消息重定向 如果 resolveInstanceMethod:
方法中没有找到方法实现,将会进入第二个补救措施,就是消息重定向 forwardingTargetForSelector:
,这个方法可以将消息的接受者更换为其他对象。
1 2 3 4 5 6 7 8 - (id )forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector (play)) { return [SecondGame new]; } return nil ; }
转发 如果上述方法返回 nil,则会进入到最后一个补救方法中,即消息转发流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector (play)) { return [NSMethodSignature signatureWithObjCTypes:"v@:" ]; } return [super methodSignatureForSelector:aSelector]; } - (void )forwardInvocation:(NSInvocation *)anInvocation { if (anInvocation.selector == @selector (play)) { anInvocation.selector = @selector (anotherPlay); [anInvocation invokeWithTarget:self ]; } }
最终这里可以实现,让原本的消息接受者去执行一个其他的方法,也可以更好消息接受者,去执行一个指定的方法,都是可以的。
模拟继承 使用消息转发的原理,可以实现模拟多继承,我们定义一个 Person
类和一个 Machine
类,并定义一个 Robot
类对前两个类进行继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @interface Person : NSObject - (void )hello; @end @implementation Person - (void )hello { NSLog (@"Hello World!" ); } @end @interface Machine : NSObject - (void )lubricate; @end @implementation Machine - (void )lubricate { NSLog (@"Lubricate." ); } @interface Robot : NSObject - (void )hello; - (void )lubricate; @end #import "Robot.h" #import "Person.h" #import "Machine.h" @interface Robot ()@property (nonatomic , strong ) Person *person;@property (nonatomic , strong ) Machine *machine;@end @implementation Robot - (instancetype )init { self = [super init]; if (self ) { self .person = [[Person alloc] init]; self .machine = [[Machine alloc] init]; } return self ; } - (id )forwardingTargetForSelector:(SEL)aSelector { if ([self .person respondsToSelector:aSelector]) { return self .person; } else if ([self .machine respondsToSelector:aSelector]) { return self .machine; } else { return [super forwardingTargetForSelector:aSelector]; } } @end