Runtime
虽然平时在工作中,比较少利用到Runtime的知识,但是,runtime的知识对于我们来说,却是必备的技能包之一。在开发iOS App的时候,基本上每个人都接触过[object doSomething]
这样的方法调用方式。通过[]
来进行某个实例对象或者类对象的方法。不过,可能在学习OC的时候,可能都会忽略“发送消息”这样的字眼。在OC中,方法的调用,实际上就是消息的发送与接收。[object doSomething]
的意思就是,给object
这个对象(object
作为接收者),发送一个doSomething
的消息。
换句话说,OC在编译阶段会确定消息的内容与其接收者,但是直到在运行过程中,才会确定接收者会如何处理该消息:是直接执行?转发给其他对象?还是完全不知道这个消息是啥东东,直接crash?这决定性的时刻,都是发生在Runtime的过程中。So,我们可以看到,Runtime是如此滴重要。
与Runtime进行交互
Objective-C的程序能够通过下面三种方式与Runtime进行交互:
- OC源码
- Foundation框架中的
NSObject
的方法 - 直接调用runtime的函数
OC源码
对于大多数情况下,runtime的系统会在幕后自动帮我们完成这部分工作。我们只需要关心OC代码的编写和编译过程即可。 当你编译OC的类和方法时,编译器会根据OC的动态特性来创建相关的数据结构和函数调用。数据结构会获取类的信息,category的定义和协议的声明,详细一点来说,就是method selectors, instance variable templates等。
NSObject方法
基本上Cocoa中的对象都是继承自NSObject
,大多数对象也都会继承NSObject
的方法(NSProxy
这个类比较特殊)。
有的NSObject
的方法可以查询runtime系统的一些信息,通过这些方法来实现对象的自省。比方说isKindOfClass:
和isMemberOfClass:
,会检测对象是否在当前的继承体系中;respondsToSelector:
表示对象是否能接收响应这个消息;conformsToProtocol:
表示对象是否实现了指定的协议;methodForSelector:
则提供了方法的实现的地址(函数指针)
Runtime函数
Runtime系统的库能在/usr/include/objc
的目录中找到。
Runtime相关的数据结构
接下来我们说下Runtime相关的数据结构。还是从objc_msgSend
说起。
从Runtime的源码中找到objc_msgSend
的定义如下:
void objc_msgSend(void /* id self, SEL op, ... */ )
id
id类型相信大家都已经很熟悉了,在OC中,基本上对象都能用id
进行表示,因为OC直到runtime的时候才能确认一个对象的类型,所以id
类型基本上能说“万金油”类型吧。
实际上id
是一个指向类实例的指针:typedef struct objc_object *id
好奇宝宝又要发问了:"乜嘢系objc_object
"?在Apple文档中查的话,我们能看到objc_object
的结构是:
struct objc_object {
Class isa;
};
objc_object
是一个含向类对象的isa指针,文档对该结构体的说明如下:
When you create an instance of a particular class, the allocated memory contains an objc_object data structure, which is directly followed by the data for the instance variables of the class. The alloc and allocWithZone: methods of the Foundation framework class NSObject use the function class_createInstance to create objc_object data structures.
当你创建类的实例对象时,已分配的内存在实例变量的数据后面,会包含有一个
objc_object
的数据结构。Foundation框架中的NSObject
的alloc
和allocWithZone:
方法会使用class_createInstance
来创建objc_object
这个结构体。
也正是因为这个isa指针,所以在runtime的过程中,我们能通过isa指针指向的类对象来确定实例的类型。
在这篇文章中看到一个提醒,就是isa指针并不一定会指向实例所属的类, 这里需要注意下KVO的问题。先记下来,等之后学习再查看
SEL
在OC中,我们一般使用@selector
来获取对应的方法,即SEL
类型。SEL
的实际定义为: typedef struct objc_selector *SEL;
, 代表了一个方法选择器(method selector)
runtime的源码中,对objc_selector
并没有过多的详细的描述。但是我在Stack Overflow的How do SEL and @selector work?找到了答案:
SEL
实际上可以看做是存储了方法名称(C的字符串)的const char []
举个🌰,@selector(instanceMethod)
这句代码创建了字符串"instanceMethod", 然后将字符串传递给OC的runtime,runtime将其转换为唯一能够响应这个字符串的的指针,大概就是:SEL sel = @selector(instanceMethod);
SEL sel = sel_registerName("instanceMethod");
更详细的过程需要查看runtimede的sel_getName
和sel_registerName
Class
回到上面的isa指针,你会发现isa指针的类型是Class
。还是看源码:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
(id -> objc_object
(这里是Class isa) -> objc_class
, 应该就是id最终指向的是objc_class
这个结构体的意思吗?)所以在OC中的所有对象都是由objc_class
构成,包括类,类也是一个对象。
根据下面这张图片来看的话

