iOS消息发送和转发⽰例详解
前⾔龙之谷2转
Objective-C 是⼀门动态语⾔,它将很多静态语⾔在编译和链接时期做的事情,放到了运⾏时来处理。之所以能具备这种特性,离不开 Runtime 这个库。Runtime 很好的解决了如何在运⾏时期到调⽤⽅法这样的问题。下⾯话不多说了,来⼀起学习学习吧。
消息发送
在 Objective-C 中,⽅法调⽤称为向对象发送消息:
// MyClass 类
@interface MyClass: NSObject
- (void)printLog;
@end
@implementation MyClass
- (void)printLog {
NSLog(@"print log !");
}
@end
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
// 输出: print log !
上⾯代码中的 [myClass printLog] 也可以这么写:
((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
[myClass printLog] 经过编译后就是调⽤ objc_msgSend ⽅法。
我们看看这个⽅法的⽂档定义:
id objc_msgSend(id self, SEL op, ...);
self:消息的接收者 op:消息的⽅法名,C 字符串 ... :参数列表
Runtime 是如何到实例⽅法的具体实现的?
基础概念
讲之前,我们需要先明⽩⼀些基础概念:Objective-C 是⼀门⾯向对象的语⾔,对象⼜分为实例对象、类对象、元类对象以及根元类对象。它们是通过⼀个叫 isa 的指针来关联起来,具体关系如下图:
以我们上⽂的代码为例:
MyClass *myClass = [[MyClass alloc] init];
整理下相互间的关系:
myClass 是实例对象
MyClass 是类对象
MyClass 的元类就是 NSObject 的元类
NSObject 就是 Root class (class)
NSObject 的 superclass 为 nil
NSObject 的元类就是它⾃⼰
NSObject 的 superclass 就是 NSObject
对应上图中的位置关系如下:
接着,我们⽤代码来验证下上⽂的关系:
MyClass *myClass = [[MyClass alloc] init];
Class class = [myClass class];
Class metaClass = object_getClass(class);
Class metaOfMetaClass = object_getClass(metaClass);
Class rootMetaClass = object_getClass(metaOfMetaClass);
Class superclass = class_getSuperclass(class);
Class superOfSuperclass = class_getSuperclass(superclass);
Class superOfMetaOfSuperclass = class_getSuperclass(object_getClass(superclass));
NSLog(@"MyClass 实例对象是:%p",myClass);
NSLog(@"MyClass 类对象是:%p",class);
NSLog(@"MyClass 元类对象是:%p",metaClass);
NSLog(@"MyClass 元类对象的元类对象是:%p",metaOfMetaClass);
NSLog(@"MyClass 根元类对象是:%p",rootMetaClass);
NSLog(@"MyClass ⽗类是:%@",class_getSuperclass(class));
NSLog(@"MyClass ⽗类的⽗类是:%@",superOfSuperclass);
NSLog(@"MyClass ⽗类的元类的⽗类是:%@",superOfMetaOfSuperclass);
NSLog(@"NSObject 元类对象是:%p",object_getClass([NSObject class]));
NSLog(@"NSObject ⽗类是:%@",[[NSObject class] superclass]);
NSLog(@"NSObject 元类对象的⽗类是:%@",[object_getClass([NSObject class]) superclass]);
//输出:
MyClass 实例对象是:0x60c00000b8d0
MyClass 类对象是:0x109ae3fd0
MyClass 元类对象是:****0x109ae3fa8
MyClass 元类对象的元类对象是:****0x10ab02e58**
MyClass 根元类对象是:0x10ab02e58
MyClass ⽗类是:NSObject
MyClass ⽗类的⽗类是:(null)
MyClass ⽗类的元类的⽗类是:NSObject
NSObject 元类对象是:0x10ab02e58
NSObject ⽗类是:(null)
NSObject 元类对象的⽗类是:NSObject
可以发现,输出结果是完全符合我们的结论的!
现在我们能知道各种对象之间的关系:
实例对象通过 isa 指针,到类对象 Class;类对象同样通过 isa 指针,到元类对象;元类对象也是通过 isa 指针,到根元类对象;最后,根元类对象的 isa 指针,指向⾃⼰。可以发现 NSObject 是整个消息机制的核⼼,绝⼤数对象都继承⾃它。
寻流程
上⽂提到了,⼀个 Objective-C ⽅法会被编译成 objc_msgSend,这个函数有两个默认参数,id 类型的 self, SEL 类型的 op。我们先看看 id 的定义:
typedef struct objc_object *id;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
我们可以看到,在 objc_object 结构体中,只有⼀个指向 Class 类型的 isa 指针。
吴亦凡14岁女孩聊天记录我们再看看 Class 的定义:
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;
⾥⾯有很多参数,很显眼的能看到这⼀⾏:
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
看名字也容易理解,这个 methodLists 就是⽤来存放⽅法列表的。我们再看看 objc_method_list 这个结构体:
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
⾥⾯的 objc_method ,也就是我们熟悉的 Method:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
Method ⾥⾯保存了三个参数:
⽅法的名称
⽅法的类型
⽅法的具体实现,由 IMP 指针指向
经过层层挖掘,我们能明⽩实例对象调⽤⽅法的⼤致逻辑:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
先被编译成((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
沿着⼊参 myClass 的 isa 指针,到 myClass 的类对象(Class),也就是 MyClass
接着在 MyClass 的⽅法列表 methodLists 中,到对应的 Method
最后到 Method 中的 IMP 指针,执⾏具体实现
类对象的类⽅法⼜是怎么到并执⾏的?
由上⽂,我们已经知道,实例对象是通过 isa 指针,到其类对象(Class)中保存的⽅法列表中的具体实现的。
⽐如:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
可以理解为:printLog ⽅法就是保存在 MyClass 中的。
那么如果是个类⽅法,⼜是保存在什么地⽅的呢?
我们回顾下 Class 的定义:
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;
可以发现到这⼀⾏:
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
这⾥的 isa 同样是指向⼀个 Class 的指针。上⽂中,我们也知道了类对象的 isa 指针是指向元类对象的。那么不难得出:
类对象的类⽅法,是保存在元类对象中的!
类对象和元类对象都是 Class 类型,仅仅服务的对象不同罢了。到了元类对象,⾃然就到了元类对象中的 methodLists,接下来就和实例对象的⽅法寻调⽤⼀样的流程了。
关于⽗类(superclass)
在 Objective-C 中,⼦类调⽤⼀个⽅法,如果没有⼦类没有实现,⽗类实现了,会去调⽤⽗类的实现。上⽂中,到methodLists 后,寻 Method 的过程如下:
如何提⾼⽅法查的效率?
上⽂中,我们⼤概知道,⽅法是通过 isa 指针,查 Class 中的 methodLists 的。如果⼦类没实现对应的⽅法实现,还会沿着⽗类去查。整个⼯程,可能有成万上亿个⽅法,是如何解决性能问题的呢?
例如:
for (int i = 0; i < 100000; ++i) {
MyClass *myObject = myObjects[i];
[myObject methodA];
}
这种⾼频次的调⽤ methodA,如果每调⽤⼀次都需要遍历,性能是⾮常差的。所以引⼊了 Class Cache 机制:
纪念一二九运动演讲稿Class Cache 认为,当⼀个⽅法被调⽤,那么它之后被调⽤的可能性就越⼤。
查⽅法时,会先从缓存中查,到直接返回;不到,再去 Class 的⽅法列表中。
在上⽂中 Class 的定义中,我们可以发现 cache:
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
说明了缓存是存在类中的,每个类都有⼀份⽅法缓存,⽽不是每个类的 object 都保存了⼀份。
消息转发
如果⽅法列表(methodLists)没到对应的 selector 呢?
// ViewController.m 中(未实现 myTestPrint ⽅法)
[self performSelector:@selector(myTestPrint:) withObject:@",你好!"];
系统会提供三次补救的机会。
第⼀次
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (实例⽅法)
+ (BOOL)resolveClassMethod:(SEL)sel {} (类⽅法)
这两个⽅法,⼀个针对实例⽅法;⼀个针对类⽅法。返回值都是 Bool。
使⽤⽰例:
// ViewController.m 中
void myMethod(id self, SEL _cmd,NSString *nub) {
NSLog(@"ifelseboyxx%@",nub);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (sel == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
return YES;
}else {
return [super resolveInstanceMethod:sel];
}
}
我们只需要在 resolveInstanceMethod: ⽅法中,利⽤ class_addMethod ⽅法,将未实现的 myTestPrint: 绑定到 myMethod 上就能完成转发,最后返回 YES。
第⼆次
- (id)forwardingTargetForSelector:(SEL)aSelector {}
这个⽅法要求返回⼀个 id。使⽤场景⼀般是将 A 类的某个⽅法,转发到 B 类的实现中去。
使⽤⽰例:
想转发到 Person 类中的 -myTestPrint: ⽅法中:
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
/
/ ViewController.m 中
- (id)forwardingTargetForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [Person new];
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
第三次
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}
第⼀个要求返回⼀个⽅法签名,第⼆个⽅法转发具体的实现。⼆者相互依赖,只有返回了正确的⽅法签名,才会执⾏第⼆个⽅法。
这次的转发作⽤和第⼆次的⽐较类似,都是将 A 类的某个⽅法,转发到 B 类的实现中去。不同的是,第三次的转发相对于第⼆次更加灵活,forwardingTargetForSelector: 只能固定的转发到⼀个对象;forwardInvocation: 可以让我们转发到多个对象中去。
使⽤实例:
想转发到 Person 类以及 Animal 类中的 -myTestPrint: ⽅法中:
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
@interface Animal : NSObject
@end
@implementation Animal
- (void)myTestPrint:(NSString *)str {
NSLog(@"tiger%@",str);
}
@end
// ViewController.m 中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
-
(void)forwardInvocation:(NSInvocation *)anInvocation {
Person *person = [Person new];
Animal *animal = [Animal new];
if ([person respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:person];健康证怎么办
}
if ([animal respondsToSelector:anInvocation.selector]) {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论