《Effective Objective-C》阅读笔记(下)

news2025/3/1 0:11:48

目录

内存管理

理解引用计数

引用计数工作原理

自动释放池

保留环

以ARC简化引用计数

使用ARC时必须遵循的方法命名规则

变量的内存管理语义

ARC如何清理实例变量

在dealloc方法中只释放引用并解除监听

编写“异常安全代码”时留意内存管理问题

以弱引用避免保留环

以“自动释放池块”降低内存峰值

用“僵尸对象”调试内存管理问题

不要使用retainCount

块与大中枢派发

理解“块”这一概念

块的基础知识

块的内部结构

全局块、栈块及堆块

为常用的块类型创建typedef

用handler块降低代码分散程度

用块引用其所属对象时不要出现保留环

多用派发队列,少用同步锁

​编辑

多用GCD,少用performSelector系列方法

掌握GCD及操作队列的使用时机

通过Dispatch Group机制,根据系统资源状况来执行任务

使用dispatch_once来执行只需运行一次的线程安全代码

不要使用dispatch_get_current_queue

系统框架

熟悉系统框架

多用枚举块,少用for循环

对自定义其内存管理语义的collection使用无缝桥接

构建缓存时选用NSCache而非NSdictionary

精简initiakize与load的实现代码

别忘了NSTimer会保留其目标对象


内存管理

理解引用计数

OC使用引用计数来管理内存。

引用计数工作原理

NSObject协议声明了下面三个方法来操作计数器,以递增或递减其值:

retain 递增保留计数

release 递减保留计数

autorelease 待清理“自动释放池”,再递减保留计数

对象被创建后保留计数的变化过程如下图所示:

一般调用完release后都会清空指针,这就能保证不会出现可能指向无效对象的指针。

自动释放池

有时可以不调用release,改为调用autorelease,此方法会在稍后递减计数,通常是下一次“事件循环”时递减。

使用自动释放池autorelease可以延长对象生命期,使其在跨越方法调用边界后依然可以存活一段时间。

保留环

引用计数经常要注意的一个问题是“保留环”,“保留环”会导致内存泄漏,因为循环中的对象保留计数不会降为0,通常采用“若引用”来解决这一问题。

以ARC简化引用计数

ARC,即自动引用计数,所做的事情就是自动管理引用计数。由于ARC自动执行retain、release、autorelease等操作,所以不能调用上述方法

ARC在调用这些方法时,并不通过消息派发机制,而是直接调用其底层C语言版本,这样性能更好。

使用ARC时必须遵循的方法命名规则

若方法名以下列词语开头,则其返回的对象归调用者所有

若方法名不以上述四个词语开头,则表示其所返回的对象不归调用者所有。

变量的内存管理语义

在应用程序中,可用下列修饰符来改变局部变量与实例变量的语义:

通过__weak局部变量可以打破由块所引入的“保留环”。

ARC如何清理实例变量

ARC会借助OC特性来生成清理例程,来自动清理OC对象,所以ARC环境下通常无需再编写dealloc方法

不过如果有非OC的对象,那么仍然需要清理,dealloc方法可以这样写:

在dealloc方法中只释放引用并解除监听

对象在经历其生命期后,最终会为系统所回收,这时就要执行dealloc方法了。dealloc方法中要做的主要就是释放对象所拥有的引用。如果对象拥有其他非OC对象,那就要手工释放。

dealloc方法中还要把配置的观测行为清理掉,比如用NSNotificationCenter订阅的通知,dealloc可以这样写:

有一些开销较大或系统内稀缺的资源不在dealloc中释放,比如文件描述符、套接字、大块内存等。通常的做法是:实现另一个方法,当对象使用完后,就调用此方法。

这种对象所属的类,接口可以这样写:

#import <Foudation/Foudation.h>
@interface EOCServerConnection : NSObject
- (void)open:(NSString*)address;
- (void)close;
@end

该类与开发者的约定是:想打开连接,就调用“open:”方法,连接完毕就调用close方法。

close与dealloc方法可以这样写:

