【学习iOS高质量开发】——协议与分类

文章目录

  • 一、通过委托与数据源协议进行对象间通信
    • 1.委托模式
    • 2.要点
  • 二、将类的实现代码分散到便于管理的数个分类之中
    • 1.如何实现
    • 2.要点
  • 三、总是为第三方类的分类名称加前缀
    • 1.为什么总是为第三方类的分类名称加前缀
    • 2.要点
  • 三、勿在分类中声明属性
    • 1.勿在分类中声明属性的原因
    • 2.要点
  • 四、使用“class-continuation分类”隐藏实现细节
    • 1.什么是class-continuation分类
    • 2.“class-continuation分类”的合理用法
    • 3.要点
    • 五、通过协议提供匿名对象
    • 1.什么是匿名对象
    • 2.如何使用
    • 3.要点


一、通过委托与数据源协议进行对象间通信

1.委托模式

对象之间经常需要相互 通信,而通信方式有很多种。OC开发者广泛使用一种“委托模式”的编程设计模式来实现对象间的通信,该模式的主旨是:定义一套接口,某对象若想接收另一个对象的委托,则需遵从此接口,以便于成为其“委托对象”,而这“另一个对象”则可以给委托对象回传一些信息,也可以在发生相关事件时通知委托对象。
此模式可将数据与业务逻辑解藕。比如说,用户界面里有个现实一系列数据所用的视图,那么,此视图只应包含显示数据所需的逻辑代码,而不应决定要显示和中数据以及数据之间如何交互等问题。视图对象的属性中,可以包含负责数据与事件处理的对象。这两种对象分别称为:“数据源”与“委托”。

@protocol EOCNetworkFetcherDelegate 
- (void) networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
- (void) networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
@end


有了这个协议之后,类就可以用一个属性来存放其委托对象了。在本例总,这个类就是EOCNetworkFetcher类。此类的接口可以写成这样:

@interface EOCNetworkFetcher : NSObject
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate> delegate;
@end


这个id类型的协议属性一定要定义成weak,而非strong,因为两者之间必须为“非拥有关系”。一般情况下,扮演delegate的那个对象也要持有本对象,直到用完本对象之后,才会释放。
假如声明属性的时候用strong将本对象与委托对象之间定为“拥有关系”,那么就会引入“保留环”。因此, 本类中存放委托对象的这个属性要么定义为weak,要么定义为unsafe_unretained。如果需要在相关对象销毁时自动清空,则定义为前者,若不需要自动清空,则定义为后者。
在这里插入图片描述
某类若要遵从委托协议,可以在其接口中声明,也可以在“分类”中声明。如果要象外界公布此类实现了某协议,那么就在接口中声明,而如果这个协议是个委托协议的话,那么通常只会在类的内部使用。所以说,这种情况一般都是在“分类”里声明的:

@implementation EOCDataModel () <EOCNetworkFetcherDelegate>
@end
@implementation EOCDataModel 
- (void) networkFetcher: (EOCNetworkFetcher *)fetcher didReceiveData: (NSData *) data {
	/* Handle data */
}
- (void) networkFetcher: (EOCNetworkFetcher *)fetcehr didFailWithError: (NSError *)error {
	/* Handle error */;
}
@end


委托协议中的方法一般都是“可选的”,因为扮演“受委托者”角色的这个对象未必关心其中的所有方法。这时我们就可以使用@optional关键字来标注其大部分或全部的方法:

@protocol EOCNetworkFetcherDelegate
@optional
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError: (NSError *)error;
@end


然而在委托对象上调用可选方法时,就必须提前使用类型信息查询方法,来判断这个委托对象能否响应相关选择子。以EOCNetworkFetcher为例,应该这样写:

NSData *data = /* data obtained from network */;
if ([_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)]) {
	[_delegate networkFetcher:self didReceiveData:data];
}


判断委托对象是否实现了相关方法,如果实现了就调用,如果没有实现就不执行任何操作(因为给nil发送消息将使if语句的值成为false)。
也可以像下方,传入发起委托的实例,然后在delegete对象在实现相关方法时,根据传入的实例分别执行不同的代码子:

- (void)networkFetcher: (EOCNetworkFetcher *)fetcher didReceiveData: (NSData *)data {
	if (fetcher == _myFetcherA) {
		/* Handle data */
	} else if (fetcher == _myFetcherB) {
		/* Handle data */
	}
}


上面这段代码表明,委托对象有两个不同的“网络数据获取器”,所以他们必须根据所传的参数判断到底是哪个EOCNetworkFetcher获取到了数据。若没有此信息,则委托对像在同一时间只能使用一个网络请求获取器,这么做不太好。
delegate里的方法也可以用于从获取委托对象中获取信息。比方说,EOCNetworkFetcher类也许想提供一种机制:在获取数据的时候如果遇到了“重定向”,那么将询问其委托对象是否应该发生重定向。delegate对象中的相关方法也可以写成这样:

- (BOOL)networkFetcher: (EOCNetworkFetcher *)fetcher shouldFollowRedirectToURL: (NSURL *) url;


用结构体缓存委托对象是否能响应特定的选择子。实现缓存功能所用的代码可以写在delegate属性所对应的设置方法里:

- (void) setDelegate:(id<EOCNetworkFetcher>) delegate {
	_delegate = delegate;
	_delegateFlags.didReceiveData = [delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)];
	_delegateFlags.didFailWithError = [delegate respondsToSelector: @selector(networkFetcher:didFailWithError:)];
	_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
}

2.要点

  • 委托模式为对象提供了一套接口,使其可由此将相关事件告知其他对象。
  • 将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事件定义成方法。
  • 当某对象需要从另外一个对象中获取数据时,可以使用委托模式。这种情况下,该模式亦称“数据源协议”
  • 若有必要,可实现含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中。

二、将类的实现代码分散到便于管理的数个分类之中

1.如何实现

OC中有一个分类机制,但是通常是使用它来补充一个需要的类,但其实还有更好的用途,那就是用来规划我们的代码,我们可以通过OC的“分类”机制,把类代码按逻辑划入几个分区中。就像这样:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName: (NSString *)firstName andLastName:(NSString *)lastName;

/ * Friendship methods * /
- (void) addFriend:(EOCPerson *)person;
- (void) removeFriend:(EOCPerson *)person;
- (BOOL) isFriendsWith:(EOCPerson *)person;

/ * Work methods * /
- (void) performDaysWork;
- (void) takeVacationFromWork;

/ * Play methods * /
- (void) goToTheCinema;
- (void) goToSportsGame;

@end


现在,类的实现代码按照方法分成了好几个部分。所以说,这项语言特性就叫做“分类”。本例中,类的基本要素(诸如属性与初始化方法等)都声明在“主实现”里。可是,随着分类数量增加,当前这份实现文件很快就膨胀得无法管理了,此时就可以把每个分类提取到各自的文件中去,以EOCPerson为例,可以按照其分类拆分成下列几个文件:

EOCPerson + Friendship(.h/.m)
EOCPerson + Work(.h/.m)
EOCPerson + Play(.h/.m)


//EOCPerson + Friendship.h
#import "EOCPerson.h"

@interface EOCPerson (Friendship)
- (void) addFriend:(EOCPerson *)person;
- (void) removeFriend:(EOCPerson *)person;
- (void) isFriendsWith:(EOCPerson *)person;
@end

//EOCPerson + Friendship.m
#import "EOCPerson + Friendship.h"

@implementation EOCPerson (Friendship)
- (void) addFriend: (EOCPerson *)person {
	/* ... */
}
- (void) removeFriend:(EOCPerson *)person {
	/* ... */
}
- (BOOL) isFriendsWith:(EOCPerson *)person {
	/* ... */
}

@end


并且我们之前有说过私有方法的命名,通过特殊的前缀将私有方法指示出来,那么我们学了分类规划之后,我们还可以通过创建一个分类,这个分类其中全是私有方法,通过这种方法将这些私有方法都规划到一个类中,当然其还是的遵循之前的命名规则。

