【iOS】—— NSProxy类

news2024/11/16 23:40:31

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/506783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在线病毒分析工具评测试用

总览 1 区分在线杀毒引擎与在线沙盒 在线杀毒引擎用的大多是国际上出名的杀毒厂商的引擎作为底层&#xff0c;对文件进行扫描&#xff0c;结论通常会反馈“无毒”或者杀毒引擎自己的代码。 在线沙盒用云端的机器跑一次程序&#xff0c;然后收集程序的相关信息。 两者各有与…

情感分析讲解

情感分析简述 情感分析(Sentiment Analysis)又称倾向性分析&#xff0c;或意见挖掘&#xff0c;它是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。利用情感分析能力&#xff0c;可以针对带有主观描述的自然语言文本&#xff0c;自动判断该文本的情感正负倾向…

MongoDB 聚合操作Map-Reduce

这此之前已经对MongoDB中的一些聚合操作进行了详细的介绍&#xff0c;主要介绍了聚合方法和聚合管道&#xff1b;如果您想对聚合方法和聚合管道进行了解&#xff0c;可以参考&#xff1a; MongoDB 数据库操作汇总https://blog.csdn.net/m1729339749/article/details/130086022…

ClickHouse为何能超越Elasticsearch?

背景 Elasticsearch是一个强大的分布式全文检索和数据分析引擎&#xff0c;也是日志分析系统经常使用的一种实现方案&#xff0c;但近年来随着ClickHouse的发展&#xff0c;Elasticsearch在日志分析领域的地位逐渐被取代&#xff0c;许多公司已经将自己的日志分析解决方案从ES…

games101作业1

作业1的大致要求就是让我们实现如下两个函数&#xff0c;一个是返回在三维空间中绕着Z轴旋转的矩阵&#xff0c;另一个是返回投影矩阵。正确完成这两个函数之后&#xff0c;运行代码你就会在窗口中看到一个三角形&#xff0c;并且按a键和d键会发生旋转。 首先来实现get_model_m…

RuleApp1.4.0 文章社区客户端

简介&#xff1a; 可以打包成安卓&#xff0c;苹果&#xff0c;h5&#xff0c;小程序&#xff0c;全新的版本增加了私聊和群聊&#xff0c;动态模块等&#xff0c;还有自动和手动封禁机制。[滑稽][滑稽]主要模块&#xff1a;用户模块&#xff0c;文章模块&#xff0c;动态模块…

国产服务器tomcat开机自启

目录结构 前言方法一方法二方法三参考连接 前言 国产服务器配置tomcat开机自启动&#xff1b;目前测试两种服务器 银河麒麟&#xff08;Linux localhost.localdomain 4.19.90-52.22.v2207.ky10.x86_64 #1 SMP Tue Mar 14 12:19:10 CST 2023 x86_64 x86_64 x86_64 GNU/Linux&am…

多模态速读:ViLT、ALBEF、VLMO、BLIP

ViLT : Vision-and-Language Transformer Without Convolution or Region Supervision ViLT : Vision-and-Language Transformer Without Convolution or Region SupervisionIntroductionApproach参考 ALBEF: Vision and LanguageRepresentation Learning with Momentum Distil…

如何在香港服务器上进行网站迁移?五个主要步骤

​  服务器迁移是将大量关键信息从一台服务器移动到另一台服务器的过程&#xff0c;同时确保新服务器已正确配置以承载这些新信息。对于业务涉及中国大陆、香港及亚太区地区往来的用户&#xff0c;您可能需要将网站迁移到香港服务器上&#xff0c;来更好地发展业务。香港服务…

【c语言】字符串常用函数组件化封装 | 字符串总结

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

【JavaScript】9.事件

事件 1. 注册事件&#xff08;绑定事件&#xff09; 给元素添加事件&#xff0c;称为注册事件或者绑定事件 1.1 注册事件两种方式 传统注册方式&#xff08;onclick&#xff09; 传统方式注册事件特点&#xff1a; 注册事件的唯一性同一个元素同一个事件只能设置一个处理函数…

离了大谱,公司测试岗却新来了个00后卷王,3个月薪资干到20K.....

最近聊到软件测试的行业内卷&#xff0c;越来越多的转行和大学生进入测试行业。想要获得更好的待遇和机会&#xff0c;不断提升自己的技能栈成了测试老人迫在眉睫的问题。 不论是面试哪个级别的测试工程师&#xff0c;面试官都会问一句“会编程吗&#xff1f;有没有自动化测试…

spring-web HandlerAdapter 源码分析

说明 本文基于 jdk 8, spring-framework 5.2.x 编写。author JellyfishMIX - github / blog.jellyfishmix.comLICENSE GPL-2.0 HandlerAdapter 接口 提供作为处理器适配器的能力。 supports 方法判断是否支持该 handler。 public interface HandlerAdapter {/*** 判断是否…

【跟着陈七一起学C语言】今天总结:初识C语言

友情链接&#xff1a;专栏地址 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的错误&#x…

Ansys Zemax | 设计抬头显示器时要使用哪些工具 – 第二部分

本文为使用OpticStudio工具设计优化HUD抬头显示器系统的第二部分&#xff0c;主要包含演示了如何使用OpticStudio工具设计分析抬头显示器&#xff08;HUD&#xff09;性能&#xff0c;即全视场像差&#xff08;FFA&#xff09;和NSC矢高图。&#xff08;联系我们获取文章附件&a…

RabbitMQ之工作队列 ( Work Queues )

Work Queues 1. 轮询分发消息1.1 抽取工具类1.2 启动两个工作线程1.3 启动一个发送线程1.4 结果展示 2. 消息应答2.1 概念2.2 自动应答2.3 消息应答的方法2.4 Multiple 的解释2.5 消息自动重新入队2.6 消息手动应答代码2.7 手动应答效果演示 3. RabbitMQ 持久化3.1 概念3.2 队列…

逍遥自在学C语言 | 条件控制的正确使用姿势

前言 在C语言中&#xff0c;有三种条件判断结构&#xff1a;if语句、if-else语句和switch语句。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学习的小白程序猿 —— 逍遥。 二、if语句 基本语法 if (条…

大厂过来人忠告:学java有没有前途?想转行应该准备什么?

对于想转行学习java来人说&#xff0c;最可怕的问题就是信息闭塞。很多人开始的时候都是因为没能了解清楚情况&#xff0c;找不到学习思路&#xff0c;胡乱下手学习一通其实效果并不好&#xff0c;只是感动了自己&#xff0c;没有太大成效。毕竟时间这么宝贵&#xff0c;你也不…

ThingsBoard教程(四十):规则节点解析 计算增量节点 Calculate delta

本篇文章介绍一个ThingsBoard 规则引擎中的一个节点,Calculate delta Calculate delta 计算增量 该节点可以在规则中获取上一次遥测的值,以此可以实现二次遥测的差。比如一个设备,一天上传一次数据,如果你要对比今天和昨天的数据,并将两者数据差保存到数据库,就能够使用…

三子棋游戏的实现(C语言)

三子棋游戏的实现&#xff0c;在这里我们要求满足&#xff1a; 游戏不退出&#xff0c;继续下一把&#xff08;循环&#xff09;用多文件的形式实现&#xff0c;如下&#xff1a; 用game.h文件存放函数的声明并包含需要的头文件用game.c文件存放各个函数的具体实现用test.c文件…