背景
有三個(gè)類ClassA、ClassB国瓮、ClassC灭必,A和B分別有兩個(gè)方法run和walk,假設(shè)我們需要讓C同時(shí)擁有run和walk方法狞谱,我們要如何做才能達(dá)到我們想要的結(jié)果呢?
我們都知道OC是不支持多繼承的禁漓,這是因?yàn)橄C(jī)制名稱查找發(fā)生在運(yùn)行時(shí)而非編譯時(shí)跟衅,很難解決多個(gè)基類可能導(dǎo)致的二義性問(wèn)題,但我們可以通過(guò)下面幾種方法來(lái)達(dá)到我們想要的效果播歼。
- delegate(代理)
- 組合
- 消息轉(zhuǎn)發(fā)(快速轉(zhuǎn)發(fā)和標(biāo)準(zhǔn)轉(zhuǎn)發(fā))
- 類別(category)
- NSProxy
一伶跷、delegate(代理)
將C類需要繼承的方法以及屬性在ClassA和ClassB中各自聲明一份協(xié)議,C類遵守這兩份協(xié)議秘狞,同時(shí)在C類中實(shí)現(xiàn)協(xié)議中的方法以及屬性
#ClassA的聲明協(xié)議
@protocol ClassADelegate
- (void)run;
@property (nonatomic,copy) NSString *runDistance;
@end
#ClassB的聲明協(xié)議
@protocol ClassBDelegate
- (void)walk;
@property (nonatomic,copy) NSString *walkDistance;
@end
#ClassC遵守ClassADelegate撩穿、ClassBDelegate兩份協(xié)議
#同時(shí)在ClassC中實(shí)現(xiàn)ClassADelegate、ClassBDelegate兩份協(xié)議中的方法以及屬性
#import "ClassC.h"
#import "ClassA.h"
#import "ClassB.h"
@interface ClassC()<ClassADelegate,ClassBDelegate>
@end
@implementation ClassC
- (void)sport{
[self run];
[self walk];
}
- (void)run{
self.runDistance = @"10000";
NSLog(@"今天跑了%@步",self.runDistance);
}
- (void)walk{
self.walkDistance = @"5000";
NSLog(@"今天走了%@步",self.walkDistance);
}
@synthesize walkDistance;
@synthesize runDistance;
@end
二谒撼、組合
在ClassC的.h或者.m文件中,聲明ClassA和ClassB的實(shí)例,這樣我們就可以通過(guò)調(diào)用A類和B類實(shí)例來(lái)執(zhí)行run和walk方法
#ClassC.h文件 聲明ClassA和ClassB的實(shí)例
@class ClassA,ClassB;
@interface ClassC : NSObject
@property (nonatomic,strong) ClassA *a;
@property (nonatomic,strong) ClassB *b;
- (void)sport;
@end
#ClassC.m文件 調(diào)用A類和B類實(shí)例來(lái)執(zhí)行run和walk方法
#import "ClassC.h"
#import "ClassA.h"
#import "ClassB.h"
@implementation ClassC
- (void)sport{
[self.a run];
[self.b walk];
}
@end
三雾狈、消息轉(zhuǎn)發(fā)(快速轉(zhuǎn)發(fā)和標(biāo)準(zhǔn)轉(zhuǎn)發(fā))
當(dāng)向某個(gè)對(duì)象發(fā)送消息廓潜,但runtime system在當(dāng)前類以及父類中都找不到對(duì)應(yīng)方法的實(shí)現(xiàn)時(shí),runtime system并不會(huì)立即報(bào)錯(cuò)使程序崩潰善榛,而是依次執(zhí)行下列步驟:
- 1.動(dòng)態(tài)方法解析:向當(dāng)前類發(fā)送resolveInstanceMethod: 信號(hào)辩蛋,檢查是否動(dòng)態(tài)向該類添加了方法
-
2.快速消息轉(zhuǎn)發(fā):檢查該類是否實(shí)現(xiàn)了 forwardingTargetForSelector: 方法,若實(shí)現(xiàn)了則調(diào)用這個(gè)方法移盆,若該方法返回nil或者非self悼院,則向該返回對(duì)象重新發(fā)送消息。
快速消息轉(zhuǎn)發(fā)的實(shí)現(xiàn)方法很簡(jiǎn)單咒循,只需要重寫(xiě)- (id)forwardingTargetForSelector:(SEL)aSelector方法就可以了据途,我們來(lái)用例子證明,還是拿上面的ClassA和ClassB叙甸,假設(shè)B有walk方法颖医,而A沒(méi)有。
#import "ClassA.h"
#import "ClassB.h"
#import "ClassA+MyCategory.h"
@implementation ClassA
- (void)run{
NSLog(@"跑步");
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
// 當(dāng)然這里可以通過(guò)判斷方法名來(lái)決定轉(zhuǎn)發(fā)的對(duì)象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
ClassB *b = [[ClassB alloc] init];
if([b respondsToSelector:aSelector]){
return b;
}
return nil;
}
@end
#調(diào)用
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [[ClassA alloc] init];
[(ClassB *)a walk];
//[a run];
}
-
3.標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā):runtime發(fā)送methodSignatureForSelector:消息獲取Selector對(duì)應(yīng)的方法簽名裆蒸。返回值非空則通過(guò)forwardInvocation:轉(zhuǎn)發(fā)消息熔萧,返回值為空則向當(dāng)前對(duì)象發(fā)送doesNotRecognizeSelector:消息,程序崩潰退出僚祷。
標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)需要重寫(xiě) methodSignatureForSelector: 和 forwardInvocation: 兩個(gè)方法
#import "ClassA.h"
#import "ClassB.h"
#import "ClassA+MyCategory.h"
@interface ClassA(){
ClassB *_b;
}
@end
@implementation ClassA
- (id)init
{
self = [super init];
if (self) {
_b = [[ClassB alloc] init];
}
return self;
}
- (void)run{
NSLog(@"跑步");
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
// 當(dāng)然這里可以通過(guò)判斷方法名來(lái)決定轉(zhuǎn)發(fā)的對(duì)象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
//ClassB *b = [[ClassB alloc] init];
if(signature == nil){
signature = [_b methodSignatureForSelector:aSelector];
}
NSUInteger argCount = [signature numberOfArguments];
for (NSInteger i=0; i<argCount; i++) {
NSLog(@"%s" , [signature getArgumentTypeAtIndex:i]);
}
NSLog(@"returnType:%s ,returnLen:%ld" , [signature methodReturnType] , [signature methodReturnLength]);
NSLog(@"signature:%@" , signature);
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// 當(dāng)然這里可以通過(guò)判斷方法名來(lái)決定轉(zhuǎn)發(fā)的對(duì)象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
SEL selector = [anInvocation selector];
//ClassB *b = [[ClassB alloc] init];
if([_b respondsToSelector:selector]){
//這里如果沒(méi)有響應(yīng)佛致,系統(tǒng)則會(huì)報(bào)錯(cuò)崩潰
[anInvocation invokeWithTarget:_b];
}
}
@end
運(yùn)行結(jié)果
2020-01-04 10:32:34.288075+0800 MethodSwizzlingDemo[2006:407016] @
2020-01-04 10:32:34.288303+0800 MethodSwizzlingDemo[2006:407016] :
2020-01-04 10:32:34.288399+0800 MethodSwizzlingDemo[2006:407016] returnType:v ,returnLen:0
2020-01-04 10:32:34.288592+0800 MethodSwizzlingDemo[2006:407016] signature:<NSMethodSignature: 0x60000047a540>
2020-01-04 10:32:34.288887+0800 MethodSwizzlingDemo[2006:407016] 散步
四、類別(category)
類別也可以用來(lái)模擬多繼承辙谜,比如
- 當(dāng)前類添加方法俺榆。
- 利用runTime來(lái)添加屬性。
#import "ClassA.h"
@interface ClassA (MyCategory)
- (void)walk;//添加方法
@property (nonatomic,copy)NSString *myselfStr;//利用runTime來(lái)添加屬性
@end
@implementation ClassA (MyCategory)
static NSString *const myStr;
- (void)walk {
NSLog(@"走路" );
}
- (void)setMyselfStr:(NSString *)myselfStr {
objc_setAssociatedObject(self, &myStr, myselfStr, OBJC_ASSOCIATION_COPY);
}
- (NSString *)myselfStr {
return objc_getAssociatedObject(self, &myStr);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [[ClassA alloc] init];
[a walk];
}
五筷弦、NSProxy
我們都知道NSObject是Objective-C中大部分類的基類肋演。但不是很多人知道除了NSObject之外的另一個(gè)基類——NSProxy,蘋(píng)果官方文檔是這樣描述的
NSProxy is an abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet. Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.
NSProxy 是一個(gè)抽象基類抑诸,它是為一些作為對(duì)象的替身或者并不存在的對(duì)象定義的API。一般的爹殊,發(fā)送給代理的消息被轉(zhuǎn)發(fā)給一個(gè)真實(shí)的對(duì)象或者代理本身引起加載(或者將本身轉(zhuǎn)換成)一個(gè)真實(shí)的對(duì)象蜕乡。NSProxy的基類可以被用來(lái)透明的轉(zhuǎn)發(fā)消息或者耗費(fèi)巨大的對(duì)象的lazy 初始化。
就是說(shuō)梗夸,NSProxy是一個(gè)虛類层玲,你可以通過(guò)繼承它,并重寫(xiě)這兩個(gè)方法以實(shí)現(xiàn)消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)例
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
現(xiàn)在NSProxy的真面目終于浮出水面:
- NSProxy 是NSProxy 是一個(gè)抽象類,跟 NSObject 一樣的基類反症,都遵守<NSObject>協(xié)議
- NSProxy是一個(gè)抽象類辛块,必須繼承實(shí)例化其子類才能使用。
- NSProxy從類名來(lái)看是代理類專門(mén)負(fù)責(zé)代理對(duì)象轉(zhuǎn)發(fā)消息的铅碍。相比NSObject類來(lái)說(shuō)NSProxy更輕量級(jí)润绵,通過(guò)NSProxy可以幫助Objective-C間接的實(shí)現(xiàn)多重繼承的功能。
還是以ClassA胞谈、ClassB尘盼、ClassC為例:
-------------------------------------------------
// ClassA.h
#import <Foundation/Foundation.h>
#ClassA的聲明協(xié)議
@protocol ClassADelegate
- (void)run;
@end
@interface ClassA : NSObject
@end
-------------------------------------------------
// ClassA.m
#import "ClassA.h"
@interface ClassA () < ClassADelegate >
@end
@implementation ClassA
- (void)run {
NSLog(@"跑步");
}
@end
-------------------------------------------------
-------------------------------------------------
// ClassB.h
#import <Foundation/Foundation.h>
#ClassB的聲明協(xié)議
@protocol ClassBDelegate
- (void)walk;
@end
@interface ClassB : NSObject
@end
-------------------------------------------------
// ClassB.m
#import "ClassB.h"
@interface ClassB () < ClassBDelegate >
@end
@implementation ClassB
- (void)walk {
NSLog(@"走路");
}
@end
-------------------------------------------------
一定要通過(guò)protocol來(lái)聲明接口,而不是直接在類的@interfere中定義烦绳。因?yàn)橥ㄟ^(guò)protocol來(lái)聲明接口卿捎,然后讓proxy類遵循此協(xié)議,可以騙過(guò)編譯器防止編譯器提示proxy類未聲明接口的錯(cuò)誤径密。這個(gè)問(wèn)題下面可以看到午阵。
-------------------------------------------------
// ClassC.h
#ClassC這個(gè)子類必須要遵循之前定義的兩個(gè)協(xié)議ClassADelegate與ClassBDelegate
#目的是騙過(guò)編譯器,讓編譯器認(rèn)為這個(gè)類實(shí)現(xiàn)了上面兩個(gè)協(xié)議
#import <Foundation/Foundation.h>
#import "ClassA.h"
#import "ClassB.h"
@interface ClassC : NSProxy < ClassADelegate, ClassBDelegate >
+ (instancetype )dealerProxy;
@end
-------------------------------------------------
// ClassC.m
#import "ClassC.h"
#import <objc/runtime.h>
@interface ClassC () {
ClassA *_classA;
ClassC *_classB;
NSMutableDictionary *_methodsMap;
}
@end
@implementation ClassC
#pragma mark - class method
+ (instancetype)dealerProxy{
return [[ClassC alloc] init];
}
#pragma mark - init
- (instancetype)init{
_methodsMap = [NSMutableDictionary dictionary];
_ classA = [[ClassA alloc] init];
_classB = [[ClassB alloc] init];
//映射target及其對(duì)應(yīng)方法名
[self _registerMethodsWithTarget: classA];
[self _registerMethodsWithTarget: _classB];
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{
//獲取當(dāng)前選擇子
SEL sel = invocation.selector;
//獲取選擇子方法名
NSString *methodName = NSStringFromSelector(sel);
//在字典中查找對(duì)應(yīng)的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);
//在字典中查找對(duì)應(yīng)的target
id target = _methodsMap[methodName];
//檢查target
if (target && [target respondsToSelector:sel]) {
return [target methodSignatureForSelector:sel];
} else {
return [super methodSignatureForSelector:sel];
}
}
@end
-------------------------------------------------
#調(diào)用
- (void)viewDidLoad {
[super viewDidLoad];
ClassC *classC = [ClassC dealerProxy];
[classC run];
[classC walk];
}
運(yùn)行結(jié)果
2020-05-04 10:32:34.288075+0800 ProxyDemo[2006:407016] 跑步
2020-05-04 10:32:34.288303+0800 ProxyDemo[2006:407016] 走路
感覺(jué)NSProxy實(shí)現(xiàn)多繼承就像享扔,代理+組合+消息轉(zhuǎn)發(fā)的結(jié)合體底桂。