利用OC对象的消息重定向forwardingTargetForSelector方法构建高扩展性的滤镜功能
在OC中,当像一个对象发送消息,而对象找到消息后,从它的类方法列表,父类方法列表,一直找到根类方法列表都没有找到与这个选择子对应的函数指针。那么这个对象就会触发消息转发机制。
OC对象的继承链和isa指针链如图:
消息转发流程如下:
1.先调用实例方法resolveInstanceMethod
如果作者在这里使用runtime动态添加对应的方法,并且返回yes。就万事大吉。对象找到了处理的方法,
并且将这个新增的方法添加到类的方法缓存列表
2.如果上面的方法返回NO的话,对象会调用forwardingTargetForSelector方法
允许作者选择其他的对象,处理这个消息。
这个方法,也是待会我们要做文章的地方。画重点。
3.如果上面两个方法都没有做处理,那么对象会执行最后一个方法methodSignatureForSelector,提供一个有效的方法签名,若提供了有效的方法签名,程序将会通过forwardInvocation方法执行签名。若没有提供方法签名就会触发doesNotRecognizeSelector方法,触发崩溃。
整个调用流程图如下:
整个代码调用顺序如下:
//1 + (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; } //2 - (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 { }
打印结果如下:
2018-12-27 00:14:00.469445+0800 iOS_KnowledgeStructure[7940:110114] 1---gogogo 2018-12-27 00:14:00.469613+0800 iOS_KnowledgeStructure[7940:110114] 1---resolveInstanceMethod: 2018-12-27 00:14:00.469765+0800 iOS_KnowledgeStructure[7940:110114] 2---gogogo 2018-12-27 00:14:00.469873+0800 iOS_KnowledgeStructure[7940:110114] 2---forwardingTargetForSelector: 2018-12-27 00:14:00.469978+0800 iOS_KnowledgeStructure[7940:110114] 3---gogogo 2018-12-27 00:14:00.470097+0800 iOS_KnowledgeStructure[7940:110114] 3---methodSignatureForSelector: 2018-12-27 00:14:00.470247+0800 iOS_KnowledgeStructure[7940:110114] 1---_forwardStackInvocation: 2018-12-27 00:14:00.470355+0800 iOS_KnowledgeStructure[7940:110114] 1---resolveInstanceMethod: 2018-12-27 00:14:00.470765+0800 iOS_KnowledgeStructure[7940:110114] 4---forwardInvocation: 2018-12-27 00:14:00.471367+0800 iOS_KnowledgeStructure[7940:110114] 4-最后一步--<NSInvocation: 0x600002442000> 2018-12-27 00:14:00.471969+0800 iOS_KnowledgeStructure[7940:110114] 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
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步