快速轉(zhuǎn)發(fā)
什么是快速轉(zhuǎn)發(fā)呢冤竹?我們假設(shè)有這么一個(gè)對(duì)象 CacheProxy峻汉,若是有未知的選擇器發(fā)送到 CacheProxy势誊,objc_msgSend 都會(huì)調(diào)用 CacheProxy 的 forwardingTargetForSelector: 方法跟啤,如果這個(gè)方法返回一個(gè)對(duì)象烫饼,那么 objc_msgSend 會(huì)試著將這個(gè)未知的選擇器發(fā)送給返回的那個(gè)對(duì)象熬甫,這就是快速轉(zhuǎn)發(fā)(fast forwarding)胰挑。那么我們可以利用這個(gè)快速轉(zhuǎn)發(fā)的特性來(lái)代理對(duì)象。
我們使用 CacheProxy 這個(gè)對(duì)象來(lái)說(shuō)明如何利用快速轉(zhuǎn)發(fā)的特性來(lái)緩存其他對(duì)象的 setter 和 getter 方法椿肩。
// CacheProxy.h
#import <Foundation/Foundation.h>
// 快速轉(zhuǎn)發(fā) :若是有未知的選擇器發(fā)送到 CacheProxy瞻颂,objc_msgSend 都會(huì)調(diào)用 CacheProxy 的 forwardingTargetForSelector: 方法,如果這個(gè)方法返回一個(gè)對(duì)象郑象,那么 objc_msgSend 會(huì)試著將這個(gè)未知的選擇器發(fā)送給返回的那個(gè)對(duì)象贡这。
@interface CacheProxy : NSProxy
// 初始化方法 ,返回類(lèi)型為 id 類(lèi)型厂榛,可以避免編譯器報(bào)錯(cuò)
- (id)initWithObject:(id)anObject properties:(NSArray *)properties;
@end
值的注意的是 CacheProxy 并不是 NSObject 的子類(lèi)盖矫,而是 NSProxy 的子類(lèi),NSProxy 是一個(gè)輕量級(jí)的根類(lèi)击奶,是為那些轉(zhuǎn)發(fā)大部分方法調(diào)用的類(lèi)而設(shè)計(jì)的辈双。CacheProxy 本身就是一個(gè)代理對(duì)象,適合成為 NSProxy 的子類(lèi)正歼。
// CacheProxy.m
#import "CacheProxy.h"
#import <objc/runtime.h>
@interface CacheProxy ()
@property (nonatomic,strong) id object;//被代理的對(duì)象
@property (nonatomic,strong) NSMutableDictionary *valueForProperty;// 用于保存被代理對(duì)象的屬性值
@end
@implementation CacheProxy
// setFoo: -> foo
// 通過(guò) selector 得到屬性名
static NSString *propertyNameForSelector(SEL selector){
// 省略代碼
}
// foo -> setFoo:
// 通過(guò)屬性名得到 selector
static SEL setterForPropertyName(NSString *property){
// 省略代碼
}
// getter 方法實(shí)現(xiàn)
static id propertyIMP(id self, SEL _cmd){
NSString *propertyName = NSStringFromSelector(_cmd);
id value = [[self valueForProperty] valueForKey:propertyName];
// NSMutableDictionary 不能存儲(chǔ) nil辐马,所以使用 NSNull 來(lái)處理 nil
if (value == [NSNull null]) {
return nil;
}
if (value) {
return value;
}
// 從緩存對(duì)象取不到屬性值的話(huà),那么從原對(duì)象取屬性值
value = [[self object] valueForKey:propertyName];
// 從原對(duì)象取屬性值之后局义,將屬性值緩存到緩存對(duì)象
[[self valueForProperty] setValue:value forKey:propertyName];
return value;
}
// setter 方法實(shí)現(xiàn)
static void setPropertyIMP(id self, SEL _cmd, id aValue){
id value = [aValue copy];
NSString *propertyName = propertyNameForSelector(_cmd);
// 先將屬性值設(shè)置到緩存對(duì)象喜爷,再將屬性值設(shè)置到原對(duì)象
[[self valueForProperty] setValue:(value != nil ? value : [NSNull null]) forKey:propertyName];
[[self object] setValue:value forKey:propertyName];
}
- (id)initWithObject:(id)anObject properties:(NSArray *)properties{
_object = anObject;
_valueForProperty = [NSMutableDictionary new];
// 緩存對(duì)象為 anObject 的所有屬性生成 setter 和 getter 方法
for(NSString *property in properties){
// 添加 getter 方法
class_addMethod([self class], NSSelectorFromString(property), (IMP)propertyIMP, "@@:");
// 添加 setter 方法
class_addMethod([self class], setterForPropertyName(property), (IMP)setPropertyIMP, "v@:@");
}
return self;
}
// 覆寫(xiě)以下方法,CacheProxy 緩存對(duì)象對(duì)外可以被識(shí)別為 object 對(duì)象
- (NSString *)description{
return [NSString stringWithFormat:@"%@ (%@)",[super description],self.object];
}
- (BOOL)isEqual:(id)object{
return [self.object isEqual:object];
}
- (NSUInteger)hash{
return [self.object hash];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.object respondsToSelector:aSelector];
}
- (BOOL)isKindOfClass:(Class)aClass{
return [self.object isKindOfClass:aClass];
}
// 如果有未知的選擇器發(fā)送到 CacheProxy 緩存對(duì)象萄唇,在這里把所有的未知選擇器都發(fā)送給代理對(duì)象檩帐。
// 快速轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
return self.object;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.object methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation setTarget:self.object];
[invocation invoke];
}
@end
方法 static NSString *propertyNameForSelector(SEL selector) 用于通過(guò) selector 得到屬性名。
方法 static SEL setterForPropertyName(NSString *property)用于通過(guò)屬性名得到 selector另萤。
方法 static id propertyIMP(id self, SEL _cmd) 是一個(gè) getter 方法實(shí)現(xiàn)湃密。由于 NSMutableDictionary 不能存儲(chǔ) nil诅挑,所以使用 NSNull 來(lái)處理 nil。從 CacheProxy 這個(gè)緩存對(duì)象對(duì)象取不到屬性值的話(huà)泛源,那么從被代理對(duì)象取屬性值拔妥。 從被代理對(duì)象取屬性值之后,將屬性值緩存到緩存對(duì)象达箍。
方法 static void setPropertyIMP(id self, SEL _cmd, id aValue) 是一個(gè) setter 方法實(shí)現(xiàn)没龙。 先將屬性值設(shè)置到緩存對(duì)象,再將屬性值設(shè)置到被代理對(duì)象缎玫。
forwardingTargetForSelector: 方法硬纤,如果有未知的選擇器發(fā)送到 CacheProxy 緩存對(duì)象,在這里把所有的未知選擇器都發(fā)送給被代理對(duì)象赃磨。
如果被代理的對(duì)象不響應(yīng) CacheProxy 發(fā)送過(guò)來(lái)的未知選擇器筝家,那么 objc_msgSend會(huì)調(diào)用 methodSignatureForSelector: 和 forwardInvocation: 進(jìn)行普通轉(zhuǎn)發(fā)。
初始化方法 initWithObject: properties: 做的工作是代理對(duì)象生成被代理對(duì)象的屬性 setter 和 getter 方法邻辉。
由于 NSProxy 有一些方法的默認(rèn)實(shí)現(xiàn)溪王,有默認(rèn)實(shí)現(xiàn)的方法則不會(huì)被轉(zhuǎn)發(fā)到代理對(duì)象,所以需要覆寫(xiě)以下方法值骇。
- (NSString *)description{
return [NSString stringWithFormat:@"%@ (%@)",[super description],self.object];
}
- (BOOL)isEqual:(id)object{
return [self.object isEqual:object];
}
- (NSUInteger)hash{
return [self.object hash];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.object respondsToSelector:aSelector];
}
- (BOOL)isKindOfClass:(Class)aClass{
return [self.object isKindOfClass:aClass];
}
創(chuàng)建完了 CacheProxy 這個(gè)代理對(duì)象在扰,我們?cè)賱?chuàng)建一個(gè) 被代理對(duì)象 CachePerson
#import <Foundation/Foundation.h>
@interface CachePerson : NSObject
// 2個(gè)屬性
@property (nonatomic,copy) NSString *firstName;
@property (nonatomic,copy) NSString *lastName;
@end
接下來(lái)看看如何使用 CacheProxy 這個(gè)代理對(duì)象
// 說(shuō)明 CacheProxy 如何緩存其他對(duì)象的 setter 和 getter 方法
NSLog(@"------------------------------------------");
id cachePerson = [[CachePerson alloc] init];
id cacheProxy = [[CacheProxy alloc] initWithObject:cachePerson properties:@[@"firstName",@"lastName"]];
// 設(shè)置 CacheProxy 對(duì)象的屬性, cachePerson 這個(gè)被代理對(duì)象的屬性也有值
[cacheProxy setFirstName:@"CCCC"];
[cacheProxy setLastName:@"DDDD"];
NSLog(@"CacheProxy : firstName-->%@, lastName--->%@",[cacheProxy firstName],[cacheProxy lastName]);
NSLog(@"CachePerson : firstName-->%@, lastName--->%@",[cachePerson firstName],[cachePerson lastName]);
// 說(shuō)明快速轉(zhuǎn)發(fā)
NSLog(@"------------------------------------------");
// 只設(shè)置被代理對(duì)象 CachePerson 的屬性雷客,利用快速轉(zhuǎn)發(fā)的特性 CacheProxy 也能拿到 CachePerson 的屬性
id cachePerson2 = [[CachePerson alloc] init];
[cachePerson2 setFirstName:@"EEEE"];
[cachePerson2 setLastName:@"FFFF"];
id cacheProxy2 = [[CacheProxy alloc] initWithObject:cachePerson2 properties:@[@"firstName"]];
NSLog(@"CacheProxy2 : firstName-->%@, lastName--->%@",[cacheProxy2 firstName],[cacheProxy2 lastName]);
NSLog(@"CachePerson2 : firstName-->%@, lastName--->%@",[cachePerson2 firstName],[cachePerson2 lastName]);
運(yùn)行結(jié)果:
------------------------------------------
CacheProxy : firstName-->CCCC, lastName--->DDDD
CachePerson : firstName-->CCCC, lastName--->DDDD
------------------------------------------
CacheProxy2 : firstName-->EEEE, lastName--->FFFF
CachePerson2 : firstName-->EEEE, lastName--->FFFF
從運(yùn)行結(jié)果來(lái)看,第一個(gè)案例桥狡,我們?cè)O(shè)置了 CacheProxy 對(duì)象的屬性 firstName 和 lastName搅裙, cachePerson 這個(gè)被代理對(duì)象的屬性 firstName 和 lastName 也拿到了對(duì)應(yīng)的值。第二個(gè)案例裹芝,我們只設(shè)置被代理對(duì)象 CachePerson 的屬性 firstName 和 lastName 的值部逮,但是利用快速轉(zhuǎn)發(fā)的特性 CacheProxy 也能拿到 CachePerson 的屬性 firstName 和 lastName 的值。
普通轉(zhuǎn)發(fā)
在前面的快速轉(zhuǎn)發(fā)失效之后嫂易,也就是經(jīng)過(guò) resolveInstanceMethod: 和 forwardingTargetForSelector: 方法之后還是無(wú)法找到未知選擇器的響應(yīng)對(duì)象兄朋,那么 Runtime 就會(huì)嘗試最慢的轉(zhuǎn)發(fā)方式 forwardInvocation: 方法。
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation setTarget:self.object];
[invocation invoke];
}
如上面代碼所示怜械,在 forwardInvocation: 方法會(huì)收到一個(gè) NSInvocation 參數(shù)颅和,這個(gè) NSInvocation 類(lèi)把目標(biāo),選擇器缕允,方法簽名和方法參數(shù)封裝在一起峡扩。我們可以在方法內(nèi)部改變 NSInvocation 的目標(biāo)然后再調(diào)用。
若是類(lèi)有實(shí)現(xiàn) forwardInvocation: 方法障本,那么也需要實(shí)現(xiàn) methodSignatureForSelector: 方法教届, NSInvocation 類(lèi)的方法簽名就是來(lái)自于這個(gè)方法响鹃。
在 forwardInvocation:方法中,我們可以看到該方法沒(méi)有任何返回值案训,但是 Runtime 會(huì)把 NSInvocation 的結(jié)果返回給最初的調(diào)用者买置。
轉(zhuǎn)發(fā)失敗
在整個(gè)消息轉(zhuǎn)發(fā)過(guò)程都沒(méi)有為未知選擇器找到響應(yīng)對(duì)象,那么接下來(lái)要怎么辦强霎?
由于 forwardInvocation:是消息轉(zhuǎn)發(fā)過(guò)程的最后一環(huán)忿项,若是它不處理這個(gè)未知選擇器的話(huà),那么就什么也不會(huì)發(fā)生脆栋,也可以利用這個(gè)特點(diǎn)來(lái)丟棄某些方法倦卖。但是 forwardInvocation: 的默認(rèn)實(shí)現(xiàn)會(huì)有一些動(dòng)作,它會(huì)調(diào)用 doesNotReconizeSelector:方法椿争,該方法會(huì)拋出 NSInvalidArgumentException怕膛。
我們平常也可以使用 doesNotReconizeSelector:方法做一些自己的事情,比如某個(gè)類(lèi)的 init 方法不允許調(diào)用秦踪,那么有如下實(shí)現(xiàn)方案褐捻。
- (id)init{
[self doesNotReconizeSelector:_cmd];
}
要是有人調(diào)用 init 方法, Runtime 會(huì)報(bào)錯(cuò)椅邓。
其實(shí)要實(shí)現(xiàn)這個(gè)效果還可以這么做柠逞,
- (id)init{
NSAssert(NO,"不要直接調(diào)用 init 方法");
return nil;
}
最后值得注意的是,當(dāng)有一個(gè)類(lèi)無(wú)法響應(yīng)未知的選擇器的時(shí)候應(yīng)該在 forwardInvocation:中調(diào)用 doesNotReconizeSelector: 景馁。除非你非常清楚你想要做什么板壮,不然不要直接返回,直接返回可能會(huì)導(dǎo)致一些非常蛋疼的 bug 合住。
參考
本文是《iOS 編程實(shí)戰(zhàn)》的讀書(shū)筆記绰精,對(duì)閱讀的內(nèi)容進(jìn)行總結(jié)。當(dāng)我們看懂了之后透葛,不一定懂笨使;我們跟著書(shū)上代碼敲了一遍之后,還是不一定懂僚害;只有我們能夠把自己理解的內(nèi)容寫(xiě)下來(lái)或者通過(guò)其它方式表達(dá)出來(lái)的時(shí)候硫椰,這個(gè)才是真的懂了;