在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