NSProxy
文章目录
- NSProxy
- NSProxy简介
- NSProxy模拟多继承
- NSProxy 避免NSTimer循环引用
在学消息转发的时候看到过这个类,本来没打算细看,后来看学长博客循环引用的时候也看到了这个类,就来细看看。
NSProxy简介
NSProxy 是一个实现了 NSObject 协议类似于 NSObject 的抽象基类,是根类,与 NSObject 类似:
NS_ROOT_CLASS
@interface NSProxy <NSObject> {
Class isa;
}
+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;
- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
// - (id)forwardingTargetForSelector:(SEL)aSelector;
苹果官方文档:
NSProxy 是一个抽象基类,它为一些表现的像是其它对象替身或者并不存在的对象定义API。一般的,发送给代理的消息被转发给一个真实的对象或者代理本身引起加载(或者将本身转换成)一个真实的对象。NSProxy的基类可以被用来透明的转发消息或者耗费巨大的对象的lazy 初始化。
NSProxy最核心的东西:
NSObject
寻找方法顺序:本类 -> 父类 -> 动态方法解析 -> 备用对象 -> 消息转发;NSproxy
寻找方法顺序:本类 -> 消息转发;
NSProxy实现了包括NSObject协议在内基类所需的基础方法,但是作为一个抽象的基类并没有提供初始化的方法。它接收到任何自己没有定义的方法他都会产生一个异常,所以一个实际的子类必须提供一个初始化方法或者创建方法,并且重载forwardInvocation:方法和methodSignatureForSelector:方法来处理自己没有实现的消息。
一个子类的forwardInvocation:实现应该采取所有措施来处理invocation,比如转发网络消息,或者加载一个真实的对象,并把invocation转发给他。methodSignatureForSelector:需要为给定消息提供参数类型信息,子类的实现应该有能力决定他应该转发消息的参数类型,并构造相对应的NSMethodSignature对象。详细信息可以查看NSDistantObject, NSInvocation, and NSMethodSignature的类型说明。
相信看了这些描述我们应该能对NSProxy有个初步印象,它仅仅是个转发消息的场所,至于如何转发,取决于派生类到底如何实现的。比如我们可以在内部hold住(或创建)一个对象,然后把消息转发给该对象。那我们就可以在转发的过程中做些手脚了。甚至也可以不去创建这些对象,去做任何你想做的事情,但是必须要实现他的forwardInvocation:和methodSignatureForSelector:方法。
它仅仅是个转发消息的场所,至于如何转发,取决于派生类的具体实现,比如可以在内部 hold 住(或创建)一个对象,然后把消息转发给该对象,那我们就可以在转发的过程中做些“手脚”了,甚至也可以不去创建这些对象,去做任何你想做的事情,但是必须要实现它的 forwardInvocation: 和 methodSignatureForSelector: 方法。
NSProxy模拟多继承
大致过程就是让它持有要实现多继承的类的对象,然后用多个接口定义不同的行为,并让 Proxy 去实现这些接口,然后在转发的时候把消息转发到实现了该接口的对象去执行,这样就好像实现了多重继承一样。注意:这个真不是多重继承,只是包含,然后把消息路由到指定的对象而已,其实完全可以用 NSObject 类来实现;
在刚刚也提到过了:
- NSObject 寻找方法顺序:本类 -> 父类 -> 动态方法解析 -> 备用对象 -> 消息转发;
- NSproxy 寻找方法顺序:本类 -> 消息转发;
同样做“消息转发”,NSObject 会比 NSProxy 多做好多事,也就意味着耽误很多时间。
首先先写两个类:
#import "ClassA.h"
@implementation ClassA
- (void)infoA {
NSLog(@"ClassA");
}
@end
#import "ClassB.h"
@implementation ClassB
- (void)infoB {
NSLog(@"ClassB");
}
@end
然后再去实现NSProxy的子类
@interface ClassProxy : NSProxy
@property(nonatomic, strong, readonly) NSMutableArray *targetArray;
- (void)handleTargets:(NSArray *)targets;
@end
#import "ClassProxy.h"
#import "objc/runtime.h"
@interface ClassProxy()
@property (nonatomic, strong) NSMutableDictionary *methodDic;
@end
@implementation ClassProxy
- (void)handleTargets:(NSArray *)targets {
self.methodDic = [NSMutableDictionary dictionary];
for (int i = 0; i < targets.count; i++) {
[self registMethodWithTarget:targets[i]];
}
}
- (void)registMethodWithTarget:(id)target {
unsigned int countOfMethods = 0;
Method *method_list = class_copyMethodList([target class], &countOfMethods);
for (int i = 0; i < countOfMethods; i++) {
Method method = method_list[i];
// 得到方法的符号
SEL sel = method_getName(method);
// 得到方法的符号字符串
const char *sel_name = sel_getName(sel);
// 得到方法的名字
NSString *method_name = [NSString stringWithUTF8String:sel_name];
self.methodDic[method_name] = target;
}
free(method_list);
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = invocation.selector;
NSString *methodName = NSStringFromSelector(sel);
id target = self.methodDic[methodName];
if (target) {
[invocation invokeWithTarget:target];
} else {
[super forwardInvocation:invocation];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *Method;
NSString *methodName = NSStringFromSelector(sel);
id target = self.methodDic[methodName];
if (target) {
Method = [target methodSignatureForSelector:sel];
} else {
Method = [super methodSignatureForSelector:sel];
}
return Method;
}
@end
在主函数调用它:
int main(int argc, const char * argv[]) {
@autoreleasepool {
//模拟多重继承
ClassA *classA = [[ClassA alloc] init];
ClassB *classB = [[ClassB alloc] init];
ClassProxy *classProxy = [ClassProxy alloc];
[classProxy handleTargets:@[classA, classB]];
[classProxy performSelector:@selector(infoA)];
[classProxy performSelector:@selector(infoB)];
}
return 0;
}
需要注意的是,NSProxy没有初始化方法,handleTargets方法就可以理解为一个我们自己写的init方法。
输出结果:
NSProxy 避免NSTimer循环引用
在使用NSTimer的时候,经常会造成循环引用的问题,因为self持有了timer,而self又作为timer的target,所以就会造成循环引用。
我们第一眼看到这个问题的时候,一定会想到用__weak typeof(self) weakSelf = self;
这个方法来解决,但是这个方法不能解决,为什么?
我们在block说过,如果外面是个强指针,block引用的时候哪股就用强指针保存,如果外面是个弱指针,block引用的时候内部就用弱指针保存,所以对于block我们使用weakSelf有用。
但是对于CADisplayLink和NSTimer来说,无论外面传递的是弱指针还是强指针,都会传入一个内存地址,定时器内部都是对这个内存地址产生强引用,所以传递弱指针没有用。
对于IOS10之后,就有了block来解决NSTimer:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf doSomething];
}];
我们主要想说的是,利用NSProxy来解决循环引用:
@implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1
target:[WeakProxy proxyWithTarget:self]
selector:@selector(timeOut)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timeOut {
NSLog(@"1");
}
@end
还是一样,新建一个NSProxy的子类,将属性target设为weak类型,和刚刚一样,完成消息转发的流程:
#import "WeakProxy.h"
@interface WeakProxy ()
@property (nonatomic, weak) id target;
@end
@implementation WeakProxy
+ (instancetype)proxyWithTarget:(id)target {
return [[self alloc] initWithTarget:target];
}
- (instancetype)initWithTarget:(id)target {
self.target = target;
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = invocation.selector;
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
@end