利用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

posted @ 2018-12-27 23:23 滴水微澜 阅读(...) 评论(...) 编辑 收藏