2.要点

  • 使用分类机制把类的实现代码划分成易于管理的小块。
  • 将应该视为“私有”的方法归入名叫Private的分类中,以隐藏实现细节。

三、总是为第三方类的分类名称加前缀

1.为什么总是为第三方类的分类名称加前缀

分类机制通常用 于向无源码的既有类中新增功能。这个特性极为强大,但在使用时也很 容易忽视其中可能产生的问题。这个问题在于:分类中的方法是直接添加在类里面的,它们 就好比这个类中的固有方法。将分类方法加人类中这一操作是在运行期系统加载分类时完成 的。运行期系统会把分类中所实现的每个方法都加人类的方法列表中。如果类中本来就有此 方法,而分类又实现了一次,那么分类中的方法会覆盖原来那一份实现代码。实际上可能会 发生很多次覆盖,比如某个分类中的方法覆盖了“ 主实现” 中的相关方法,而另外一个分类 中的方法又覆盖了这个分类中的方法。多次覆盖的结果以最后 一个分类为准。
比方说,要给NSString 添加分类,并在其中提供一些辅助方法,用于处理与HTTP URL 有关的字符串。你可能会把分类写成这样:

@interface NSString (HTTP)

//Encode a string with URL encoding
- (NSString *)urlEncodeString;

//Decode a URL encoded string
- (NSString *)urlDecodedString;

@end 


现在看起来没什么问题,可是,如果还有 一个分类也往NSString里添加方法,那会如何呢?那个分类里可能也有个名叫urIEncodedstring的方法,其代码与你所添加的大同小 异,但却不能正确实现你所需的功能。那个分类的加载时机如果晚于你所写的这个分类,那 么其代码就会把你的那 一份覆盖掉,这样的话,你在代码中调用urlEncodedstring 方法时,实际执行的是那个分类里的实现代码。由于其执行结果和你预期的值不同所以自己所写 的那些代码也许就无法正常运行了。这种bug很难追查因为你可能意识不到实际执行的 urlEncodedString代码并不是自己实现的那一份。
要解决此问题,**一般的做法是:以命名空间来区别各个分类的名称与其中所定义的方法。 想在Objective-C中实现命名空问功能,只有 一个办法,就是给相关名称都加上某个共用的 前缀。与给类名加前级时所应考虑的因素相似,给分类所加的前缀也要选 得怡当才行。**一般来说,这个前级应该与应用程序或程序库中其他地方所用的前缀相同。 于 是,我们可以给刚才那个NSString分类加上ABC前缀:

@interface NSString (ABC_HTTP)

//Encode a string with URL encoding
- (NSString *)abc_urlEncodedString;

//Decode a URL encoded string
- (NSString *)abc_urlDecodedString;

@end


2.要点

  • 向第三方类中添加分类时,总应给其名称加上你专用的前缀
  • 向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀

三、勿在分类中声明属性

1.勿在分类中声明属性的原因

属性是封装数据的方式。尽管从技术上说,分类里也可以声明属性,但这种做法还是要尽量避免。原因在于,除了“class-continuation分类 ” 之 外 , 其 他分类都无法向类中新增实例变量,因此,它们无法把实现属性所需的实例变量合成出来。

所以你要是想在分类中实现属性就得自己进行设置该属性的存取方法。你就可以用@dynamic或者消息转发机制来实现其存取方法,但是还是不太提倡那样做的。

当然,除了上述的方法可以实现设置分类中属性的存取方法之外,还有关联对象也可以解决在分类中不能合成实例变量的问题。就像这样:
在这里插入图片描述

这样做可行,但是不太理想,要把相似的代码写很多遍,而且内存管理问题上容易出错,因为我们在为属性实现存取方法时,经常会忘记遵从其内存管理语义。

正确的做法应该是:把所有属性定义在主接口里。类所封装的全部数据都应该定义在主接口中,这是唯一能够实现实例变量的地方。并且分类机制,它的目的就是用于扩展类的功能,而非封装数据。

有时属性可读也可以定义在分类中,我们可以用其get方法获取相关的信息,但是这样的话还不如直接定义一个方法多好,用处还多,所以说,分类还是好好使用其扩展功能吧。

