在OC中少見的不繼承于NSObject 的類------>NSProxy,下面來介紹一下NSProxy
NSObject類是Objective-C中大部分類的基類。但不是很多人知道除了NSObject之外的另一個基類——NSProxy.
我們可以這樣認為,NSProxy是一個虛類,它可以被繼承,并重寫下面兩個方法來實現消息轉發到另一個實例
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel ;
NSProxy是什么呢?其實它是什么玩意呢?大家可以去查閱一下Apple的官方API,其實NSProxy就是:負責將消息轉發到真正的target的代理類。
什么說的是什么意思呢?下面來分析一下:在我們生活中如下自己想買某件商品,但是自己又不愿意親自去買(向代理發消息),這樣我們可以去找個跑路的人告訴他自己要干啥事幫著自己買(轉發消息給真實的target),然后間接去完成某件事
在我們了解完什么是NSProxy之后,然后繼續深入
通過NSProxy在Objective-C中模擬多繼承###
多繼承在編程中可以說是比較有用的特性。舉個例子,原本有兩個相互獨立的類A和類B,它們各自繼承各自的父類,項目進行地好好的,突然有一天產品經理過來告訴你,我要在下個版本加一個xxxxx的特性,非常緊急。一臉懵逼的你發現如果要實現這個特性,你需要對類A以及其父類作很大的修改,代價非常之高。突然你意識到原來類B的父類已經有類似的功能,你只需要讓類A繼承于類B的父類并重寫其某些方法就能實現,這樣做高效且低風險,于是你屁顛屁顛地擼起了代碼。
可是,Objective-C卻不支持這樣一個強大的特性。不過NSProxy可以幫我們在某種程度上(這只是一個模擬的多繼承,并不是完全的多繼承)解決這個問題:
現在假設我們想要去買書,但是我懶癌犯了,不想直接去書店(供應商)買,如果有一個跑腿的人(經銷商)幫我去書店買完,我再跟他買。同時,我買完書又想買件衣服,我又可以很輕松地在他那里買到一件衣服(多繼承)。
首先,我們定義BookProvider類與ClothesProvider類作為基類。###
下面我會為大家講一下這兩個類####
TDBookProvider####
// TDBookProvider.h#import @protocol TDBookProviderProtocol - (void)purchaseBookWithTitle:(NSString *)bookTitle;
@end
@interface TDBookProvider : NSObject
@end
// TDBookProvider.m#import "TDBookProvider.h" @interface TDBookProvider ()
@end
@implementation TDBookProvider
- (void)purchaseBookWithTitle:(NSString *)bookTitle{ NSLog(@"You've bought \"%@\"",bookTitle);
}
@end
TDClothesProvider.h
// TDClothesProvider.h
#import
typedef NS_ENUM (NSInteger, TDClothesSize){
TDClothesSizeSmall = 0,
TDClothesSizeMedium,
TDClothesSizeLarge
};
@protocol TDClothesProviderProtocol
- (void)purchaseClothesWithSize:(TDClothesSize )size;
@end
@interface TDClothesProvider : NSObject
@end
// TDClothesProvider.m
#import "TDClothesProvider.h"
@interface TDClothesProvider ()
@end
@implementation TDClothesProvider
- (void)purchaseClothesWithSize:(TDClothesSize )size{
NSString *sizeStr;
switch (size) {
case TDClothesSizeLarge:
sizeStr = @"large size";
break;
case TDClothesSizeMedium:
sizeStr = @"medium size";
break;
case TDClothesSizeSmall:
sizeStr = @"small size";
break;
default:
break;
}
NSLog(@"You've bought some clothes of %@",sizeStr);
}
@end
這里要注意:一定要通過protocol來聲明接口,而不是直接在類的@interfere中定義。因為通過protocol來聲明接口,然后讓proxy類遵循此協議,可以騙過編譯器防止編譯器提示proxy類未聲明接口的錯誤。這個問題下面可以看到。
**
現在兩個Provider的類寫完,我們可以直接向供應商買東西了,但這跟我們的需求還有很大差異,我們需要一個中間的經銷商
**
// TDDealerProxy.h
#import
#import "TDBookProvider.h"
#import "TDClothesProvider.h"
@interface TDDealerProxy : NSProxy
+ (instancetype )dealerProxy;
@end
// TDDealerProxy.m
#import "TDDealerProxy.h"
#import <objc/runtime.h>
@interface TDDealerProxy () {
TDBookProvider *_bookProvider;
TDClothesProvider *_clothesProvider;
NSMutableDictionary *_methodsMap;
}
@end
@implementation TDDealerProxy
#pragma mark - class method
+ (instancetype)dealerProxy{
return [[TDDealerProxy alloc] init];
}
#pragma mark - init
- (instancetype)init{
_methodsMap = [NSMutableDictionary dictionary];
_bookProvider = [[TDBookProvider alloc] init];
_clothesProvider = [[TDClothesProvider alloc] init];
//映射target及其對應方法名
[self _registerMethodsWithTarget:_bookProvider];
[self _registerMethodsWithTarget:_clothesProvider];
return self;
}
#pragma mark - private method
- (void)_registerMethodsWithTarget:(id )target{
unsigned int numberOfMethods = 0;
//獲取target方法列表
Method *method_list = class_copyMethodList([target class], &numberOfMethods);
for (int i = 0; i < numberOfMethods; i ++) {
//獲取方法名并存入字典
Method temp_method = method_list[i];
SEL temp_sel = method_getName(temp_method);
const char *temp_method_name = sel_getName(temp_sel);
[_methodsMap setObject:target forKey:[NSString stringWithUTF8String:temp_method_name]];
}
free(method_list);
}
#pragma mark - NSProxy override methods
- (void)forwardInvocation:(NSInvocation *)invocation{
//獲取當前選擇子
SEL sel = invocation.selector;
//獲取選擇子方法名
NSString *methodName = NSStringFromSelector(sel);
//在字典中查找對應的target
id target = _methodsMap[methodName];
//檢查target
if (target && [target respondsToSelector:sel]) {
[invocation invokeWithTarget:target];
} else {
[super forwardInvocation:invocation];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
//獲取選擇子方法名
NSString *methodName = NSStringFromSelector(sel);
//在字典中查找對應的target
id target = _methodsMap[methodName];
//檢查target
if (target && [target respondsToSelector:sel]) {
return [target methodSignatureForSelector:sel];
} else {
return [super methodSignatureForSelector:sel];
}
}
@end
這里有兩個要注意的問題:1、TDDealerProxy這個子類必須要遵循之前定義的兩個協議TDBookProviderProtocol與TDClothesProviderProtocol,目的是騙過編譯器,讓編譯器認為這個類實現了上面兩個協議2、NSProxy類是沒有init方法的,也就是說如果我們要獲得一個NSProxy的實例,代碼只需要這樣:
NSProxy *proxyInstance = [NSProxy alloc];
但是我們需要在AppDelagate 中 實現一下方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
TDDealerProxy *dealerProxy = [TDDealerProxy dealerProxy];
[dealerProxy purchaseBookWithTitle:@"Swift 100 Tips"];
[dealerProxy purchaseClothesWithSize:TDClothesSizeMedium];
// Override point for customization after application launch.
return YES;
}