利用OC物件的訊息重定向forwardingTargetForSelector方法構建高擴充套件性的濾鏡功能
阿新 • • 發佈:2018-12-27
在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---gogogoOC訊息轉發的應用 當訊息轉發走到第二步時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_ENDImage例項元件程式碼如下: 只是聲明瞭遵守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