利用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 ...
随机推荐
- jmeter分布式环境
搭建jmeter分布式环境 (1)确定分布式结构,即1台机器部署master.几台机器部署slave? (2)将相同版本的jmeter分别拷贝到这几台机器 (3)修改maste ...
- spark-streming 中调用spark-sql时过程遇到的问题
在spark-streming 中调用spark-sql时过程遇到的问题 使用版本:spark-2.1.0 JDK1.8 1. spark-sql中对limit 的查询结果使用sum() 聚合操作不生 ...
- api接口响应类型定义
public class Response<T> { public ResponseStatus Status { get; set; } public string Message { ...
- 【Unity】Domina-Game总结与反思
[Unity]Domina-Game总结与反思 2018/6/15 我总算是把物理课作业--Domina-Game给赶完了,这也算是我用Unity做的第一个游戏吧(不得不说我的脚本写的超烂的)...纪 ...
- RabbitMq初探——消息分发
消息分发 前言 我们在用到消息队列的场景,一般是处理逻辑复杂,耗时,所以将同步改为异步处理,接入队列,下游处理耗时任务. 队列消息数量很大,且下游worker进程(消费者)处理耗时长,所以就有了任务的 ...
- WPF Adorner 在TabControl切换TabItem时消失
错误的截图: 一开始以为是MVVM绑定的代码中出现了问题,但是通过断点追踪并没有发现问题. 通过通过VS的实时可视化树发现问题:切换Item时Adorner会在AdornerLayer直接消失.届时怀 ...
- C#写入文件
using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\testDir\test2.txt", t ...
- SpringMvc-ModelAndView 结果出不来 显示路径问题 解决办法
今天写SpringMVC的时候 写ModelAndView的时候 死活跳不过页面去-一直报错 显示路径也错误 查看导包问题 应该: import org.springframework.web.se ...
- ZooKeeper学习2---ZooKeeper安装配置
一.Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式. ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境:■ 伪集群模式:就是在一台物 ...
- [Objective-C语言教程]指针(15)
Objective-C中的指针简单易学.使用指针可以更轻松地执行某些Objective-C编程任务,并且在不使用指针的情况下无法执行其他任务(如动态内存分配). 所以有必要学习指向成为一个完美的Obj ...