2.要点

  • 把封装数据所用的全部属性都定义在主接又里。
  • 在“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性。

四、使用“class-continuation分类”隐藏实现细节

1.什么是class-continuation分类

OC动态消息系统的工作方式决定了其不可能实现真正的私有方法或者私有实例变量。那么怎么实现私有变量和私有方法呢?这就要用到特殊的“class-continuation分类”了。

“class-continuation分类”和普通的分类不同,他必须定义在其所接续的那个类的实现文件里,并且这个类没有名字。

@interface EOCPerson ()
// Methods here
@end


这样你就可以在其中定义你的私有方法和私有变量了,这样有什么好处呢?公共接口里本来就能定义实例变量。不过,把它们定义在“class-continuation分类”或“实现块”中可以将其隐藏起来,只供本类使用。这些实例变量也并非真的私有,因为在运行期总可以调用某些方法绕过此限制,不过,从一般意义上来说,他们还是私有的。此外,由于没有声明在公共头文件里,所以将代码作为程序库的一部分来发行时,其隐藏程度更好。

2.“class-continuation分类”的合理用法

“class-continuation分类”还有一种合理用法,就是将public接口中声明为“只读”的属性扩展为“可读写”,以便在类的内部设置其值。

就是说,你在外部.h文件中定义一个“只读”的属性,然后你又在“class-continuation分类”将其的“只读”属性改为“可读写”的,那么这样下来,在外部看来他就是一个“只读”的属性,但是你可以在其内部自定义的设置其值了,他在内部来说就是“可读写”的了。

这样做很有用,既能令外界无法修改对象,又能在其内部按照需要管理其数据。这样,封装在类中的数据就由实例本身来控制,而外部代码则无法修改其值。

还有一种用法:若对象所遵从的协议只应视为私有,则可在“class-continuation分类”中声明名,这样就不会泄漏我们所遵从的协议

#import "EOCPerson.h"
#import "EOCSecretDelegate.h"

@interface EOCPerson () <EOCSecretDelegate>
@end

@implementation EOCPerson
/* ... */
@end


3.要点

  • 通过“class-continuation分类”向类中新增实例变量。
  • 如果某属性在主接口中声明为“只读”,而类的内部又要用设置方法修改此属性,那么就在“class-continuation分类”中将其扩展为“可读写”。
  • 把私有方法的原型声明在“class-continuation分类”里面。
  • 若想使类所遵循的协议不为人所知,则可于“class-continuation分类”中声明。

五、通过协议提供匿名对象

1.什么是匿名对象

若是接口背后有多个不同的实现类,而你又不想指明具体使用哪个类,那么可以考虑用这个方法——因为有时候这些类可能会变,有时候他们又无法容纳于标准的类继承体系中,因而不能以某个公共基类来统一表示。此概念通常称为“匿名对象”。

@property (nonatomic, weak) id<EOCDelegete> delegate;

这个delegate就是“匿名的”,因为当你调用这个delegate的时候你并不知道它指的是那个类,而你却又能使用它所指代类的方法,这就把那个类给隐藏起来了,匿名对象也是同样的原理。

因为你可能定义很多的类,但是我们不能将它们都继承于同一个类,并且在OC中只有id类型可以将这些类的随便一个类都返回,所以我们在使用匿名对象的时候一定是返回的id类型。比如:我们将所有数据库都具备的那些方法放到协议中,令返回的对象遵从此协议。

2.如何使用

先定义一个协议其中包括数据库都有的方法:

@protocol EOCDatabaseConnection
- (void)connect;
- (void)disconnect;
- (BOOL)isConneceted;
- (NSArray *)performQuery:(NSString *)query;
@end


提供一个单例接口:

#import <Foundation/Foundation.h>

@protocol EOCDatabaseConnection;

@interface EOCDatabaseConnection;
+ (id)sharedInstance;
- (id<EOCDatabaseConnection>)connectionWithIdentifier: (NSString *)identifier;

@end


这样的话,处理数据库连接的类名称就不会暴露了,来自不同框架的那些类限制就都可以使用同一个方法来返回了,而不用对每个类都写一个这种协议。

