iOS类、元类和isa、super指针的关系图和消息转发
iOS类、元类和isa、super指针的关系图:
图1对象执行某方法后查找方法路径:例如:[objectA getName]
类执行某方法后查找方法路径:例如:[ClassA alloc]
总结:
对象执行方法后会从isa指向的对象中查找方法列表(先找cache,方法执行过第一次后会存到cache,后找methodLists),没找到则沿着super指针往上寻找,直到根类,如果一直找不到则进入消息转发流程
对象的方法列表存在类对象或者类对象的父类中
类对象的方法列表(类方法)存在元类或者元类的父类中
附:
Object-C类结构体
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
没有找到方法则进入消息转发流程:
新建测试用的Person
类
@interface Person : NSObject
@end
此类没有任何方法,在其他类中调用:
Person *person = [[Person alloc] init];
[person performSelector:@selector(test:) withObject:@"测试" afterDelay:0];
Person
类实现中:
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test:)) {
class_addMethod(self, @selector(test:), (IMP)dynamicAddTest, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicAddTest(id self, SEL _cmd,id place){
NSLog(@"arg:%@",place);
}
@end
此时Person
类的方法查询顺序如图2,查询不到则进入消息转发流程,首先进入
+ (BOOL)resolveInstanceMethod:(SEL)sel {
}
多说一点:如果是类方法调用则进入resolveClassMethod
,前边图1也可以看出来:类是元类的对象
[Person performSelector:@selector(test:) withObject:@"测试" afterDelay:0];
+ (BOOL)resolveClassMethod:(SEL)sel {
}
在这个方法中,我们有机会动态添加一个方法实现(IMP)给目标类来处理这个消息,避免了unrecognized selector sent to instance的错误
。
class_addMethod
第三个参数为"v@:@"
,这表示方法的类型编码,v
代表一种void
,@
代表一个对象(无论是静态类型还是类型id
),:
代表方法选择器(SEL
),详细说明的Apple官方文档传送门:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100%EF%BC%89
如果在resolveInstanceMethod
中未处理,则进入
- (id)forwardingTargetForSelector:(SEL)aSelector {
}
这个方法的返回值是id
类型,我们可以返回一个可以处理此消息的对象,完整代码如下:
@implementation Car
- (void)test:(NSString *)string {
NSLog(@"%s",__FUNCTION__);
}
@end
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)) {
return [[Car alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
Car
类中实现了test:
方法,在forwardingTargetForSelector
中return
了一个Car
的对象,这则消息被转发给return
的对象处理。
在forwardingTargetForSelector
方法中没有做操作则顺序进入:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
//sel:_forwardStackInvocation
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
其中methodSignatureForSelector
返回方法的签名,两种生成方法,
根据类型编码的官方文档,写出对应方法的类型编码"v@:@@"
,然后生成方法签名:
[NSMethodSignature signatureWithObjCTypes:"v@:@@"]
另一种由某个对象的方法生成,一个方法和另一个方法的返回值和参数都相同,则生成的方法签名对象NSMethodSignature *
是相同的:
Car *car = [[Car alloc] init];
[Car methodSignatureForSelector:@selector(test:)];
完整代码如下:
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)){
return [NSMethodSignature signatureWithObjCTypes:"v@:@@"];
}
return [super forwardingTargetForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(test:)){
NSString *string = @"测试";
NSString *string2 = @"测试2";
Car *car = [[Car alloc] init];
[anInvocation setTarget:car];
[anInvocation setSelector:@selector(testInvocation:string2:)];
[anInvocation setArgument:&string atIndex:2];
[anInvocation setArgument:&string2 atIndex:3];
[anInvocation invoke];
}
}
@end
@implementation Car
- (void)testInvocation:(NSString *)string string2:(NSString *)string2{
NSLog(@"%s",__FUNCTION__);
NSLog(@"arg0:%@",string);
NSLog(@"arg1:%@",string);
}
@end
注意:NSInvocation
的对象执行setTarget
方法不可以这么写:
NSInvocation * anInvocation = [[NSInvocation alloc] init];
[anInvocation setTarget:[[Car alloc] init]]
setTarget
不会对参数强引用,所以传入的参数刚生成就被释放掉了,导致执行invoke
时会crash。
消息转发完整流程如下图(点击放大):
Demo地址:https://github.com/360fengdai/MessageForwardingDemo.git
参数资料:
iOS - NSInvocation的使用:https://www.jianshu.com/p/da96980648b6
使用 NSInvocation 向对象发送消息:https://www.jianshu.com/p/bd04451f2e0e
iOS消息转发机制:https://www.jianshu.com/p/151edae1d6ee
iOS 消息发送与转发详解:https://juejin.im/post/5aa79411f265da237a4cb045
Apple类型编码:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100%EF%BC%89