OC消息转发机制

OC方法调用发送消息,如果找不到对应名称的方法时,在发生Crash之前有三次机会进行补救。

  1. 动态方法解析
  2. 备用接受者
  3. 消息转发
动态方法解析

对象在接收到未知的消息时,首先会调用所属类的类方法 +resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。
这个方法返回值Bool类型,表示这个类能否新增一个方法处理这种情况。使用这种办法,需要实现写好对应的方法实现代码,在运行时的时候添加插入就好了。

这个方法常用来实现@dynamic 属性的setter getter 方法
e.g.


//
//  TestObjA.h
//  test
//
@interface TestObjA : NSObject
@property(nonatomic , strong) NSString *testStr;
@end

//
//  TestObjA.m
//  test
//
@implementation TestObjA
@dynamic testStr;
@end

这种情况下调用setter 和 getter方法会发生crash


    TestObjA *obj = [[TestObjA alloc] init];
    [obj setTestStr:@"haha"];
    NSString *result = [obj testStr];
    NSLog(result);

下面演示了用此方法实现@dynamic 属性


//
//  TestObjA.h
//  test
//
@interface TestObjA : NSObject
@property(nonatomic , strong) NSString *testStr;
@property (nonatomic, strong) NSString *storeStr;
@end

//
//  TestObjA.m
//  test
//
void dynamicStrSetter (id self,SEL _cmd, id value) {
    TestObjA *obj = (TestObjA *)self;
    obj.storeStr = (NSString *)value;
}

id dynamicStrGetter (id self,SEL _cmd) {
    TestObjA *obj = (TestObjA *)self;
    return obj.storeStr;
}


@implementation TestObjA
@dynamic testStr;

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(setTestStr:)) {
        class_addMethod(self, sel, (IMP)dynamicStrSetter, "v@:@");
        return true;
    }else if(sel == @selector(testStr)) {
        class_addMethod(self, sel, (IMP)dynamicStrGetter, "@@:");
        return true;
    }
    return [super resolveClassMethod:sel];
};
@end

这样在调用setter和getter方法的时候会调用之前实现好的C语言方法,保证程序正常运行。

备用接受者

如果上一步没有实现,会继续调用下面的方法

- (id)forwardingTargetForSelector:(SEL)aSelector

这个方法返回一个非空对象,这个对象会作为这个方法新的接受者,重新进行消息发送。

e.g.
新建一个TestObjB对象


//
//  TestObjB.h
//  test
//
@interface TestObjB : NSObject
- (void)testFunc;
@end

//
//  TestObjB.m
//  test
//
@implementation TestObjB
- (void)testFunc {
    NSLog(@"test"); 
}
@end

然后在TestObjA 中实现下面方法


- (id)forwardingTargetForSelector:(SEL)aSelector {
    return [[TestObjB alloc] init];
}

现在再进行一下测试:


     TestObjA *obj = [[TestObjA alloc] init];
    [obj performSelector:@selector(testFunc) withObject:nil];

会发现有打印输出:test

消息转发

将上面第二步TestObjA 中的代码删除,添加一下代码:


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

    if (!signature) {
        if ([TestObjB instancesRespondToSelector:aSelector]) {
            signature = [TestObjB instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([TestObjB instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:[[TestObjB alloc] init]];
    }
}

然后运行上次的测试代码,结果仍然可以打印输出:test

在这个阶段,会先调用 -(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector 生成一个方法签名

然后调用 - (void)forwardInvocation:(NSInvocation *)anInvocation 方法进行消息转发。

注意: anInvocation 的初始化必须要 方法签名: NSMethodSignature

至此,三个可以拦截未知消息crash的步骤已经完成。如果都没有进行处理,运行时会调用NSObject的forwardInvocation:方法,这个方法的实现只是简单调用了doesNotRecognizeSelector:方法,它不会转发任何消息,直接抛出异常。

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值