#import <Foudation/Foudation.h>
@interface EOCServerConnection : NSObject
- (void)open:(NSString*)address;
- (void)close;
@end

还要注意:dealloc中不要随便调用其他方法,执行异步任务的方法不应在dealloc里调用,只能在正常状态下执行的方法不应在dealloc里调用,属性的存取方法不应在dealloc里调用。

编写“异常安全代码”时留意内存管理问题

在try块中,如果先保留了某个对象,在释放它以前又抛出了异常,那么除非catch块能处理此问题,否则对象所占内存就将泄漏。

在手动管理引用计数时,要使用@finally块,并在块中释放对象,这样无论是否抛出异常,其中的代码都保证会运行且只运行一次。

当自动管理引用计数时,需要打开-fobjc-arc-exceptions标志,该标志默认关闭,只有在打开时,才会自动管理异常的引用计数,不过降低运行效率。

当遇到大量异常捕获操作时,就要考虑重构代码,用NSError式错误信息传递法来取代异常。

以弱引用避免保留环

最简单的保留环就是两个对象互相引用对方。

保留环会导致内存泄漏

如果只剩一个引用还指向保留环中的实例,而现在又把这个引用移除,那么整个保留环就泄漏了。

避免保留环最佳方式是弱引用,这种引用经常用来表示“非拥有关系”,将属性声明为unsafe_unretained或weak即可。

两者都会对属性弱引用,区别只是对象回收后的行为:

以“自动释放池块”降低内存峰值

嵌套自动释放池,可以控制应用的内存峰值,使其不致过高。

比如这段代码:

如果方法创建临时对象,那这些对象即便不再使用,也依然存活,直到系统将其释放并回收,意味着所有临时对象都要等for循环执行完才会释放。这会导致程序所占内存量持续上涨。

当循环长度取决于用户输入时更如此,比如:

这时,增加一个自动释放池,把循环内的代码包裹在“自动释放池块”中,那么循环中自动释放的对象就会放在这个池,内存峰值就会降低

用“僵尸对象”调试内存管理问题

Cocoa提供了“僵尸对象”这一功能,当功能启用后,系统会把所有已经回收的实例转化为“僵尸对象”,而不会真正回收它们。这种对象内存不可能遭到覆写,并且收到消息后会抛出异常,准确说明发送过来的消息,并描述回收之前的那个对象。僵尸对象是调试内存管理问题的最佳方式。

将NSZombieEnabled环境变量设为YES,即可开启此功能。开启功能后,系统会修改对象的isa指针,指向特殊的僵尸类,从而使对象变为僵尸对象,他会相应所有选择子,响应方式为:打印一条包含信息内容及其接收者的消息,然后终止应用程序

不要使用retainCount

ARC中已经废弃了retainCount这一方法,而在不启用ARC时,也不应该使用这一方法。

这个方法之所以无用,原因在于:它所返回的保留计数只是某个给定时间点上的值。

块与大中枢派发

理解“块”这一概念

块可以实现闭包

块的基础知识

块与函数类似,只不过是直接定义在另一个函数里的,和定义它的函数共享同一个范围内的东西,用符号“^”来表示,后面跟一对花括号。

块类型的语法结构如下:

块的强大之处是:在声明它的范围内,所有变量都可以为其所捕获,但是默认情况下,不可以在块里修改。

如果声明时加上__block修饰符,这样就可以在块内修改了。

如果块捕获了对象类型,就会自动保留它,就会导致一个问题,块本身也具有引用计数,这样就很容易导致循环引用。

块的内部结构

栈的内存布局如图:

这里invoke变量是个函数指针,指向块的实现代码,descriptor是指向结构体的指针,结构体中声明了块对象的总体大小,还声明了copy与dispose两个辅助函数对应的函数指针。

全局块、栈块及堆块

定义块时,所占内存区域是分配在栈中的,离开相应范围后,栈内存可能被覆写。

将块对象发送copy消息就可以拷贝,把块从栈复制到堆。此后再调用copy,只会递增块对象引用计数

