利用OC对象的消息重定向forwardingTargetForSelector方法构建高扩展性的滤镜功能
在OC中,当像一个对象发送消息,而对象找到消息后,从它的类方法列表,父类方法列表,一直找到根类方法列表都没有找到与这个选择子对应的函数指针。那么这个对象就会触发消息转发机制。
OC对象的继承链和isa指针链如图:

整个调用流程图如下:

整个代码调用顺序如下:
//
+ (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
下面就利用消息转发机制,构建装饰器,来实现图像滤镜功能。
科普一下装饰器模式。

图像滤镜的UML类图为:

主要代码实现如下:
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
#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
完整的demo实现: https://github.com/zhfei/Objective-C_Design_Patterns
利用OC对象的消息重定向forwardingTargetForSelector方法构建高扩展性的滤镜功能的更多相关文章
- HelloServlet类继承HttpServlet利用HttpServletResponse对象
HelloServlet类继承HttpServlet利用HttpServletResponse对象 HelloServlet类的doGet()方法先得到username请求参数,对其进行中文字符编码转 ...
- iOS Foundation框架 -3.利用NSNumber和NSValue将非OC对象类型数据存放到集合
1.Foundation框架中提供了很多的集合类如:NSArray,NSMutableArray,NSSet,NSMutableSet,NSDictionary,NSMutableDictionary ...
- 利用forwardInvocation实现消息重定向
在obj-c中我们可以向一个实例发送消息,相当于c/c++ java中的方法调用,只不过在这儿是说发送消息,实例收到消息后会进行一些处理.比如我们想调用一个方法,便向这个实例发送一个消息,实例收到消息 ...
- 使用 jQuery 选择器获取页面元素后,利用 jQuery 对象的 css() 方法设置其样式。
查看本章节 查看作业目录 需求说明: 使用 jQuery 选择器获取页面元素后,利用 jQuery 对象的 css() 方法设置其样式. 要求如下: 点击页面的"更改样式"按钮后, ...
- 使用 jQuery 选择器获取页面元素,然后利用 jQuery 对象的 css() 方法设置其 display 样式属性,从而实现显示和隐藏效果。
查看本章节 查看作业目录 需求说明: 使用 jQuery 选择器获取页面元素,然后利用 jQuery 对象的 css() 方法设置其 display 样式属性,从而实现显示和隐藏效果. 具体要求如下: ...
- 使用 jQuery 基本选择器获取页面元素,然后利用 jQuery 对象的 css() 方法动态设置 <span> 和 <a> 标签的样式
查看本章节 查看作业目录 需求说明: 使用 jQuery 基本选择器获取页面元素,然后利用 jQuery 对象的 css() 方法动态设置 <span> 和 <a> 标签的样式 ...
- iOS开发·runtime原理与实践: 消息转发篇(Message Forwarding) (消息机制,方法未实现+API不兼容奔溃,模拟多继承)...
本文Demo传送门: MessageForwardingDemo 摘要:编程,只了解原理不行,必须实战才能知道应用场景.本系列尝试阐述runtime相关理论的同时介绍一些实战场景,而本文则是本系列的消 ...
- effective OC2.0 52阅读笔记(二 对象、消息、运行期)
第二章:对象.消息.运行期 6 理解属性这一概念 总结:OC解决硬编码偏移量问题的做法,一种方案是把实例变量当做一种存储偏移量所用的特殊变量,交由类对象保管,偏移量会在运行期查找,叫做稳固的“应用程序 ...
- IOS基础之 (四) OC对象
一 建立一个OC的类 完整的写一个函数:需要函数的声明和定义. 完整的写一个类:需要类的声明和实现. 1.类的声明 声明对象的属性和行为 #import <Foundation/Foundati ...
随机推荐
- tar.gz 解压
tar -xzvf .tar.gz tar [-cxtzjvfpPN] 文件与目录 .... 参数: -c :建立一个压缩文件的参数指令(create 的意思): -x :解开一个压缩文件的参数指令! ...
- docker容器怎么设置开机启动
https://my.oschina.net/lwenhao/blog/1923003 docker服务器.以及容器设置自动启动 一.docker服务设置自动启动 说明:适用于yum安装的各种服务 查 ...
- .Net core 2.0的数据初始化
在StartUp.cs里面,添加Seed方法 public static void Seed(IApplicationBuilder applicationBuilder) { using (var ...
- JSON is undefined. Infopath Form People Picker in SharePoint 2013
After some analysis, we found that, this is a known defect with the Microsoft and it is being fixed ...
- Amazon新一代云端关系数据库Aurora(下)
本文由 网易云发布. 作者:郭忆 本篇文章仅限内部分享,如需转载,请联系网易获取授权. 故障恢复 MySQL基于Check point的机制,周期性的建立redo log与数据页的一致点.一旦数据库 ...
- 《Spark MLlib 机器学习实战》1——读后总结
1 概念 2 安装 3 RDD RDD包含两种基本的类型:Transformation和Action.RDD的执行是延迟执行,只有Action算子才会触发任务的执行. 宽依赖和窄依赖用于切分任务,如果 ...
- Linux 解压 压缩文件
来源于:http://blog.csdn.net/mmllkkjj/article/details/6768294/ 解压 tar –xvf file.tar //解压 tar包tar -xzvf f ...
- mxonline实战12, 课程评论,相关课程推荐,课程视频页
对应github地址:第12天 一. 课程评论 1. 创建URL, VIEW courses/views.py -> Course
- nginx之重写
rewrite可以写在server段.location段和if段.语法: rewrite regexp replacement [flag] flag是标记.有4种标记,它们的作用如下表. flag ...
- STM32-增量式旋转编码器测量
Development kit:MDK5.14 IDE:UV4 MCU:STM32F103C8T6 一.增量式旋转编码器 1.简介 编码器(encoder)是将信号(如比特流)或数据进行编制.转换为可 ...