3.要点

  • 协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵从某协议的id类型,协议里规定了对象所实现的方法。
  • 使用匿名对象来隐藏类型名称(或类名 )。
  • 如果具体类型不重要,重要的是对象能够相应(定义在协议里的)特定方法,那么可使用匿名对象来表示。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【最新Dubbo3深入理解】Dubbo3中的SPI机制以及IOC、AOP

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

MFC 配置Halcon

1.新建一个MFC 工程&#xff0c;Halcon 为64位&#xff0c;所以先将工程改为x64 > VC 目录设置包含目录和库目录 包含目录 库目录 c/c ->常规 链接器 ->常规 > 链接器输入 在窗口中添加头文件 #include "HalconCpp.h" #include "Halcon.h"…

canvas水波纹效果,jquery鼠标水波纹插件

canvas水波纹效果&#xff0c;jquery鼠标水波纹插件 效果展示 jQuery水波纹效果&#xff0c;canvas水波纹插件 HTML代码片段 <div class"scroll04wrap"><h3>发展历程</h3><div class"scroll04"><p>不要回头&#xff0c;一…

移动硬盘误删的文件还能找回来吗?1分钟弄清答案!

“想问问大家如果移动硬盘里保存了很多文件&#xff0c;但上次使用时不小心将部分文件删除了&#xff0c;还有机会找回误删的文件吗&#xff1f;应该怎么进行误删文件的恢复呢&#xff1f;” 在数字化时代&#xff0c;移动硬盘成为了我们存储和传输数据的重要工具。然而&#x…

外泌体相关基因肝癌临床模型预测——2-3分纯生信文章复现——02.数据格式整理(1)

内容如下&#xff1a; 1.外泌体和肝癌TCGA数据下载 2.数据格式整理 3.差异表达基因筛选 4.预后相关外泌体基因确定 5.拷贝数变异及突变图谱 6.外泌体基因功能注释 7.LASSO回归筛选外泌体预后模型 8.预后模型验证 9.预后模型鲁棒性分析 10.独立预后因素分析及与临床的…

【Appium UI自动化】pytest运行常见错误解决办法

通过Appium工具录制代码在pycharm上运行报错&#xff1a; 错误一&#xff1a; 1.提示 setup() 方法运行 error failed 解决办法&#xff1a;未创建 init __ 方法&#xff0c;创建一个空的__init.py文件就解决了。 原因&#xff1a; 错误二&#xff1a; 2.运行代码&#xff…

Rabbitmq入门与应用(四)-RabbitMQ常见模式

RabbitMQ常见Queue模式 简单模式 点对点模式&#xff0c;一个生产者一个消费者 生产者将消息发送到队列&#xff0c;消费者从队列中获取消息&#xff0c;队列是存储消息的缓冲区。 查看管理端效果 序列化解决方案 基于java序列化基于Json Bean public MessageConverter mess…

【Java从入门到精通】Java Character 类

Java Character 类 Character 类用于对单个字符进行操作。 Character 类在对象中包装一个基本类型 char 的值 实例 char ch a;// Unicode 字符表示形式 char uniChar \u039A; // 字符数组 char[] charArray { a, b, c, d, e }; 然而&#xff0c;在实际开发过程中&#xf…

利用Python email的MIMEBase及MIMEApplication发送邮件附件

**MIMEBase、 MIMEApplication背景知识点总结** MIMEBase 和 MIMEApplication 都是用于创建 MIME 消息的类&#xff0c;但它们之间有一些异同点。 异同点如下&#xff1a; 用途&#xff1a; MIMEBase 是一个基类&#xff0c;用于表示任何类型的 MIME 消息部分。它提供了一种通…

Video generation models as world simulators-视频生成模型作为世界模拟器

原文地址&#xff1a;Video generation models as world simulators 我们探索在视频数据上进行大规模生成模型的训练。具体来说&#xff0c;我们联合训练文本条件扩散模型&#xff0c;同时处理不同持续时间、分辨率和长宽比的视频和图像。我们利用一个在视频和图像潜在编码的时…

Redis信创平替之TongRDS(东方通),麒麟系统安装步骤