NSObject
,有趣的是,NSObject
的元类是自己(可以用objc_getMetaClass
试一试), 超类为nil。
好了,回到objc_class
这个结构上
OBJC2_UNAVAILABLE
表明的是objc2.0以后就不适用。虽然如此,但是为了兼容新版本,我们还是能从这么点内容中看看实例对象的结构。
从源码上,我们能看到该结构存储了超类,实例变量,方法列表,缓存和所遵循的协议。
objc_ivar_list
objc_ivar_list
指的是成员变量列表
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
objc_class
存储的是指向成员列表的指针,简单理解就是存储了objc_ivar
的结构体,而objc_ivar
代表了变量的名称,属性,偏移量和位置。因为在runtime中成员变量列表的变量大小,内存和位置已经确定,所以我们没办法在运行过程中给类动态添加变量。如果是通过Category来添加的话,并不是对成员变量列表进行修改来达到目的。
objc_method_list
objc_method_list
指的是方法列表
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *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;
} OBJC2_UNAVAILABLE;
在objc_class
中,是该类型的变量是一个指向指针的指针,这也是为什么我们能动态添加方法,不能动态添加属性的原因。一个是指向列表的指针,一个是指向列表的指针的指针。简单来说,objc_method_list
就是存储了objc_method
的列表,objc_method
就是方法的相关信息。
objc_cache
从名字可以看出,这是一个缓存用的:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
这是Apple为了提高效率而提供的。在实例对象接收到一个消息时,会首先在缓存区寻找方法的实现,如果找不到的话,才会在dispatch table中根据isa的指向来查找方法实现,从而提高执行效率。Runtime会将最近被调用的方法存储到cache中。
objc_protocol_list
struct objc_protocol_list {
struct objc_protocol_list *next;
long count;
__unsafe_unretained Protocol *list[1];
};
这个指针代表的是objc_protocol_list
这个协议列表,表示类所实现的协议
消息
objc_msgSend
在OC中,只有到了runtime的时候,消息才会和方法的实现进行绑定。在这个过程中,编译器会将消息表达式[receiver message]
转化为消息函数objc_msgSend
.该函数带有两个参数,一个是消息的接收者,另一个是在方法调用时用到的方法名称,也就是method selector:
objc_msgSend(receiver, selector)
另外,如果消息表达式是带参数的话,那么消息函数看起来就是这样的:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息函数通过下面的步骤来进行动态绑定:
- 找到selector的方法实现。由于不同的类可能会实现同一个方法,所以是基于接收者的类型进行方法实现的查找的。
- 找到实现后,调用之。将实现接收者对象(指针)和参数发送给方法实现
- 最后,将实现过程的返回值作为消息的返回值进行返回
消息机制的关键在于编译器为每个类和对象所创建的结构体。每个类的结构体包含了两个最基本的元素:
- 指向超类的指针
- 类分发表(dispatch table)
分发表将方法选择器和方法实现的地址进行连接。比如,选择器的setOrigin::
方法和setOrigin::
的实现相关联。
当一个对象被创建,分配内存,初始化实例变量的时候,首先出现在对象的变量中的是指向类结构的指针,这就是我们上面说的isa。通过isa指针和其类,超类以及继承结构中的类进行相联系。

当消息被发送给某个对象时,消息函数会根据对象的isa指针指向的类结构在分发表中寻找方法选择器。如果在没找到,objc_msgSend
会在超类的分发表中查找。就是这样一层一层往上查询。一旦定位到了选择器,函数就会调用表中的方法并将其传递到接收对象的数据结构中。
这就是runtime中的方法实现,或者说是面向对象的术语:动态绑定。
为了提高消息处理的效率,runtime系统会将调用过的选择器和方法地址进行缓存(就是上面我们提到过的objc_cache
)。每个类都有一个独立的cache区域。在分发表中查找之前,消息机制会首先检查接收对象的类的cache区域。如果在cache中能够找到,消息的发送会相对函数调用慢一点。但是app已经运行了足够长的时间,基本上常用的方法都已经缓存起来的话,消息的查找调用效率就杠杠的
隐藏的参数
当objc_msgSend
找到实现方法的“程序”时,它会调用该“程序”并将消息中所有的参数传递进去。要注意的是,有两个隐藏的参数:
- 接收者对象
- 方法选择器
之所以是隐藏的原因,是因为它们并不是在源码而是在方法中进行定义的。所以也就不难解释,为什么objc_msgSend
的定义中是这样子的:/* id self, SEL op, ... */
虽然没有显式声明这些参数,我们仍然能够引用它们。方法将接收者对象引用为self
,将接收器作为_cmd
。在下面的例子中,_cmd
代表了strange
方法,self
作为接收strange
消息的对象
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
获取方法的地址
唯一避免动态绑定的方法,就是直接获取方法的地址并且调用。不过这种获取地址的做法,常见于方法被多次调用的情况,当被大量调用的时候,使用这种做法相对来说会更加高效。
通过NSObject
的methodForSelector:
方法,我们可以获取到指向方法实现的指针,并使用它来调用实现。要注意的是,指针在进行类型转换时,需要额外小心。所有返回值和参数类型都需要被包含在转换中。
举个栗子:
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for (i = 0; i < 1000; i++)
setter(targetList[i], @selector(setFilled:), YES);
传递给方法实现的前两个参数分别是接收者(self
)和方法选择器(_cmd
)。这些参数隐藏在方法的语法中,但是在将方法作为函数调用的时候则需要显式传递进来。
使用methodForSelector:
来避免动态绑定,能够节省多次发送消息的时间。但是,这种差别只会出现在消息被重复很多很多次的情况下。
最后需要记住一点是methodForSelector:
是由Cocoa runtime system提供的,并不属于OC语言的特性。
动态方法解析
除了消息以外,我们也可以提供动态方法来实现函数的调用。比方说,OC中的@dynamic
:
@dynamic propertyName;
@dynamic
的作用是告知编译器,该属性相关的方法会动态提供。
你可以实现resolveInstanceMethod:
和resolveClassMethod:
两个方法来给实例对象或者类对象动态添加方法的实现。
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation...
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "V@:"); // v@:涉及到的是Type Encoding的知识,详细资料可以在Apple文档中查找到
return YES; // 返回NO的话会触发转发
}
return [super resolveInstanceMethod:aSEL];
}
@end
类会在转发机制触发之前,可能会有机会来解析这个方法。如果respondsToSelector:
或者instancesRespondToSelector:
被调用的话,动态方法的解析器首先会给选择器提供IMP。如果你实现了resolveInstanceMethod:
方法,但是想要触发转发机制的话,则需要返回NO
.
在这篇blog中提到一个类方法和实例方法中objc_getClass(self)
,[self class]
和objc_getClass([self class])
的问题(作者也有结论):
于是就自己研究了一下:
对于类对象:
// RuntimeTest.h
@interface RuntimeTest : NSObject
+ (void)Test2;
@end
// RuntimeTest.m
@implementation RuntimeTest
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(Test2)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(Test)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (void)Test {
NSLog(@"Self: %p", self);
NSLog(@"[self class]: %p", [self class]);
NSLog(@"object_getClass(self): %p", object_getClass(self));
NSLog(@"object_getClass([self class]): %p", object_getClass([self class]));
NSLog(@"Meta Class: %p", objc_getMetaClass("RuntimeTest"));
}
@end
// 输出
// 2017-04-19 21:51:54.373 ObjectiveCDemo[13405:452834] Self: 0x107d44038
// 2017-04-19 21:51:54.373 ObjectiveCDemo[13405:452834] [self class]: 0x107d44038
// 2017-04-19 21:51:54.373 ObjectiveCDemo[13405:452834] object_getClass(self): 0x107d44010
// 2017-04-19 21:51:54.373 ObjectiveCDemo[13405:452834] object_getClass([self class]): 0x107d44010
// 2017-04-19 21:51:54.374 ObjectiveCDemo[13405:452834] Meta Class: 0x107d44010
可以看到,如果self
是一个类对象的话,[self class]
指向的是自己本身,objc_getClass(self)
和objc_getClass([self class])
一样,指向的是类对象的元类。因为类方法的定义是在元类中,所以在上面的代码中,我们使用的是object_getClass(self)
来获取元类,并在元类中添加方法实现。
而对于实例变量呢?
// RuntimeTest.h
@interface RuntimeTest : NSObject
- (void)varMethod;
@end
// RuntimeTest.m
@implementation RuntimeTest
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(varMethod)) {
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(testInstanceMethod)), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)testInstanceMethod {
NSLog(@"Self: %p", self);
NSLog(@"[self class]: %p", [self class]);
NSLog(@"object_getClass(self): %p", object_getClass(self));
NSLog(@"object_getClass([self class]): %p", object_getClass([self class]));
NSLog(@"Meta Class: %p", objc_getMetaClass("RuntimeTest"));
}
@end
// 输出
// 2017-04-19 22:01:36.326 ObjectiveCDemo[13541:460804] Self: 0x608000008230
// 2017-04-19 22:01:36.326 ObjectiveCDemo[13541:460804] [self class]: 0x102390068
// 2017-04-19 22:01:36.326 ObjectiveCDemo[13541:460804] object_getClass(self): 0x102390068
// 2017-04-19 22:01:36.326 ObjectiveCDemo[13541:460804] object_getClass([self class]): 0x102390040
// 2017-04-19 22:01:36.326 ObjectiveCDemo[13541:460804] Meta Class: 0x102390040
可以看到,这里[self class]
和object_getClass(self)
一样指向类对象,object_getClass([self class])
指向的是元类。因为实例方法是定义在类对象中,所以这里使用的是[self class]
。
结论是:
1. 如果对象是类对象的话,
self
和[self class]
指向的是类对象自身,object_getClass(self)
和object_getClass([self class])
指向的是元类。在进行动态方法解析是,需要使用的是object_getClass(self)
, 即在元类上添加类方法的实现。因为类方法的定义是在元类上2. 如果对象是实例对象的话,
self
指向的是对象本身,[self class]
和object_getClass(self)
指向的是类对象,object_getClass([self class])
指向的是元类对象。在进行动态方法解析时,需要使用[self class]
,即在类上添加实例方法的实现。因为实例方法是定义在类对象上的。3. 如果忘了用哪个的话。。用object_getClass(self)
就是最妥当的。。简单来记的话,就是:self
是自身,object_getClass([self classs])
是元类,objcect_getClass(self)
是上级。[self class]
实例时为类,类时为自身。
2017-04-21 update:
4.object_getClass(self)
获取的是,isa的指向
5.注意isKindOfClass
和isMemberOfClass
:
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Person class] isKindOfClass:[Person class]];
BOOL res4 = [(id)[Person class] isMemberOfClass:[Person class]];
上面的代码中,只有res1为YES,其他的都为NO,为什么呢?
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
从isKindOfClass:
和isMemberOfClass:
的定义中,我们可以看到,当对象为类对象时,用来比对的是元类对象和类对象,对象为实例对象时,比对的是类对象!
第一句代码使用的是isKindOfClass:
,比较的是[NSObject class]
。object_getClass([NSObject class])
获取的是NSObject
这个类对象的元类,该元类的超类指向了NSObject
这个类对象,所以返回YES。而第二句代码,比较的是NSObject
这个类对象和其元类对象,所以返回NO。
第三句代码比较的是Person
元类的结构和Person
类对象,返回NO
第四句代码比较的是Person
元类和Person
这个类,故返回NO
6.总结
对象类型 | self | [self class] | object_getClass(self) | object_getClass([self class]) |
---|---|---|---|---|
实例对象 | 自身 | 类对象 | 类对象 | 元类 |
类对象 | 自身 | 自身 | 元类 | 根元类 |
方法转发
当将消息发送给一个“不认识”它的对象时,会发生什么呢?
- 动态方法解析:
resolveInstanceMethod:
和resolveClassMethod:
返回YES的话,表明能处理这个消息了,返回NO的话,进行第二步; - 重定向(也有叫Fast Forwarding):
forwardingTargetForSelector:
将消息转发给另一个对象。如果这个方法返回nil
或者self
的话,就会将消息发送给另一个对象进行处理 - Normal Forwarding:
forwardInvocation:
&methodSignatureForSelector:
- 如果上面三个步骤都不满足的话,就会发送
doesNotRecognizeSelector:
消息
Fast Forwarding
forwardingTargetForSelector:
返回nil
或者self
的话,则会进行Normal Forwarding,继续消息转发。
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(myMethod)) {
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(myMethod)) {
return NSClassFromString(@"AlternateClass");
}
return [super forwardingTargetForSelector:aSelector];
}
Forwarding
向不处理该消息的对象发送消息是错误的做法。但是在报错之前,runtime系统会给接收对象机会来处理这个消息。在报错之前,runtime会发送一个forwardInvocation:
消息,在消息里,将NSInvocation
对象(该对象封装了原来的消息和参数)作为参数一并发送给该对象。
因此,我们可以实现forwardInvocation:
方法来给出个缺省的响应来避免报错。正如方法名一样,forwardInvocation:
做的事情,就是将消息转发给另一个对象。
想象下,你声明了一个可以响应negotiate
消息的对象,但是你同时该对象的响应里面,也能包含有来自其他类型对象的响应,这时候你会怎么做咧?很简单,你可以在negotiate
的消息的方法实现中,转发给其他对象即可。
更进一步的说,你希望对于negotiate
这个消息的响应由其他类来实现,但是又不想通过继承来做的话,也可以将消息转发给能实现的类来完成:
- (id)negotiate {
if ([someOtherObject respondsTo:@selector(negotiate)]) {
return [someOtherObject negotiate];
}
return self;
}
要转发消息的话,在forwardInvocation:
的方法中需要决定消息的去处以及转发出去的时候,需要带上原来的参数。可以通过invokeWithTarget:
来进行转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:someOtherObject];
} else {
[super forwardInvocation:anInvocation];
}
}
说了辣么多,anInvocation
是哪里来的?在本节开头的第三个步骤里面,不知道你是否注意到了methodSignatureForSelector:
这个方法。methodSignatureForSelector:
和forwardInvocation
一般是配套使用的。在调用forwardInvocation:
之前, runtime系统会向对象发送methodSignatureForSelector:
来获取方法签名用于生成NSInvocation
对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [self methodSignatureForSelector:selector];
}
return signature;
}
我们可以将forwardInvocation:
方法看做是那些“不认识”方法的分配中心,将这些分发分配给不同的接收者,或者将所有消息一股脑发送到同一个目的地(跟收发快递的机制差不多嘛~😁)。forwardInocation:
方法同时也能将几个不同的消息合并到同一个响应里面。
**注意: **forwardInvocation:
只在对象不能识别消息的时候会执行!
转发和多继承
大家都知道,OC是不允许多继承的,但是,转发机制却模仿了继承的机制。通过转发,我们能实现类似多继承的效果,如下图所示,通过消息转发将其他类的方法继承(“借”)过来用:

在上面的图中,Warrior的实例将negotiate消息转发给Diplomat类的实例。对Warrior来说,negotiate的响应就是Diplomat类实例给出的响应,看起来就像Warrior给出的响应一样。
因此,转发消息的对象看起来就像继承了另一个类一样。在上面的例子中,给人的感觉就是Warrior类继承了Diolimat类。
转发机制提供了另外一些特性,你可以把这些特性作为多继承来使用。转发和多继承有个重要的不同点就是:多继承将不同功能合并在一个对象想中,它可能会变得臃肿,变得多层面。而转发,是将不同的工作分发给不同的对象。它将问题化小分给众多对象,且这些对象对于消息发送者来说是透明不可见的。
也就是说,多继承是一个对象有N个功能,转发呢,就是将问题交给别的对象去完成。
替代者对象
转发不仅仅与多继承相似,它还能让轻量级的对象来替代重量级的对象。
OC中的proxy(代理)就是一种替代方式。代理对象会关注转发给远端接收者的消息的细节,确保所有的参数都被copy并且能够通过连接进行取回等。但是它并不会执行远端对象的功能,仅仅给它一个本地地址,让它能够接收消息而已。
转发和继承
虽然转发和继承有点相似,但是NSObject
从不会将两者弄混。respondsToSelector:
和isKindOfClass:
会在继承结构中查找对应的方法,而不会在转发链中。
if ([aWarrior respondsToSelector:@selector(negotiate)]) {
...
}
上面的respondsToSelector:
的结果是NO
。虽然它可以接收消息,并且能够进行消息响应,但是实际上,消息是被转发给Diplomat对象来实现的。
在大多数情况下,NO
才是正确的答案。不过,如果你使用转发来设置一个替代者或者扩展了该类的话,转发机制可能就会像继承一样。如果你想要你的对象看起来像继承一样的话,你需要重写respondsToSelector:
和isKindOfClass:
来实现你的转发算法
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
除了respondsToSelector:
和isKindOfClass:
以外,instancesRespondsToSelector:
方法同样也应该实现转发算法。如果使用了协议的话,那么conformsToProtocol:
方法也要重写了。同样的,如果对象转发了消息的话,则需要通过methodSignatureForSelector:
来返回准确的方法描述,这个方法最终会响应被转发的信息。比方说,假如一个对象能够将消息转发给替代者,你可以这样做:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}