利用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 ...
随机推荐
- (zxing.net)一维码EAN 13的简介、实现与解码
一维码EAN 13:属于国际标准条码, 由13个数字组成,为EAN的标准编码型式(EAN标准码). 依结构的不同,EAN条码可区分为: EAN 13码: 由13个数字组成,为EAN的标准编码型式(EA ...
- IIS服务器添加网站
1.添加IIS服务:对“我的电脑”右键,管理,点击服务和应用程序,如果下面没有”Internet Information Services(IIS)管理器“,打开控制面板,点击程序,启用或者关闭Win ...
- Let it crash philosophy part II
Designing fault tolerant systems is extremely difficult. You can try to anticipate and reason about ...
- MyBatis入门程序(基于XML配置)
创建一个简单的MyBatis入门程序,实现对学生信息的增删改查功能(基于XML配置) 一.新建一个Java工程,导入MyBatis核心jar包.日志相关的jar包以及连接Oracle数据库所需驱动包, ...
- sql server中的 trimtrailingblanks
使用sp_help 查出 发现有个这个属性, 如何修改呢? SET ANSI_PADDING ONAlter Table Sys_users_History Alter column PveSit ...
- LOJ#6046. 「雅礼集训 2017 Day8」爷(分块)
题面 传送门 题解 转化为\(dfs\)序之后就变成一个区间加,区间查询\(k\)小值的问题了,这显然只能分块了 然而我们分块之后需要在块内排序,然后二分\(k\)小值并在块内二分小于它的元素--一个 ...
- VMware Workstation 14 pro License Keys
AC5XK-0ZD4H-088HP-9NQZV-ZG2R4 CG54H-D8D0H-H8DHY-C6X7X-N2KG6 ZC3WK-AFXEK-488JP-A7MQX-XL8YF ZC5XK-A6E0 ...
- C语言多线程编程一
1. Windows下同时打开多个对话框: #include <Windows.h> #include <process.h> //创建线程 void runmsg(void ...
- GitHub创建项目,保存代码。
平时学习会写一些代码,虽然只是零零散散的功能,但是基本都是在一个项目下操作,偶尔会忘记代码编辑顺序.国庆这几天在家,想把GitHub用起来,实现自己代码的可追溯,可查询.学习本篇博客,你需要一点的Gi ...
- Ionic2使用TypeScript调用自定义JavaScript脚本
在项目app目录下面写一个.d.ts 里面声明你要引用JS库里面定义的变量,变量名要保持一致 declare var Strophe: any; 然后把JS库放在www目录下面 然后在index.ht ...