除了栈块和堆块,还有全局块,这种块不会捕捉任何状态,也不需要,块所使用的内存区域在编译器就已经确定了。这种块可以声明在全局内存中,不需要在栈中创建。

全局块的拷贝是个空操作,它实际上相当于单例。

为常用的块类型创建typedef

块类型的语法结构如下:

为了简化,可以使用typedef为块起一个已读的别名,比如这样:

现在可以直接使用新类型了。

当块作为参数时,也可以简化,比如:

简化后:

使用类型定义还有个好处,就是在打算重构块时,会很方便,只要修改类型定义处即可

用handler块降低代码分散程度

为用户界面编码时,经常异步执行任务,如果使用委托模式,那么处理数据的代码总是在受委托者处实现,如下图:

而如果改用块来写的话,代码就会变得更加清晰。

如此一来,代码就变得好懂许多了。

除此之外,委托模式还有一个缺点:如果类要分别使用多个获取器下载不同数据,那就得在delegate回调方法里根据传入参数来切换。

如果使用块,就可以将实现代码与委托者内联在一起。

在设计API时,如果用到了handler块,那就可以增加一个参数,使调用者可以通过此参数来决定应该把块安排在哪个队列上执行

用块引用其所属对象时不要出现保留环

使用块很容易导致“保留环”

有两种常见情况。第一种如下例:

当使用block捕获self时,就可能造成这种情况,这时应该等闭包执行完后,再打破保留环

另一种情况是互相引用,这时常见的方法是不保留引用,设定一套机制,令获取器对象自己设法保持存活。

多用派发队列,少用同步锁

在GCD出现之前,有两种办法来实现同步机制,第一种是“同步块”(@synchronized),第二种是直接使用NSLock对象。这两种方法都有其缺陷,替代方案就是GCD。

有种简单而高效的办法可以代替同步块或锁对象,那就是使用“串行同步队列”。GCD在底层实现。可以做许多优化。

比如属性的存取:

将属性的访问操作都放在串行队列中,就可以实现同步。

如果把设置方法改为异步派发,就可以进一步优化

然而,获取方法与设置方法不能并发执行,但多个获取方法可以并发执行。将并发队列与栅栏块结合,可以进一步优化。

在队列中,栅栏块必须单独执行,不能与其他块并行。

实现代码也很简单:

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
- (NSString*)someString {
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}
​
- (void)setSomeString:(NSString*)someString{
    dispatch_barrier_async(_syncQueue, ^{
      _someString = someString;
    });
}

多用GCD,少用performSelector系列方法

使用performSelector方法,可以动态绑定消息,如下图:

但是这种方法不仅可能导致内存泄漏,还很难传达一些非对象类型参数。

避免这些限制和缺陷,方法就是使用块,而perform方法提供的那些线程功能,都可以通过在大中枢派发机制中使用块来执行,延后执行可以用dispatch_after来实现,在另一个线城上执行任务可以通过dispatch_sync及dispatch_async来实现

掌握GCD及操作队列的使用时机

有时使用NSOperationQueue要比使用GCD技术要更合适。

用NSOperationQueue类的“addOperationWithBlock:”方法搭配NSBlockOperation类使用操作队列,语法与GCD非常类似,但使用下来有以下好处:

通过Dispatch Group机制,根据系统资源状况来执行任务

Dispatch group是GCD的一项特性,能够把任务分组。

与dispatch group相关的有以下几个函数:

dispatch_group_tdispatch_group_create// 创建分组
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_bloack_t block);//管理分组
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);//管理分组

有两个方法可以用于等待dispatch group执行完毕:

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

二者的区别是:前者会阻塞当前线程,而后者只会在完成任务后进行通知。

未必一定要使用dispatch group,使用异步派发加单个队列也可以实现该效果。然而,使用dispatch group,GCD会根据系统资源状况来调度这些任务。若不采用group,则不仅性能有所下降,还需要额外代码来防止死锁

使用dispatch_once来执行只需运行一次的线程安全代码

在GCD中,有一种很好的方式实现单例,采用以下函数:

void dispatch_once(dispatch_once_t *token, dispatch_block_t block);

