@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *age;
@end
@implementation ViewController
// 告诉编译器,不要生成set 和 get方法,但是调用get 和set也不会出错
@dynamic name;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.name = @"pangshishan";
}
上述代码中中name是一个属性,在@implementation中,用@dynamic修饰之后,name属性将不会生成getter 和 setter方法。在下面这段代码中,用setter方法给name复制,系统会抛出“ -[ViewController setName:]: unrecognized selector sent to instance 0x7f95f2f07100”异常,这是由于系统找不到setName:这个方法。用runtime消息转发,有三次机会可以不让系统抛出这个异常(当然,把这个方法写上肯定是没问题的,但我们今天主题是:Runtime的转发机制)
我们在SomeOtherObject的 .m中实现setName:方法,让SomeOtherObject接受未处理的消息
#pragma mark - 1.重定向方法,在本类里进行方法重定向处理
void dynamicMethodIMP(id self, SEL _cmd)
{
NSLog(@"dynamicMethodIMP -- %@", NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"sel is %@", NSStringFromSelector(sel));
// 当方法是setter方法时
if (sel == @selector(setName:)) {
// 动态给本类添加方法
class_addMethod([SomeOtherObject class], sel, (IMP)dynamicMethodIMP, "v@:");
}
return [super resolveInstanceMethod:sel];
}
#pragma mark - 2. 找个类处理她
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"setName:"]) {
return [[SomeOtherObject alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
#pragma mark - 3. 最后一步,把selector的所有细节包程NSInvocation, invocation对象负责分发消息给其他对象,这一步是完整的转发机制
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// (1)把2注释掉,1解开注释,依然有效
// if ([NSStringFromSelector(aSelector) isEqualToString:@"setName:"]) {
// return [NSMethodSignature signatureWithObjCTypes:"v@:"];
// }
// return [super methodSignatureForSelector:aSelector];
// (2)
NSMethodSignature *signature = [super methodSignatureForSelector: aSelector];
if (signature == nil) {
if ([SomeOtherObject instancesRespondToSelector:aSelector]) {
signature = [SomeOtherObject instanceMethodSignatureForSelector:aSelector];
}
}
return signature; // 只有当返回值不是空的时候才能转发成功
}
// anInvocation里打包了此类不能处理的selector所有的细节
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 将anInvocation里打包的消息,发送给SomeOtherObject处理,如果SomeOtherObject也不能处理,可以在它里面继续转发
// 这也是变相的实现了多继承,但是消息转发只是将一部分功能放到其他类,并没有多继承造成的代码过于丰富的问题
[anInvocation invokeWithTarget:[[SomeOtherObject alloc] init]];
}
以上简单的介绍了一下Runtime的消息转发机制,这是我写的demo,感兴趣的小伙伴可以下载