在OC中,当像一个对象发送消息,而对象找到消息后,从它的类方法列表,父类方法列表,一直找到根类方法列表都没有找到与这个选择子对应的函数指针。那么这个对象就会触发消息转发机制。

OC对象的继承链和isa指针链如图:

消息转发流程如下:
1.先调用实例方法resolveInstanceMethod
如果作者在这里使用runtime动态添加对应的方法,并且返回yes。就万事大吉。对象找到了处理的方法,
并且将这个新增的方法添加到类的方法缓存列表
2.如果上面的方法返回NO的话,对象会调用forwardingTargetForSelector方法
允许作者选择其他的对象,处理这个消息。
这个方法,也是待会我们要做文章的地方。画重点。
3.如果上面两个方法都没有做处理,那么对象会执行最后一个方法methodSignatureForSelector,提供一个有效的方法签名,若提供了有效的方法签名,程序将会通过forwardInvocation方法执行签名。若没有提供方法签名就会触发doesNotRecognizeSelector方法,触发崩溃。

整个调用流程图如下:

 整个代码调用顺序如下:

//
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"1---%@",NSStringFromSelector(sel));
NSLog(@"1---%@",NSStringFromSelector(_cmd));
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"1---%@",NSStringFromSelector(sel));
NSLog(@"1---%@",NSStringFromSelector(_cmd));
return NO;
}
//
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"2---%@",NSStringFromSelector(aSelector));
NSLog(@"2---%@",NSStringFromSelector(_cmd));
return nil;
}
//3.最后一步,返回方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"3---%@",NSStringFromSelector(aSelector));
NSLog(@"3---%@",NSStringFromSelector(_cmd));
if ([NSStringFromSelector(aSelector) isEqualToString:@"gogogo"]) {
return [[UnknownModel2 new] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
//3.1处理返回的方法签名
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"4---%@",NSStringFromSelector(_cmd));
NSLog(@"4-最后一步--%@",anInvocation);
if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"gogogo"]) {
[anInvocation invokeWithTarget:[UnknownModel2 new]];
}else{
[super forwardInvocation:anInvocation];
}
}
//触发崩溃
- (void)doesNotRecognizeSelector:(SEL)aSelector { }

打印结果如下:

-- ::00.469445+ iOS_KnowledgeStructure[:] ---gogogo
-- ::00.469613+ iOS_KnowledgeStructure[:] ---resolveInstanceMethod:
-- ::00.469765+ iOS_KnowledgeStructure[:] ---gogogo
-- ::00.469873+ iOS_KnowledgeStructure[:] ---forwardingTargetForSelector:
-- ::00.469978+ iOS_KnowledgeStructure[:] ---gogogo
-- ::00.470097+ iOS_KnowledgeStructure[:] ---methodSignatureForSelector:
-- ::00.470247+ iOS_KnowledgeStructure[:] ---_forwardStackInvocation:
-- ::00.470355+ iOS_KnowledgeStructure[:] ---resolveInstanceMethod:
-- ::00.470765+ iOS_KnowledgeStructure[:] ---forwardInvocation:
-- ::00.471367+ iOS_KnowledgeStructure[:] -最后一步--<NSInvocation: 0x600002442000>
-- ::00.471969+ iOS_KnowledgeStructure[:] lalalalala---gogogo
 
OC消息转发的应用
 
当消息转发走到第二步时forwardingTargetForSelector,会让对象提供一个第三者来处理这个消息。
那么可以得出结论:只要对对象发送没有实现的消息,对象最后就会寻找一个第三者来接收这个消息。

下面就利用消息转发机制,构建装饰器,来实现图像滤镜功能。

 科普一下装饰器模式。

装饰器模式概念:
装饰器模式是向对象添加东西(行为),而不破坏原有对象内容结构的一种设计模式。举个例子,对象如同照片,装饰器如同相框。而一张照片可以放到多种相框内产生多种赏心悦目的效果,而又不会对照片产生改变。
 
装饰器模式UML图:

说明如下:
1.Component为抽象父类,它为组件声明了一些操作。
ConcreteComponent为实例组件类,相当于图像滤镜中的原材料“图片”。
2.Decorator为从Component父类实现而来的子抽象类,它是装饰器的抽象父类。
它里面包含了组件“图片”(图中的属性:component)的引用。
3.Component父类,Decorator父类都包含了operation接口。
4.下面的“由装饰器扩展功能”的标示部分,展示了用装饰器为组件“图片”添加功能的实际使用。

图像滤镜的UML类图为: 

图像滤镜的uml类图同装饰器类图的uml结构一致。
ImageComponent抽象父类定义接口,UIImage作为实例组件。
ImageFilter作为滤镜父类接口,扩充类apply方法。并且对组件(component)添加引用。
 
重点 重点 重点:
在 forwardingTargetForSelector中先调用自己的apply方法,然后返回它所引用的component.
1.因为ImageFilter装饰器中没有draw:方法,所以向Image对象发送[self setNeedDisplay]消息时,ImageFilter对象会调用自己的forwardingTargetForSelector方法,这方法内包含了当前装饰器的功能扩展,会执行扩展功能。
2.方法的最后有return component; 这一句是进行消息转发,让component对象进行处理这次绘制。

 主要代码实现如下:

mageComponent抽象父类接口设计如下:

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ZHFImageComponent <NSObject>
- (void)drawAtPoint:(CGPoint)point;
- (void)drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void)drawInRect:(CGRect)rect;
- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void)drawAsPatternInRect:(CGRect)rect;
@end
NS_ASSUME_NONNULL_END
 
Image实例组件代码如下:
只是声明了遵守ImageComponent的协议。
#import <UIKit/UIKit.h>
#import "ZHFImageComponent.h"
NS_ASSUME_NONNULL_BEGIN
@interface UIImage (ZHFImageComponent) <ZHFImageComponent>
@end
NS_ASSUME_NONNULL_END

装饰器接口代码如下:

.h文件

#import <Foundation/Foundation.h>
#import "ZHFImageComponent.h"
NS_ASSUME_NONNULL_BEGIN
@interface ZHFImageFilter : NSObject <ZHFImageComponent>
{
@private
id<ZHFImageComponent> component_;
}
@property (nonatomic, strong) id<ZHFImageComponent> component;
- (instancetype)initWithImageComponent:(id<ZHFImageComponent>)component;
- (void)apply;
- (id)forwardingTargetForSelector:(SEL)aSelector;
@end
NS_ASSUME_NONNULL_END

.m文件

#import "ZHFImageFilter.h"
@implementation ZHFImageFilter
@synthesize component = component_;
- (instancetype)initWithImageComponent:(id<ZHFImageComponent>)component {
if (self = [super init]) {
self.component = component;
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) hasPrefix:@"draw"]) {
[self apply];
}
//使用消息转发给另一个对象处理,来实现任务处理链条,非常巧妙!!!
return component_;
}
@end

forwardingTargetForSelector方法的实现是整个装饰器的灵魂,子类其实只是调用父类的这个方法而已。

形变装饰器代码如下:

#import "ZHFImageFilter.h"
NS_ASSUME_NONNULL_BEGIN
@interface ZHFImageTransformFilter : ZHFImageFilter
{
@private
CGAffineTransform transform_;
}
@property (nonatomic, assign) CGAffineTransform transform;
- (instancetype)initWithImageComponent:(id<ZHFImageComponent>)component
transform:(CGAffineTransform)transform;
@end
NS_ASSUME_NONNULL_END #import "ZHFImageTransformFilter.h"
@implementation ZHFImageTransformFilter
@synthesize transform = transform_;
- (instancetype)initWithImageComponent:(id<ZHFImageComponent>)component
transform:(CGAffineTransform)transform {
if (self = [super initWithImageComponent:component]) {
transform_ = transform;
}
return self;
}
- (void)apply {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextConcatCTM(context, transform_);
}
@end
可以看到,形变装饰器只是实现了apply方法,并没有对forwardingTargetForSelector方法做任何处理。
 
调用流程如下:
1.向ImageTransformFilter发送 drawInRect消息
2.ImageTransformFilter因为没有drawInRect方法,而调用父类的forwardingTargetForSelector方法
3.在父类的forwardingTargetForSelector方法中 包含 [selfapply];
4.当在父类中调用[selfapply];代码时,会执行ImageTransformFilter的apply方法。(方法的泛型)
5.最后调用returncomponent_;,将消息传给下一个图像滤镜组件。
6.重复1-5的过程。完成了消息的转发过程,形成任务处理链条。

完整的demo实现: https://github.com/zhfei/Objective-C_Design_Patterns

