博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS消息转发机制
阅读量:6270 次
发布时间:2019-06-22

本文共 4546 字,大约阅读时间需要 15 分钟。

  hot3.png

笔者前不久终于发布了自己的APP《小印记》,特此分享了一些iOS源码,如果读者学到了有用的东西,希望能前往App Store下载《小印记》支持一下笔者,谢谢!

[《小印记》iOS源码分享--极光推送实践篇](https://my.oschina.net/Jacedy/blog/863945)

[《小印记》iOS源码分享--自定义弹框篇](https://my.oschina.net/Jacedy/blog/864649)

[《小印记》源码分享--极光推送服务器篇](https://my.oschina.net/Jacedy/blog/864169)

[《小印记》iOS源码分享--HTTPS配置篇](https://my.oschina.net/Jacedy/blog/864845)

[《小印记》iOS源码分享--网络层封装篇](https://my.oschina.net/Jacedy/blog/868194)

*****************************************************************************************

    在Objective-C中,使用对象进行方法调用是一个消息发送的过程(Objective-C采用“动态绑定机制”,所以所要调用的方法直到运行期才能确定)。

    方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,苹果就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。

    OC的运行时在程序崩溃前提供了三次拯救程序的机会:

方案一:

+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel

方案二:

- (id)forwardingTargetForSelector:(SEL)aSelector

方案三:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

211220_TxEr_580523.jpg

 

    上图显示了消息转发的具体流程,接收者在每一步中均有机会处理消息。步骤越往后处理消息的代价越大。首先,会调用

+ (BOOL)resolveInstanceMethod:(SEL)sel。若方法返回YES,则表示可以处理该消息。在这个过程,可以动态地给消息增加方法。

// Person.m// 不自动生成getter和setter方法@dynamic name; + (BOOL)resolveInstanceMethod:(SEL)sel{    if (sel == @selector(name)) {        // BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)        class_addMethod(self, sel, (IMP)GetterName, "@@:");        return YES;    }    if (sel == @selector(setName:)) {        class_addMethod(self, sel, (IMP)SetterName, "v@:@");        return YES;    }        return [super resolveInstanceMethod:sel];}// (用于类方法)//+ (BOOL)resolveClassMethod:(SEL)sel//{//    NSLog(@"resolveClassMethod called %@", NSStringFromSelector(sel));//    //    return [super resolveClassMethod:sel];//}id GetterName(id self, SEL cmd){    NSLog(@"%@, %s", [self class], sel_getName(cmd));    return @"Getter called";}void SetterName(id self, SEL cmd, NSString *value){    NSLog(@"%@, %s, %@", [self class], sel_getName(cmd), value);        NSLog(@"SetterName called");

签名符号含义:

*          代表  char * char BOOL  代表  c:          代表  SEL ^type      代表  type *@          代表  NSObject * 或 id^@         代表  NSError ** #          代表  NSObject v          代表  void
// main.m/* 现在在main.m中给Person发送setName:和name消息,由于Person中未实现这两个方法,就会经消息转发调用GetterName和SetterName方法*/Person *person = [[Person alloc] init];        [person setName:@"Jake"];        NSLog(@"%@", [person name]);
// 输出结果:Person, setName:, JakeSetterName calledPerson, nameGetter called

    

    若方法返回NO,则进行消息转发的第二步,查找是否有其它的接收者。对应的处理函数是:

- (id)forwardingTargetForSelector:(SEL)aSelector。可以通过该函数返回一个可以处理该消息的对象。

    现在新建一个类Child,在Child中实现一个eat方法,在Person类中定义eat方法但不实现它。

// Child.m- (void)eat{    NSLog(@"Child method eat called");}

    然后在Person类中实现forwardingTargetForSelector:方法:

// Person.m// 当调用Person中的eat方法时,由于Person中并未实现该方法,就会经下面的方法将消息转发给可以处理eat方法的对象- (id)forwardingTargetForSelector:(SEL)aSelector{    NSString *selStr = NSStringFromSelector(aSelector);        if ([selStr isEqualToString:@"eat"]) {        return [[Child alloc] init];        // 这里返回Child类对象,让Child去处理eat消息    }    return [super forwardingTargetForSelector:aSelector];}
// main.m[person eat];
// 输出结果:Child method eat called

    通过此方案,我们可以用“组合”来模拟出“多重继承”的某些特性。在一个对象内部,可能还有一系列其他对象,该对象可以经由此方法将能够处理某选择子的相关内部对象返回,这样的话,在外界看来好像是该对象亲自处理了这些消息。

    伪多继承与真正的多继承的区别在于,真正的多继承是将多个类的功能组合到一个对象中,而消息转发实现的伪多继承,对应的功能仍然分布在多个对象中,但是将多个对象的区别对消息发送者透明。

 

    若第二步返回nil,则进入消息转发的第三步。调用

- (void)forwardInvocation:(NSInvocation *)anInvocation。这个方法实现得很简单。只需要改变调用目标,使消息在新目标上得以调用即可。不过,如果采用这种方式,实现的效果与第二步的消息转发是一致的。所以比较有用的实现方式是:先以某种方式改变消息内容,比如追加另外一个参数,或者改换选择子,等等。

// Person.m- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{     NSString *sel = NSStringFromSelector(aSelector);    // 判断要转发的SEL    if ([sel isEqualToString:@"sleep"]) {        // 为转发的方法手动生成签名        return [NSMethodSignature signatureWithObjCTypes:"v@:"];    }        return [super methodSignatureForSelector:aSelector]; }- (void)forwardInvocation:(NSInvocation *)anInvocation{    SEL selector = [anInvocation selector];    // 新建需要转发消息的对象    Child *child = [[Child alloc] init];    if ([child respondsToSelector:selector]) {        // 唤醒这个方法        [anInvocation invokeWithTarget:child];    }}
// Child.h#import 
@interface Child : NSObject- (void)eat;- (void)sleep;@end
// Child.m- (void)sleep{    NSLog(@"Child method sleep called");}
// 输出结果:Child method sleep called

    

    有时候服务器很烦不靠谱,老是不经意间返回null,可以重写NSNull的消息转发方法, 让他能处理这些异常的方法,达到解决问题的目的。

转载于:https://my.oschina.net/Jacedy/blog/625343

你可能感兴趣的文章
jacky自问自答-java并发编程
查看>>
Struts2+JSON数据
查看>>
zTree实现单独选中根节点中第一个节点
查看>>
Cocos2D-x设计模式发掘之中的一个:单例模式
查看>>
很强大的HTML+CSS+JS面试题(附带答案)
查看>>
用树莓派实现RGB LED的颜色控制——C语言版本号
查看>>
VC2012编译CEF3-转
查看>>
java 自己定义异常,记录日志简单说明!留着以后真接复制
查看>>
Android 使用AIDL实现进程间的通信
查看>>
机器学习(Machine Learning)&深度学习(Deep Learning)资料
查看>>
jquery的图片轮播 模板类型
查看>>
C# 获取文件名及扩展名
查看>>
Web安全学习计划
查看>>
输出有序数组的连续序列范围
查看>>
zinnia项目功能分析
查看>>
windows cmd for paramiko
查看>>
SQL经典面试题集锦
查看>>
View学习(一)-DecorView,measureSpec与LayoutParams
查看>>
色彩力量!21款你应该知道的优秀品牌设计
查看>>
SDUT 3503 有两个正整数,求N!的K进制的位数
查看>>