最后更新: 2018-01-17

一、消息派发机制-NSObject

在 iOS 开发中, 调用对象的方法就是给对象发送一个消息。了解消息的派发机制对于iOS开发来说是一个很实用且强大的工具, 下面我将对其详细说明;

当实例化一个Person对象 p, 调用 [p run], 那么派发机制是什么样的呢?

Step1:

在当前对象 p 中寻找是否有 run 方法,如果有,执行; 如果没有, 接着往下执行;

Step2:

p 的父类中寻找是否有 run方法, 有则执行,没有的话,接着往上找, 如果还没找到,接着往下执行;

Step3: 系统调用如下方法:

// 类方法
+ (BOOL)resolveClassMethod:(SEL)sel; // 实例化方法
+ (BOOL)resolveInstanceMethod:(SEL)sel

可以在这个方法中动态的为对象添加对应的方法,例如我添加run方法:

void run(id self, SEL _cmd) {
NSLog(@"%@ -- %s", self, sel_getName(_cmd));
} + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(run)) {
class_addMethod(self, sel, (IMP)run, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}

Step4: 调用 - (id)forwardingTargetForSelector:(SEL)aSelector来进行转发

- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector: %@", NSStringFromSelector(aSelector));
return [[Car alloc] init];
}

注意:转发机制,不能用于返回自身对象,否则会进入一个死循环

Step5: 系统会调用如下方法.

- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation");
SEL selector = [anInvocation selector]; Car *car = [[Car alloc] init];
if ([car respondsToSelector:selector]) {
[anInvocation invokeWithTarget:car];
return;
}
return [super forwardInvocation:anInvocation];
} - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"methodSignatureForSelector"); NSString *selName = NSStringFromSelector(aSelector);
if ([selName isEqualToString:@"run"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}

注意: 如果 methodSignatureForSelector: 返回为nil, 那么 forwardInvocation: 将不会调用, 直接抛出错误;

Step6: Crash

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person run]: unrecognized selector sent to instance 0x6040000194c0'

以上就是 OC 中的 NSObject 对象中的完整的消息派发机制.

关于swift 的派发机制可以参考这篇文章-深入理解 Swift 派发机制;

值得提醒的是,在Swift中, Step5的两个方法是 OBJC_SWIFT_UNAVAILABLE("")的。具体原因可以查看官方的这篇文章-What Happened to NSMethodSignature?


二、 NSObject 与 NSProxy

在 Foundation框架中开发中, NSObject 是大多数类的基类, 而基类 NSProxy 却鲜为人知;

NSob

查看 NSProxy文档, 它遵循着 protocol NSObject; 与 NSObject 相比, NSProxy更加的简洁; 没有 init 或者 new 方法。 在消息转发的实现上, 仅存在如下两个方法:

- (void)forwardInvocation:(NSInvocation *)invocation;

// swift 还不能用。
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
  1. NSObject 对象与 NSProxy 对象均会对 performSelector方法进行转发 这里与张不坏-NSProxy 所得出的结论不同;
  2. 相比于NSObject, NSProxy会将自省的方法直接forward -forwardInvocation:中, 其中包括:
    - (BOOL)isKindOfClass:(Class)aClass;
    - (BOOL)isMemberOfClass:(Class)aClass;
    - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector;
  3. 对于 category 方法, 经过测试, NSObject的category部分是无法进行转发的,但是自定义的category里面的方法,是可以进行转发, 而对于 NSProxy, 目前在系统中没有找到对应的 Category方法,自定义的是可以进行转发的;

上述的测试内容,你可以可以在这里下载到对应的方法


三、实践

关于消息转发, 显然NSObject 与 NSProxy 均能完成此动作,而且大多数由于 NSObject 开源, 而且日常接触比较多, 可控, 很多人偏向使用 NSObject 来完成消息转发的行为。 我个人认为, 苹果设计出了 NSProxy, 而且经过这么多年, 在消息转发上, NSProxy 应该更适合,正如官方文档所说:

Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object

网上关于 NSProxy 的资料不算多, 有这篇-实战: 利用NSProxy实现消息转发-模块化的网络接口层设计, 最后作者也去使用 Category 来设计了, 囧, 还有这篇-利用NSProxy解决NSTimer内存泄漏问题

在使用场景上, 我司在设计iOS客户端统计上面, 集成和很多的统计. 在设计这个结构的时候, 就使用了 NSProxy, 通过任务分发的方式, 将统计的数据分发至各个平台

@interface BehaviorCollectorDispatcher : NSProxy <BehaviorCollector>

- (instancetype)initWithBehaviorCollector:(NSArray<id<BehaviorCollector>> *)collectors;

@end

@implementation BehaviorCollectorDispatcher {

    Protocol *_protocol;
NSArray<id<BehaviorCollector>> *_collectors;
} - (instancetype)initWithBehaviorCollector:(NSArray<id<BehaviorCollector>> *)collectors {
_protocol = @protocol(BehaviorCollector);
_collectors = collectors;
return self;
} - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature;
struct objc_method_description theDescription = protocol_getMethodDescription(_protocol, selector, YES, YES);
signature = [NSMethodSignature signatureWithObjCTypes:theDescription.types];
return signature;
} - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL selector = anInvocation.selector; const char* name = sel_getName(selector);
NSString *selectorName = [NSString stringWithUTF8String:name]; if ([selectorName hasPrefix:@"collect"]) {
for (id<BehaviorCollector> collector in _collectors) {
[anInvocation invokeWithTarget:collector];
}
} else {
[super forwardInvocation:anInvocation];
}
} @end