此函数接受一个参数,只要这个参数相同,那么这个块里的代码便只执行一次。

可以这样实现单例:

这个参数应该声明在static或global作用域中。

不要使用dispatch_get_current_queue

dispatch_get_current_queue这个函数已经被弃用了。使用这个函数时,容易造成死锁。并且这个函数的行为也总是与预计不符。因为队列存在一套层级体系。

所以使用该函数检查当前队列是否为执行同步派发所用的队列,并不总是奏效。这个函数所返回的不一定是API指定的那个,有可能是API内部的那个同步队列。

为了解决这个问题,最好的办法是通过GCD提供的功能来设定“队列特有数据”,通过队列特有数据来解决不可重入导致的死锁。

系统框架

熟悉系统框架

许多系统框架可以直接使用,最重要的是Foundation与CoreFoundation。

许多常见任务都能用框架来做,例如音频与视频处理、网络通信、数据管理等。

多用枚举块,少用for循环

遍历collection有四种方式,最基本的是for循环,其次是NSEnumerator类和for...in...快速便利法,最新、最先进的是“块枚举法”。

以数组为例,NSArray中定义了这个方法

- (void)enumerateObjectsUsingBlock:(void(^)id object, NSUInteger idx, BOOL *stop)block

通过这种方法遍历collection,可以直接从块里获取更多信息。遍历数组时可以知道当前下标,遍历字典可以同时获取键值,还可以修改块的方法签名以免进行类型转换操作

对自定义其内存管理语义的collection使用无缝桥接

使用无缝桥接技术,可以在Foudation框架中的OC对象与CoreFoudation框架中的C语言数据结构之间来回转换。

使用无缝桥接,可以将collection转换为具备特殊内存管理语义的collection。

比如字典的键为“拷贝”,而值为“保留”,这时就只能使用无缝桥接来改变语义。

CF框架中的可变字典叫CFMutableDictionary,可通过下列方法来制定键和值的内存管理语义:

构建缓存时选用NSCache而非NSdictionary

构建缓存时,应选用NSCache而非NSdictionary。

NSCache的优势在于:当系统资源快要耗尽时,它可以自动删减缓存

NSCache对象可以设置上限来限制缓存中的对象总个数和成本。

NSCache经常与NSData和NSPurgeableData搭配使用。将NSPurgeableData与NSCache搭配使用,可以实现自动清除数据的功能。

精简initiakize与load的实现代码

类的初始化有两个方法,分别是load和initialize

load方法的问题在于,执行时系统处于“脆弱状态”,在执行时必然执行超类的load方法,如果依赖程序库,那库里所有相关类的load方法必定会先执行,但是根据某个库,无法判定各个类的载入顺序。因此,在load方法里使用其他类是不安全的。

load方法务必实现精简一些,因为整个程序在执行load时都会阻塞。

initialize方法会在程序首次调用该类之前调用且只调用一次。它与load不同之处在于:

1.initiakize为惰性调用

2.执行initialize时,系统处于正常状态,也能确保一定是“线程安全的环境”中执行。

3.如果某个类为实现initialize,就会运行超类的实现代码。

initialize方法在实现时也尽量精简。

别忘了NSTimer会保留其目标对象

使用重复执行模式的计时器,很容易引入“保留环”

如果创建本类的实例,并调用startPolling方法,创建计时器时,由于目标对象是self,所以要保留此实例。然而,实例也保留了计时器。因此就产生了保留环。

这时可以拓展NSTimer的功能,用“块”来打破保留环。不过需要创建分类,将实现代码加入分类中。

- (void)startPolling{
    __weak EOCClass *weakSelf = self;
    _pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block: ^{
    EOCClass *strongSelf = weakSelf;
    [strongSelf p_doPoll];
    } repeats:YES];
}

这段代码先定义了一个弱引用,令其指向self,然后捕获这个引用,而不是原本的self变量,这样self就不会被计时器所保留块开始执行时,立即生成strong引用,以保证实例在执行期间持续存活。

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

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

相关文章