我的系统: 银河麒麟桌面系统V10(SP1)兆芯版 1.先进入东方通申请使用 2.客服会发送一个TongRDS包与center.lic给你(我这里只拿到.tar.gz文件,没有网上的什么安装版) 3.上传全部文件到目录中 4.服务节点安装,并启动 tar -zxvf TongRDS-2.2.1.2_P3.Node.tar.gz cd pmemdb/bin/…

c++实现栈和队列类

c实现栈和队列类 栈(Stack)Stack示意图Stack.cpp 队列(queue)queue 示意图queue.cpp 栈(Stack) Stack示意图 Stack.cpp #pragma once #include "ListStu.cpp"template<typename T> class Stack { public: /* * void push(T& tDate)* 参数一 &#xff1a;…

【Linux从青铜到王者】 基础IO

本篇重点&#xff1a;文件描述符&#xff0c;重定向&#xff0c;缓冲区&#xff0c;磁盘结构&#xff0c;文件系统&#xff0c;inode理解文件的增删查改&#xff0c;查找一个文件为什么一定要有路径&#xff0c;动静态库&#xff0c;有的时候为什么找不到库&#xff0c;动态库的…

SQL面试题及答案

介绍 在快节奏的数据管理和信息技术世界中,导航和操作结构化数据的能力是一项非常重要的技能。SQL,即结构化查询语言,是关系数据库的基石,掌握这种语言的专业人员的需求量很大。SQL 面试在科技行业很常见,潜在的候选人会接受测试以展示他们的知识和解决问题的能力。为了帮…

酷开科技丨新年新玩法!酷开系统壁纸模式给客厅“换”新

甲辰龙年即将到来&#xff0c;新年新家新气象&#xff0c;快到酷开系统壁纸模式中挑选一款喜欢的壁纸&#xff0c;为新的一年增添一份美好和喜悦吧&#xff01; 酷开科技将更多的电视新玩法带给你&#xff0c;让你的电视成为家庭中的焦点&#xff01;酷开系统壁纸模式&#xf…

【前端素材】推荐优质后台管理系统APP Zina平台模板(附源码)

一、需求分析 当我们从多个层次来详细分析后台管理系统时&#xff0c;可以将其功能和定义进一步细分&#xff0c;以便更好地理解其在不同方面的作用和实际运作。 1. 功能层次 a. 用户管理功能&#xff1a; 用户注册和登录&#xff1a;管理用户账户的注册和登录过程。权限管…

easyui 手风琴Accordion 面板的高度设置

今天接到一个新的小需求&#xff0c;如下图&#xff0c;当预算表单只有一个时&#xff0c;要求不显示预算表单这块的内容。 考虑到页面创建时用到了表单的回调和点击方法&#xff0c;所以不能单纯的移除&#xff0c;移除右侧表格的创建会报错&#xff0c;所以只能隐藏。 隐藏…

突破挑战:利用沃尔玛跨境智星实现批量注册与下单

在进行沃尔玛测评时&#xff0c;确保环境安全至关重要。每个账号都应使用独立的运行环境&#xff0c;这样可以避免浏览器指纹、字体以及浏览器数据之间的关联问题。此外&#xff0c;账号的资料也需要特别注意。最好是自己注册自己下单&#xff0c;这样可以确保所有资源都掌握在…

JAVA工程师面试专题-并发编程篇

目录 一、线程 1、并发与并行的区别 2、同步和异步的区别 3、Java中创建线程有哪些方式? 4、Thread和Runnable的区别 5、Java中的Runnable、Callable、Future、FutureTask的区别和联系&#xff1f; 6、说一下你对 CompletableFuture 的理解 7、volatile关键字有什么用&…

如何下载 VisualStudio2022 离线包

1.概要&#xff0c;有些场景下&#xff0c;不允许联网&#xff0c;需要下载离线安装包。 2.过程 2.1 下载地址 创建基于网络的安装 - Visual Studio (Windows) | Microsoft Learn 2.2 选择引导程序 2.3 执行命令 2.3.1 下载全部&#xff1a; vs_Enterprise.exe --layout …