iOS 消息转发以及 NSProxy 实战的更多相关文章

  1. iOS消息转发机制

    iOS消息转发机制 “消息派发系统”(message-dispatch system) 若想令类能够理解某条消息,我们必须实现出对应的方法才行.但是,在编译器向类发送其无法解读的消息时并不会报错,因为 ...

  2. iOS消息转发

    消息转发是一种功能强大的技术,可以大大增加Objective-C的表现力.什么是消息转发?简而言之,它允许未知的消息被困住并作出反应.换句话说,无论何时发送未知消息,它​​都会以一个很好的包发送到您的 ...

  3. iOS 消息转发机制

    这篇博客的前置知识点是 OC 的消息传递机制,如果你对此还不了解,请先学习之,再来看这篇.这篇博客我尝试用口语的方式像讲述 PPT 一样给大家讲述这个知识点. 我们来思考一个问题,如果对象在收到无法解 ...

  4. iOS - 消息转发处理

    详细运行时基础 NSInvocation介绍 NSHipster-Swizzling Objective-C Method相关方法分析 Type Encodings Objc是OOP,所以有多态. 当 ...

  5. ios 消息转发初探

    有时候服务器的接口文档上一个数据写的是string类型,这时候你就会直接把它赋值给一个label. 问题来了,有时候这个string的确是string,没有问题,有时候又是NSNumber,当然不管三 ...

  6. iOS 消息转发

    消息转发   delegate和protocol   类别      消息转发   当向someObject发送某消息,但runtime system在当前类和父类中都找不到对应方法的实现时,runt ...

  7. iOS的消息转发机制详解

    iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添加该方法即可,使得编译器在编译时能正确找到该方法:或者,还有另外的方法,由于Objec ...

  8. iOS开发-消息转发

    消息转发是OC运行时比较重要的特性,Objective-C运行时的主要的任务是负责消息分发,我们在开发中"unrecognized selector sent to instance xx& ...

  9. iOS开发·runtime原理与实践: 消息转发篇(Message Forwarding) (消息机制,方法未实现+API不兼容奔溃,模拟多继承)...

    本文Demo传送门: MessageForwardingDemo 摘要:编程,只了解原理不行,必须实战才能知道应用场景.本系列尝试阐述runtime相关理论的同时介绍一些实战场景,而本文则是本系列的消 ...

随机推荐

  1. 关于js计算非等宽字体宽度的方法

    准备一个容器 首先在body外插入一个absolute的容器避免重绘: const svgWidthTestContainer = document.createElement('svg'); svg ...

  2. 【golang】浅析rune数据类型

    golang中string底层是通过byte数组实现的.中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8. golang中还有一个byte数据 ...

  3. java 导出自定义样式excel

    由于项目需要 要求导出一个这样的表格 然而 正常导出的表格都是这样婶儿地 这种格式网上demo有很多就不详细说了 ,主要说说上面三行是怎么画的. 第一行大标题,是9行合并成的一行,而且字体大小需要单独 ...

  4. IE6兼容笔记

    1.IE6中,元素右浮动的时候前面不能有文本或内联元素,否则会换行独占一行 解决办法:将浮动元素放到文本或内联元素前面,大都在制作新闻列表的时候会遇到这种问题. 未完,待续!

  5. Android判断是debug还是release模式

    1.当有些功能不希望在release模式实现时,但是debug模式又需要的时候,就可以对当前版本模式进行判断.如是debug模式则日志输出级别设置为Level.DEBUG,release模式设置为Le ...

  6. ios UICollectionView 加载数据后 滑动卡顿问题

    最近项目的资源图片变大了,滑动时总是卡顿,在这里用NSOperationQueue解决了一下 .h 文件 @interface CollectionViewCell : UICollectionVie ...

  7. AIX 6.1创建逻辑卷并挂载【smitty】

    1.创建卷组 #mkvg  -y   datavg     hdisk2   hdisk3   #smitty   vg

  8. phpstorm配置总结

    phpstorm配合laravel框架作为项目开发,需要添加自动提示,减少查看文档的次数,本次使用的是idel-helper插件 在当前项目下 编辑composer.json文件文件,添加如下字符 & ...

  9. GPT-2,吓坏创造者的「深度造假写手」

    简评: 今年二月份刷屏的 GPT-2 着实厉害,那个生成续写故事的例子更是效果好到吓人一跳,它到底有多厉害,本文略微讲讲.更详细的信息可参考文末 OpenAI 的博客链接. 你能从下面这两段文字里品味 ...

  10. [SCOI2016]幸运数字(线性基,倍增)

    [SCOI2016]幸运数字 题目描述 A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一.每座城市都有一个幸运数字,以纪念碑的形式矗立在这座城市的正中心,作 ...