消息转发是Objective-C的消息机制的一个特性而已,实际工程中使用到的场景其实很少,不过了解其机制也是颇有必要的。 OC的消息机制,容许用户在未实现某个消息(SEL)的具体方法(IMP)时,依然有机会可以响应该消息。能够理解为是发送消息的一个补充,专用于处理未找到消息的状况。 因此,普通的发送消息,若是没有在实例对象的类(或者类对象的元类)的方法列表中找到对应的方法,则会进入消息转发的流程,包含如下几个步骤。bash
这两个方法,容许开发者动态添加方法的具体实现。app
static void sayInstanceName(id self, SEL cmd, id value) {
NSLog(@"resolveInstanceMethod %@", value);
}
static void sayClassName(id self, SEL cmd, id value) {
NSLog(@"resolveClassMethod %@", value);
}
复制代码
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod");
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"sayInstanceName:"]) {
class_addMethod([self class], sel, (IMP)sayInstanceName, "v@:@");
return YES;
} else if ([methodName isEqualToString:@"dynamicName"]) {
class_addMethod(self, sel, (IMP)myDynamicName, "@@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 注意:
// self为实例对象时,[self class]与object_getClass(self)都返回类(前者调用后者),object_getClass([self class])返回元类。
// self为类对象时,[self class]返回自身,而object_getClass(self)返回元类,等价于object_getClass([self class])。
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"sayClassName:"]) {
class_addMethod(object_getClass(self), sel, (IMP)sayClassName, "v@:@");
return YES;
}
return [super resolveClassMethod:sel];
}
@end
复制代码
能够设置实例方法和类方法。优化
注意,这里会涉及到方法实现的Type Encoding,咱们暂时先了解简单的对应关系便可。ui
[返回值][target][action][参数]
复制代码
// 如 v@:@, 即this
// v void
// @ id
// : SEL
// @ id
复制代码
消息转发,能够将消息转发给能够响应该消息的一个对象,这个对象能够是实例对象的一个属性,也能够是绝不相关的另一个对象。atom
// 仅支持将消息转发给一个对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"forwardingTargetForSelector");
// 此处能够询问对象的全部属性,看有谁能够响应消息,即将其return
if ([NSStringFromSelector(aSelector) isEqualToString:@"sayInstanceName:"]) {
if ([self.helper respondsToSelector:aSelector]) {
return self.helper;
}
// 并不会执行,因此forwardingTargetForSelector仅支持将消息转发给一个对象
if ([self.anotherHelper respondsToSelector:aSelector]) {
return self.anotherHelper;
}
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
注意:这里只能将消息转发给 一个对象 。spa
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector");
if ([self.helper respondsToSelector:aSelector]) {
return [self.helper methodSignatureForSelector:aSelector];
}
if ([self.anotherHelper respondsToSelector:aSelector]) {
return [self.anotherHelper methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
// 支持将消息转发给任意多个对象,因此多继承也只能采用forwardInvocation:的方式
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation");
SEL sel = anInvocation.selector;
if ([self.helper respondsToSelector:sel]) {
[anInvocation invokeWithTarget:self.helper];
}
if ([self.anotherHelper respondsToSelector:sel]) {
[anInvocation invokeWithTarget:self.anotherHelper];
}
}
复制代码
这里,对方法的签名进行严格匹配,而后再执行对应的消息转发。3d
在以上步骤所有执行过,依然没能完成发送消息,则会调用以下方法:指针
// unrecognized selector sent to instance
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"doesNotRecognizeSelector");
}
复制代码
NSObject对该方法的默认实现即抛出一个异常,如:code
[Person sayClassName:]: unrecognized selector sent to class 0x109344950
复制代码
一般状况下,这两个阶段的区别以下:
方法签名包含了方法的名称、参数、返回值。iOS中使用Type Encoding的方式来表示。
例以下边的方法:
- (void)myVoid:(NSDictionary *)params {
NSLog(@"myFunc %@", params);
}
- (BOOL)myBool:(NSDictionary *)params {
NSLog(@"myFunc %@", params);
return YES;
}
- (NSInteger)myNSInteger:(NSDictionary *)params {
NSLog(@"myFunc %@", params);
return 1;
}
- (CGFloat)myCGFloat:(NSDictionary *)params {
NSLog(@"myFunc %@", params);
return CGFLOAT_MAX;
}
- (id)myFunc:(NSDictionary *)params {
NSLog(@"myFunc %@", params);
return [params copy];
}
复制代码
- (void)testInvocations {
SEL myVoid = @selector(myVoid:);
SEL myBool = @selector(myBool:);
SEL myNSInteger = @selector(myNSInteger:);
SEL myCGFloat = @selector(myCGFloat:);
SEL myFunc = @selector(myFunc:);
NSDictionary *params = @{@"name": @"name"};
NSMethodSignature *myVoidSig = [self methodSignatureForSelector:myVoid]; // v@:@
NSMethodSignature *sigMyBool = [self methodSignatureForSelector:myBool]; // B@:@
NSMethodSignature *sigMyNSInteger = [self methodSignatureForSelector:myNSInteger]; // q@:@
NSMethodSignature *sigMyCGFloat = [self methodSignatureForSelector:myCGFloat]; // d@:@
NSMethodSignature *sigMyFunc = [self methodSignatureForSelector:myFunc]; // @@:@
NSLog(@"testInvocations");
}
复制代码
重复一次,Type Encoding的格式为:
[返回值][target][action][参数]
复制代码
各个类型对应的表示分别为 void-v,Bool-B,NSInteger-q,CGFlat-d,id-@ 。
以myFunc:为例,表示为 @@:@ ,即返回值为id,接收参数也为NSObject类型(NSDictonary)。
NSInvocation能够给任意OC对象发送消息,其使用方式有固定的步骤:
- (void)testInvocation {
SEL sel = @selector(myFunc:);
NSDictionary *params = @{@"name": @"name"};
NSMethodSignature *sig = [self methodSignatureForSelector:sel];
if (!sig) {
return;
}
const char *retType = [sig methodReturnType];
if (strcmp(retType, @encode(void)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
invocation.target = self;
invocation.selector = sel;
[invocation setArgument:¶ms atIndex:2];
[invocation invoke];
NSLog(@"void ret");
/// 0是target,1是action。参数是从2开始的。
void *target;
SEL action;
[invocation getArgument:&target atIndex:0];
[invocation getArgument:&action atIndex:1];
/// target-action : <AppDelegate: 0x600003d69080>-myFunc:
NSLog(@"target-action : %@-%@", target, NSStringFromSelector(action));
} else if (strcmp(retType, @encode(NSInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
invocation.target = self;
invocation.selector = sel;
[invocation setArgument:¶ms atIndex:2];
[invocation invoke];
NSInteger ret = 0;
[invocation getReturnValue:&ret];
NSLog(@"NSInteger ret %ld", (long)ret);
} else if (strcmp(retType, @encode(NSUInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
invocation.target = self;
invocation.selector = sel;
[invocation setArgument:¶ms atIndex:2];
[invocation invoke];
NSUInteger ret = 0;
[invocation getReturnValue:&ret];
NSLog(@"NSUInteger ret %ld", (long)ret);
} else if (strcmp(retType, @encode(BOOL)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
invocation.target = self;
invocation.selector = sel;
[invocation setArgument:¶ms atIndex:2];
[invocation invoke];
BOOL ret = false;
[invocation getReturnValue:&ret];
NSLog(@"BOOL ret %ld", (long)ret);
} else if (strcmp(retType, @encode(CGFloat)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
invocation.target = self;
invocation.selector = sel;
[invocation setArgument:¶ms atIndex:2];
[invocation invoke];
CGFloat ret = 0;
[invocation getReturnValue:&ret];
NSLog(@"CGFloat ret %ld", (long)ret);
} else {
/// performSelector比较严格,若是返回值不匹配,则极可能会致使crash。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id ret = [self performSelector:sel withObject:params];
#pragma clang diagnostic pop
NSLog(@"id ret %@", ret);
}
}
复制代码
注意,这里NSInvocation默认不会强引用其各个参数,因此若参数在NSInvocation执行前就被释放则会形成野指针异常(EXC_BAD_ACCESS)。
若是有须要强引用参数的场景,如延迟执行invoke方法,则须要对参数进行强持有操做。调用retainArguments便可,同时有一个属性argumentsRetained能够用来判断。
请看开发者文档的详细描述,注意其中的细节:对象类型是retain操做,而C-string和blocks则是copy操做。
Instance Method
retainArguments
If the receiver hasn’t already done so, retains the target and all object arguments of the receiver and copies all of its C-string arguments and blocks. If a returnvalue has been set, this is also retained or copied.
Declaration
- (void)retainArguments;
Discussion
Before this method is invoked, argumentsRetained returns NO; after, it returns YES.
For efficiency, newly created NSInvocation objects don’t retain or copy their arguments, nor do they retain their targets, copy C strings, or copy any associated blocks. You should instruct an NSInvocation object to retain its arguments if you intend to cache it, because the arguments may otherwise be released before the invocation is invoked. NSTimer objects always instruct their invocations to retain their arguments, for example, because there’s usually a delay before a timer fires.
复制代码
getReturnValue方法,仅仅将返回数据拷贝到指定内存区域,并不考虑内存管理。若返回对象类型,则为__unsafe_unretained。优化办法有:
手动添加一个强持有,则返回对象会自动添加autorelease关键字,不会出现野指针异常。
NSObject __unsafe_unretained *tmpRet;
[invoke getReturnValue:&tmpRet];
NSObject *ret = tmpRet;
return ret;
复制代码
或者,使用__bridge进行类型转换,这种作法更为推荐。
void *tmpRet = NULL;
[invoke getReturnValue:&tmpRet];
NSObject *ret = (__bridge NSObject *)tmpRet;
return ret;
复制代码
若是对属性使用了@dynamic关键字,则编译器不会自动为其生成getter/setter方法,而是经过动态查找的方法。
因此,若是使用了@dynamic关键字,而没有手动添加getter/setter方法,则使用时会出错。
@property (nonatomic, copy) NSString *dynamicName;
...
@implementation Person
@dynamic dynamicName;
...
@end
复制代码
能够经过resolveInstanceMethod:方法动态添加getter/setter便可。
OC的面向对象是单一继承的关系。若是有个别场景须要用到多继承,能够是要forwardInvocation:来实现。不过不到万不得已,最好不要这样作。
// 用于描述被转发的消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector");
id p1 = [[NSClassFromString(@"BasePerson1") alloc] init];
if ([p1 respondsToSelector:aSelector]) {
return [p1 methodSignatureForSelector:aSelector];
}
id p2 = [[NSClassFromString(@"BasePerson2") alloc] init];
if ([p2 respondsToSelector:aSelector]) {
return [p2 methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation");
SEL sel = anInvocation.selector;
id p1 = [[NSClassFromString(@"BasePerson1") alloc] init];
if ([p1 respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p1];
}
id p2 = [[NSClassFromString(@"BasePerson2") alloc] init];
if ([p2 respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p2];
}
}
复制代码