利用OC对象的消息重定向forwardingTargetForSelector方法构建高扩展性的滤镜功能的更多相关文章

  1. HelloServlet类继承HttpServlet利用HttpServletResponse对象

    HelloServlet类继承HttpServlet利用HttpServletResponse对象 HelloServlet类的doGet()方法先得到username请求参数,对其进行中文字符编码转 ...

  2. iOS Foundation框架 -3.利用NSNumber和NSValue将非OC对象类型数据存放到集合

    1.Foundation框架中提供了很多的集合类如:NSArray,NSMutableArray,NSSet,NSMutableSet,NSDictionary,NSMutableDictionary ...

  3. 利用forwardInvocation实现消息重定向

    在obj-c中我们可以向一个实例发送消息,相当于c/c++ java中的方法调用,只不过在这儿是说发送消息,实例收到消息后会进行一些处理.比如我们想调用一个方法,便向这个实例发送一个消息,实例收到消息 ...

  4. 使用 jQuery 选择器获取页面元素后,利用 jQuery 对象的 css() 方法设置其样式。

    查看本章节 查看作业目录 需求说明: 使用 jQuery 选择器获取页面元素后,利用 jQuery 对象的 css() 方法设置其样式. 要求如下: 点击页面的"更改样式"按钮后, ...

  5. 使用 jQuery 选择器获取页面元素,然后利用 jQuery 对象的 css() 方法设置其 display 样式属性,从而实现显示和隐藏效果。

    查看本章节 查看作业目录 需求说明: 使用 jQuery 选择器获取页面元素,然后利用 jQuery 对象的 css() 方法设置其 display 样式属性,从而实现显示和隐藏效果. 具体要求如下: ...

  6. 使用 jQuery 基本选择器获取页面元素,然后利用 jQuery 对象的 css() 方法动态设置 <span> 和 <a> 标签的样式

    查看本章节 查看作业目录 需求说明: 使用 jQuery 基本选择器获取页面元素,然后利用 jQuery 对象的 css() 方法动态设置 <span> 和 <a> 标签的样式 ...

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

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

  8. effective OC2.0 52阅读笔记(二 对象、消息、运行期)

    第二章:对象.消息.运行期 6 理解属性这一概念 总结:OC解决硬编码偏移量问题的做法,一种方案是把实例变量当做一种存储偏移量所用的特殊变量,交由类对象保管,偏移量会在运行期查找,叫做稳固的“应用程序 ...

  9. IOS基础之 (四) OC对象

    一 建立一个OC的类 完整的写一个函数:需要函数的声明和定义. 完整的写一个类:需要类的声明和实现. 1.类的声明 声明对象的属性和行为 #import <Foundation/Foundati ...

随机推荐

  1. Solr 使用自定义 Query Parser(短语查询,精准查询)

    原文出处:http://blog.chenlb.com/2010/08/solr-use-custom-query-parser.html 由于 Solr 默认的 Query Parser 生成的 Q ...

  2. Jenkins 默认没有Launch agent via Java Web Start,该如何配置

    打开"系统管理"——"Configure Global Security" TCP port JNLP agents 配置成"随机",点击& ...

  3. asp.net——上传图片生成缩略图

    上传图片生成缩略图,原图和缩略图地址一样的时候缩略图会把原图覆盖掉 /// <summary> /// 生成缩略图 /// </summary> /// <param n ...

  4. 纸壳CMS3.0中的规则引擎,表达式计算

    纸壳CMS3.0中的规则引擎,用于计算通用表达试结果.通常业务逻辑总是复杂多变的,使用这个规则引擎可以灵活的修改计算表达式. IRuleManager IRuleManager,是使用规则引擎的主要接 ...

  5. Pi 实现VLC网络视频监控

    安装摄像头 将Pi的原装摄像头安装在pi上面 ssh连接pi 通过xshell或者putty等工具连接ssh 安装VLC $ sudo apt-get update $ sudo apt-get in ...

  6. 码农的福利来了, 编程在线Androd 客户端上线了

    编程在线下载: 编程在线网站:http://codestudy.sinaapp.com (最新版2.1) 编程在线移动版:http://codestudy.sinaapp.com/mobile/ 编程 ...

  7. django系列3.1--url路由配置, 正则, 分发include, 分组命名匹配

    一.url配置 在django项目中urls.py文件中就是为这个url调用的view(视图)函数之间的映射表,来配置访问的一个url执行什么代码 默认的基本格式: from django.conf. ...

  8. RFID

    RFID 物联网必不可少会接触到RFID,国内比较常见的RFID读卡驱动芯片有两款.一款是NXP的RC522这系列的,非常稳定,当然也相对较贵,另一款是复旦微电子的FM1702系列,国产的便宜没得说, ...

  9. 【OCP认证12c题库】CUUG 071题库考试原题及答案(26)

    26.choose two Examine the structure of the PRODUCTS table. Which two statements are true? A) EXPIRY_ ...

  10. 【文文殿下】快速傅里叶变换(FFT)学习笔记

    多项式 定义 形如\(A(x)=\sum_{i=0}^{n-1} a_i x^i\)的式子称为多项式. 我们把\(n\)称为该多项式的次数界. 显然,一个\(n-1\)次多项式的次数界为\(n\). ...