【iOS】—— NSProxy类

NSProxy


在学消息转发的时候看到过这个类,本来没打算细看,后来看学长博客循环引用的时候也看到了这个类,就来细看看。

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

猜你喜欢

转载自blog.csdn.net/m0_62386635/article/details/130577865
今日推荐