之前有在YYWeakProxy看到过 NSProxy 使用,可以解决NSTimer 对 target 是强引用的问题。主要使用 NSProxy 对 target 进行消息转发,并不会对 target 造成引用关系。那么 NSProxy 能对多个对象方法进行转发,就是 NSProxy 具有了多个对象的方法,可以实现“多继承”。
NSProxy是什么?
根据官方文档的解释,NSProxy是一个抽象超类,主要用于将一个信息,转发到一个真实对象中。
消息转发
在NSProxy中有这么一种转发方法:方法签名转发
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
可以通过消息转发的方式,让抽象类将消息转发的真实类中。不过很快发现了一个问题,这些方法并不属于 Proxy ,不能直接调用。他们之间需要有一个桥梁来沟通,可以使用协议来约定,这样就可以直接进行调用操作。下面看下具体的实现:
实现“多继承”
Person.h
#import <Foundation/Foundation.h>
@protocol PersonPtotocol <NSObject>
@property (nonatomic, copy) NSString *name;
- (NSString *)getPersonInfo;
@end
@interface Person : NSObject<PersonPtotocol>
@end
Person.m
#import "Person.h"
@implementation Person
@synthesize name = _name;
- (NSString *)getPersonInfo
{
return [NSString stringWithFormat:@"person name: %@", _name];
}
@end
由与当前类中并没有 NSString *name 和 getPersonInfo方法,所以需要在实现文件中实现,这样Person才真正具有这两个方法。
BDExtends.m
Proxy中并不包含该方法,会直接进行方法签名,但是缺少签名的对象,所以就需要提前获取target,当然可以直接通过方法名称来确定target,不过比较麻烦,我这里就使用字典来表示他们之间的关系。
- (instancetype)initExtends:(NSArray<NSString *> *)extends
{
NSMutableDictionary *tempMethodList = @{}.mutableCopy;
[extends enumerateObjectsUsingBlock:^(NSString *_Nonnull objString, NSUInteger idx, BOOL * _Nonnull stop) {
Class objClass = NSClassFromString(objString);
NSString *des = [NSString stringWithFormat:@"can not find the class of %@", objString];
NSAssert(objString != nil, des);
id obj = [[objClass alloc] init];
unsigned int numberOfMethods = 0;
Method *methodList = class_copyMethodList(objClass, &numberOfMethods);
for (int i = 0 ; i < numberOfMethods; i++) {
Method tempMethod = methodList[i];
SEL tempSel = method_getName(tempMethod);
const char *tempMethodName = sel_getName(tempSel);
[tempMethodList setObject:obj forKey:[NSString stringWithUTF8String:tempMethodName]];
}
}];
self.methodList = [[NSDictionary alloc] initWithDictionary:tempMethodList copyItems:NO];
return self;
}
获取target 和 act ,使用 class_copyMethodList 获取所有的方法列表,然后关联到自己创建的target上去,后面也可以使用方法名称直接获取对应的target,也是很不错的选择。
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
NSString *methodName = NSStringFromSelector(sel);
id target = [self.methodList valueForKey:methodName];
if (target && [target respondsToSelector:sel])
{
return [target methodSignatureForSelector:sel];
}
}
通过使用 methodSignatureForSelector ,获取了NSMethodSignature,方法也就进行了重新签名,下面就是转发实现:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL sel = invocation.selector;
NSString *methodName = NSStringFromSelector(sel);
id target = [self.methodList valueForKey:methodName];
if (target && [target respondsToSelector:sel]) {
[invocation invokeWithTarget:target];
}
}
主要是通过 NSInvocation 给真实对象转发消息,获取target来进行方法转发。
最后看下demo吧
网友评论