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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM