利用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 ...
随机推荐
- 二.jenkins构建自动化任务
1,新建任务 打开jinkens, 选择 [新建] -->[输入要构建的项目名称]-->[构建一个自由风格的软件项目]-->[OK] 2, 配置 根据自己的需要选择配置,如下: 代 ...
- JAVA—Filter
过滤器 Filter 1. Filter简介. filter 是对客户端访问资源的过滤,符合条件放行,不符合条件不放行, 并且可以对目标资源访问前后进行逻辑处理. 2. Filter 的API 详解. ...
- “全栈2019”22篇Java异常学习资料及总结
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"22篇Ja ...
- FastDFS 安装与使用
FastDFS 安装与使用 1. 什么是 FastDFS FastDFS是一个开源的高性能分布式文件系统(DFS). 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡的设计. Fa ...
- CF77E Martian Food(圆的反演or 笛卡尔定理+韦达定理)
题面 传送门 这题有两种方法(然而两种我都想不到) 方法一 前置芝士 笛卡尔定理 我们定义一个圆的曲率为\(k=\pm {1\over r}\),其中\(r\)是圆的半径 若在平面上有两两相切,且六个 ...
- CrackMe破解系列第一番Acid_burn
本文作者:gncao 楼主目前是做渗透测试的,在看一些大神的文章时,有时会看到一些关于逆向方面的知识,无奈楼主不懂逆向.但是身为搞技术的嘛,不懂就学,学不懂就再学,所以就在前些日子看了一些基础的汇编视 ...
- 查看npm全局安装位置
查看npm全局安装位置:npm config get prefix 设置位置:npm config set prefix 填写位置
- 队列的理解和实现(二) ----- 链队列(java实现)
什么是链队列 链队是指采用链式存储结构实现的队列,通常链队用单链表俩表示.一个链队显然需要两个分别指示队头和队尾的指针,也称为头指针和尾指针,有了这两个指针才能唯一的确定. package 链队列; ...
- 【Quartz】Spring Boot使用properties文件配置Quartz
(1)在resource目录下新建quartz.properties文件 #============================================================== ...
- C#-WebForm-Javascript、Jquery获取浏览器和屏幕各种高度宽度
Javascript: IE中:document.body.clientWidth ==> BODY对象宽度document.body.clientHeight ==> BODY对象高度d ...