穷举vs暴搜vs深搜vs回溯vs剪枝(典型算法思想)—— OJ例题算法解析思路

回溯算法的模版 void backtrack(vector<int>& path, vector<int>& choice, ...) {// 满⾜结束条件if (/* 满⾜结束条件 */) {// 将路径添加到结果集中res.push_back(path);return;}// 遍历所有选择for (int i 0; i < choices.size(); i) {// 做出选择…

【Java项目】基于Spring Boot的校园博客系统

【Java项目】基于Spring Boot的校园博客系统 技术简介&#xff1a;采用Java技术、Spring Boot框架、MySQL数据库等实现。 系统简介&#xff1a;校园博客系统是一个典型的管理系统&#xff0c;主要功能包括管理员&#xff1a;首页、个人中心、博主管理、文章分类管理、文章信息…

计算机毕业设计SpringBoot+Vue.js图书进销存管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

算法-数据结构(图)-迪杰斯特拉最短逻辑算法( Dijkstra)

迪杰斯特拉算法&#xff08;Dijkstras Algorithm&#xff09; 是一种用于计算单源最短路径的经典算法&#xff0c;由荷兰计算机科学家 艾兹赫尔迪杰斯特拉&#xff08;Edsger W. Dijkstra&#xff09; 于1956年提出。它的主要目标是找到从图中的某个源节点到所有其他节点的最短…

C语言【进阶篇】之指针——涵盖基础、数组与高级概念

目录 &#x1f680;前言&#x1f914;指针是什么&#x1f31f;指针基础&#x1f4af;内存与地址&#x1f4af;指针变量&#x1f4af; 指针类型&#x1f4af;const 修饰指针&#x1f4af;指针运算&#x1f4af;野指针和 assert 断言 &#x1f4bb;数组与指针&#x1f4af;数组名…

关于命令行下的 git( git add、git commit、git push)

文章目录 关于 gitgit 的概念git 操作&#xff08;git add、git commit、git push 三板斧&#xff09;安装 git新建仓库及配置git clone.gitignoregit addgit commitgit push其他 git 指令git pull&#xff08;把远端的东西拉到本地进行同步&#xff09;其他指令 关于 git git…

DaoCloud 亮相 2025 GDC丨开源赋能 AI 更多可能

2025 年 2 月 21 日至 23 日&#xff0c;上海徐汇西岸&#xff0c;2025 全球开发者先锋大会以 “模塑全球&#xff0c;无限可能” 的主题&#xff0c;围绕云计算、机器人、元宇宙等多元领域&#xff0c;探讨前沿技术创新、应用场景拓展和产业生态赋能&#xff0c;各类专业论坛、…

极速探索 HarmonyOS NEXT:开启国产操作系统开发的新篇章

极速探索 HarmonyOS NEXT&#xff1a;开启国产操作系统开发的新篇章 一、引言二、HarmonyOS NEXT 是什么&#xff1f;背景核心特性 三、HarmonyOS NEXT 的发展历程从 LiteOS 到 HarmonyOS 的逐步演进HarmonyOS NEXT 5.0 的发布 四、HarmonyOS NEXT 对科技的影响技术突破开发者生…

火狐浏览器多开指南:独立窗口独立IP教程

无论是跨境电商从业者需要管理多个店铺账号&#xff0c;还是海外社交媒体营销人员要运营多个社交平台账号&#xff0c;亦或是从事多账号广告投放的人员&#xff0c;都面临着一个共同的挑战 —— 如何高效管理多个账号&#xff0c;并确保每个账号的独立性。 在这种情况下&#…

内容中台是什么?内容管理平台解析

内容中台的核心价值 现代企业数字化转型进程中&#xff0c;内容中台作为中枢系统&#xff0c;通过构建统一化的内容管理平台实现数据资产的高效整合与智能调度。其核心价值体现在打破传统信息孤岛&#xff0c;将分散于CRM、ERP等系统的文档、知识库、产品资料进行标准化归集&a…

1.2 Kaggle大白话:Eedi竞赛Transformer框架解决方案02-GPT_4o生成训练集缺失数据

目录 0. 本栏目竞赛汇总表1. 本文主旨2. AI工程架构3. 数据预处理模块3.1 配置数据路径和处理参数3.2 配置API参数3.3 配置输出路径 4. AI并行处理模块4.1 定义LLM客户端类4.2 定义数据处理函数4.3 定义JSON保存函数4.4 定义数据分片函数4.5 定义分片处理函数4.5 定义文件名排序…

sql server笔记

创建数据库 use master gocreate database stuuuuu//删除数据库if db_id ($$$) is not nullDrop database [$$$] go//新建表USE [studyTest] GOSET ANSI_NULLS ON GOSET QUOTED_IDENTIFIER ON GOCREATE TABLE [dbo].[Table_1]([id] [int] NULL,[name] [varchar](10) NULL ) ON…

uni小程序wx.switchTab有时候跳转错误tab问题,解决办法

在一个子页面里面使用uni.switchTab或者wx.switchTab跳转到tab菜单的时候&#xff0c;先发送了一个请求&#xff0c;然后执行跳转到tab菜单&#xff0c;但是这个时候&#xff0c;出错了........也是非常的奇怪&#xff0c;不加请求就没问题......但是业务逻辑就是要先执行某个请…

【第十节】C++设计模式(结构型模式)-Flyweight( 享元)模式

目录 一、问题背景 二、模式选择 三、代码实现 四、总结讨论 一、问题背景 享元模式&#xff08;Flyweight Pattern&#xff09;在对象存储优化中的应用 在面向对象系统的设计与实现中&#xff0c;创建对象是最常见的操作之一。然而&#xff0c;如果一个应用程序使用了过多…

AORO M6北斗短报文终端:将“太空黑科技”转化为安全保障

在卫星导航领域&#xff0c;北斗系统作为我国自主研发的全球卫星导航系统&#xff0c;正以其独特的短报文通信功能引发全球范围内的广泛关注。这一突破性技术不仅使北斗系统在全球四大导航系统中独树一帜&#xff0c;具备了双向通信能力&#xff0c;更通过遨游通讯推出的AORO M…

深度生成模型(二)——基本概念与数学建模

上一篇笔记中提到了端到端模型底层核心采用了深度生成模型&#xff0c;先简单梳理一下 生成式人工智能&#xff08;Artificial Intelligence Generated Content&#xff0c;AIGC&#xff09;经历了从早期基于概率模型和规则系统的方法到现代深度生成模型的跨越式发展 深度神经…

Mac本地部署Deep Seek R1

Mac本地部署Deep Seek R1 1.安装本地部署大型语言模型的工具 ollama 官网&#xff1a;https://ollama.com/ 2.下载Deepseek R1模型 网址&#xff1a;https://ollama.com/library/deepseek-r1 根据电脑配置&#xff0c;选择模型。 我的电脑&#xff1a;Mac M3 24G内存。 这…

项目——仿RabbitMQ实现消息队列

1.项目介绍 曾经在学习Linux的过程中&#xff0c;我们学习过阻塞队列 (BlockingQueue) 。 当时我们说阻塞队列最大的用途, 就是用来实现生产者消费者模型。 生产者消费者模型是后端开发的常用编程方式&#xff0c; 它存在诸多好处&#xff1a; 解耦合支持并发支持忙闲不均削峰…

【nextjs官方demo】Chapter 6连接数据库报错

问题&#xff1a;跟着demo创建完成postgres数据库&#xff0c;并修改了env文件&#xff0c;需要访问/seed去初始化数据的时候&#xff1a; 报错信息如下&#xff0c;看信息就是bcrypt模块有问题&#xff1a; 排除了你的环境问题后&#xff0c;就看下面这句话&#xff1a; 它的…

Nginx的反向代理(超详细)

正向代理与反向代理概念 1.概念&#xff1a; 反向代理服务器位于用户与目标服务器之间&#xff0c;但对用户而言&#xff0c;反向代理服务器就相当于目标服务器&#xff0c;即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时&#xff0c;用户不需要知道目标服务…