Skip to content

Latest commit

 

History

History
213 lines (143 loc) · 5.23 KB

message_forwarding.md

File metadata and controls

213 lines (143 loc) · 5.23 KB

消息转发及其实际应用

简介

在Objective-C中,在调用对象的某个方法时,其实是在向这个对象发送消息。系统会查看这个对象能否接收该消息,如果不能,则会进行消息转发,消息转发最多三个步骤(注:如果前一步成功处理了消息,那么就不会走到下一步):

  1. 调用resolveInstanceMethod:resolveClassMethod:来决定是否动态添加方法。如果动态添加,则消息得到处理,消息转发结束;否则,进入下一步。

  2. 调用forwardingTargetForSelector:来确定能不能把这条消息转给其他接收者处理,如果返回一个非self的对象,则会把消息发送给该对象,消息转发结束;否则,进入下一步。

  3. 通过methodSignatureForSelector:方法获取签名,如果签名为nil,则消息无法处理,抛出异常;否则,调用forwardInvocation:方法,调用成功则消息转发结束,调用失败则消息无法处理,抛出异常。

注:步骤越往后,处理消息的代价越大。


***

实例

例一:通过resolveInstanceMethod:ViewController类的name属性动态添加 getter & setter 方法

// ViewController.m

#import <objc/runtime.h>
#import "ViewController.h"


@interface ViewController()

@property (nonatomic, strong) NSString *name;

@end


@implementation ViewController2

@dynamic name;

id GetterName(id self, SEL cmd) {
    return objc_getAssociatedObject(self, cmd);
}

void SetterName(id self, SEL cmd, NSString *value) {
    NSString *sel = NSStringFromSelector(cmd);
    NSString *key = [sel substringWithRange:NSMakeRange(3, sel.length - 4)].lowercaseString;
    objc_setAssociatedObject(self, NSSelectorFromString(key), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setName:@"test"];
    NSLog(@"get name: %@", [self name]);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(name)) {
        // "@@:"的意思:
        // 第一个字符表示函数的返回值类型,"@"表示GetterName函数的返回值类型为id
        // 后面的字符表示函数的参数类型,"@:"表示GetterName函数接收两个参数,
        // "@"表示参数类型为id,":"表示参数类型为SEL
        class_addMethod(self, sel, (IMP)GetterName, "@@:");
        return YES;
    }
    if (sel == @selector(setName:)) {
        // "v@:@"的意思:
        // "v"表示SetterName函数的返回值类型为void
        // "@:@"参见上面
        class_addMethod(self, sel, (IMP)SetterName, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

例二:通过forwardingTargetForSelector:将发给ViewController类的name消息,转给Person类来处理

// ViewController.m

#import <objc/runtime.h>
#import "ViewController.h"
#import "Person.h"


@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"name: %@", [self performSelector:@selector(name)]);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *sel = NSStringFromSelector(aSelector);
    
    if ([sel isEqualToString:@"name"]) {
        return [Person new];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

@end
// Person.h

@interface Person : NSObject

- (NSString *)name;

@end


// Person.m

#import "Person.h"

@implementation Person

- (NSString *)name {
    return @"I'm a tester";
}

@end

注意:resolveInstanceMethod:resolveClassMethod:方法必须返回NO,否则不会调用forwardingTargetForSelector:方法。


例三:通过methodSignatureForSelector:forwardInvocation:,将发送给ViewController类的消息name转发给Person类处理

#import <objc/runtime.h>
#import "ViewController.h"
#import "Person.h"


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"name: %@", [self performSelector:@selector(name)]);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    // 这里必须返回NO
    return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    // 这里必须返回nil
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *sel = NSStringFromSelector(aSelector);
    if ([sel isEqualToString:@"name"]) {
        // 为转发的方法手动生成签名
        return [NSMethodSignature signatureWithObjCTypes:"@@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    // 将消息转给Person类
    Person *person = [Person new];
    if ([person respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:person];
    }
}

@end
// Person.h

@interface Person : NSObject

- (NSString *)name;

@end


// Person.m

#import "Person.h"

@implementation Person

- (NSString *)name {
    return @"I'm a tester";
}

@end

注意:resolveInstanceMethod:resolveClassMethod:方法必须返回NO,且forwardingTargetForSelector:必须返回nil,否则不会调用methodSignatureForSelector:forwardInvocation:方法。