iOS 多线程面试题

article/2025/8/20 1:32:15

没有比这里更全的了,看我就好了

面试官😃 :你了解进程吗?谈谈你对进程和线程的理解?

不谈进程,线程无从谈起。要了解什么是线程,我们先需要理解什么是线程。

秒懂百科 😃

 以上百科,我们大概了解了进程和线程。也是面试考察的基本点。大致说出画红色的部分也就可以了。

面试官😃 :iOS中,有哪些实现多线程的方式?

这是一道比较综合性的题目。所知道的都回答出来

1,pthread

        特点:C语言。跨平台,可移植,使用难度大。生命周期:自己管理。

2,NSThread

        特点:OC语言。面向对象,简单易用,可直接操作线程。生命周期:自己管理。

3,GCD

        特点:替代NSThread,充分利用多核的技术。生命周期:系统管理。

4,NSOperaton

        特点:基于GCD的封装。比GCD多了一些简单实用的功能。生命周期:系统管理。

面试官😃 :请说一下多线程中GCD和NSOperation的区别?

面试官问这道题,大部分都是从网上找的面试题,想看一下面试者对GCD和NSOperation的掌握程度,知道多少说多少!但是,这不是终结,这只是个开端,慢慢往下看。

口述各自的特点和不同点,从基本谈起:

GCD和NSOperation都是iOS中多线程实现的方式,它们有各自的特点,又有区别的地方。

GCD

1,提供了一次性执行的代码,也就是说保证了一段代码在程序执行的过程中只被执行一次,并且是线程安全的!(dispatch_once),实现单例。

2,提供了延迟执行的简便方法。(dispatch_after)

3,提供了调度组的使用,监听一些列异步方法之行结束之后,我们得到统一的通知,(dispatch_group,dispatch_group_async,dispatch_group_notify)(dispatch_group_enter/dispatch_group_leave)

4,提供了快速迭代的方式dispatch_apply。按照指定的次序将制定的任务追加到指定的队列中,并等待全部队列执行结束!

5,提供了信号量(dispatch_semaphore_t),使用信号量可以实现安全的多线程!(加锁的一种方式)

6,提供了栅栏函数,dispatch_barrier_async,使用栅栏函数可以实现线程的多度单写!

​​​​​​NSOpearion

1,NSOperatoin是对GCD更高层次的封装。

2,NSOperation可以设置两个操作之间的依赖关系。

3,NSOperation是个抽象类,开发中使用它的两个子类,NSBlockOperation/NSInvocationOperation。

4,使用KVO,观察NSOperation的各种状态(isExecuted是否正在执行,isFinished是否结束,isCancled是否取消)。无法判断GCD的状态。

5,NSOperation可以设置操作的优先级。

6,NSoperation可以方便的取消一个操作的执行

7,可以重写NSOperation的main和start函数。

挖坑了,接下来填坑😃  !面试官😃  可以从以上回答的任意一点展开,深入挖掘面试者的掌握程度。好戏开场了 😃 !

面试官😃 :dispatch_once是怎样保证线程安全的?

我们都知道dispatch_once可以保证线程安全,那么它是怎么做到的呢?

如果你还不能快速手写单例,请多多练习。不能每次碰到单例,就去其他单例类复制吧!

以下只我工作中单例的一种写法,不足欢迎指正:

.h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface PersonTool : NSObject<NSCopying, NSMutableCopying>+ (instancetype)shared;- (NSString *)getName;@endNS_ASSUME_NONNULL_END

.m


#import "PersonTool.h"@implementation PersonTool+ (instancetype)shared
{static PersonTool *_instace = nil;dispatch_once_t onceToken;dispatch_once(&onceToken, ^{_instace = [[super allocWithZone:NULL] init];});return _instace;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone
{return [PersonTool shared];
}- (id)copyWithZone:(NSZone *)zone
{return [PersonTool shared];
}- (id)mutableCopyWithZone:(NSZone *)zone
{return [PersonTool shared];
}- (NSString *)getName
{return @"你好!";
}

dispatch_once的底层实现原理会单开一篇文章!后续补充!!!

面试官😃 :dispatch_after延迟执行,执行时间是准确的吗?

dispatch_after的延迟执行时间不是准确的,因为dispatch_after是在指定时间之后将任务添加到主队列,并不是在指定时间之后开始执行处理!

面试官😃 :说说你对dispatch_apply的理解?

dispatch_apply 是GCD提供的一种快速迭代的函数,按照指定的次数将指定的任务追加到指定的队列中,并等待全部任务结束。

如果用在串行队列,就和for循环一样,按顺序同步执行。

如果用在并发队列,别追加到队列的任务会异步执行,并且等待全部任务结束!

使用实例:

    dispatch_apply(5, dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT), ^(size_t iteration) {NSLog(@"iOS");});//会等到上面的任务全部执行结束在执行下面的代码NSLog(@"结束");

面试官😃 :说说你对dispatch_group的理解?

GCD提供的队列组,有两种使用方式dispatch_group_async和dispatch_group_enter/dispatch_group_leave,使用过程中要根据任务类型选择使用哪种方式。

如果面试者的回答没有说到所处理的任务类型,那么只能说面试者是知道有这个东西,没有使用或了解过!!!

如果任务类型是同步任务,使用dispatch_group_async和dispatch_group_enter/dispatch_group_leave是同样的,可以实现相同的功能。

如果任务类型是异步任务,比如(AF)网络请求,那么区别就很大了。

如果任务类型是异步任务,使用dispatch_group_async不能等到所有异步任务执行完成,再去之执行dispatch_group_notify中的代码!!!使用dispatch_group_enter/dispatch_group_leave可以实现执行完添加的异步任务,最后执行dispatch_group_notify中的代码!!!但是异步任务的顺序是不可控制的,也就是不能控制队列组中的异步任务的顺序!!!

如果你要实现这样一个功能,请求网络A和B,然后根据A/B返回的内容去刷新页面,如果使用dispatch_group,那么只能使用dispatch_grouo_enter/dispatch_grouo_leave!!!使用dispatch_group_async是不能实现这个功能的!

使用dispatch_group实现异步任务的顺序执行是做不到的!!!

方式一:dispatch_async & dispatch_group_enter & dispatch_group_leavel & dispatch_group_wait (已验证,可行)

- (void)viewDidLoad {[super viewDidLoad];dispatch_queue_t queueColumn = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);dispatch_async(queueColumn, ^{[self setrsInfoIsTimer:NO];});//...
}- (void)setrsInfoIsTimer:(BOOL)isTimer
{//局部变量的group,可以解决dispatch_group_leave(group)奔溃的问题,dispatch_queue_t serial_queue = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);dispatch_group_t group = dispatch_group_create();//MARK: 开始组装数据dispatch_group_enter(group);dispatch_async(serial_queue, ^{NSLog(@"getGuestListIsFirstEnter --- group ---");[self getHallInfoGroup:group];});//阻塞线程SerialQueue1dispatch_group_wait(group, DISPATCH_TIME_FOREVER);dispatch_group_enter(group);dispatch_async(serial_queue, ^{NSLog(@"getGuestListIsFirstEnter --- group ---");[self getGuestListIsFirstEnter:YES isTimer:isTimer group:group];});//阻塞线程SerialQueue1dispatch_group_wait(group, DISPATCH_TIME_FOREVER);dispatch_async(dispatch_get_main_queue(), ^{//...更新UI});//MARK:组装数据结束
}- (void)getHallInfoGroup:(dispatch_group_t)group
{NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {dispatch_group_leave(group);} TranFail:^(NSDictionary * _Nonnull errInfo) {dispatch_group_leave(group);} showLoading:self.showLoading];
}-(void)getGuestListIsFirstEnter:(BOOL)firstEnter isTimer:(BOOL)isTimer group:(nullable dispatch_group_t)group
{NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {dispatch_group_leave(group);} TranFail:^(NSDictionary * _Nonnull errInfo) {dispatch_group_leave(group);} showLoading:self.showLoading];
}

方式二:dispatch_group_async & dispatch_group_notify(尚未验证!)

- (void)viewDidLoad {[super viewDidLoad];dispatch_queue_t queueColumn = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);dispatch_async(queueColumn, ^{[self setrsInfoIsTimer:NO];});//...
}- (void)setrsInfoIsTimer:(BOOL)isTimer
{//局部变量的group,可以解决dispatch_group_leave(group)奔溃的问题,dispatch_queue_t serial_queue = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);dispatch_group_t group = dispatch_group_create();//MARK: 开始组装数据dispatch_async(serial_queue, group), ^{[self getHallInfoGroup:group];});//阻塞线程SerialQueue1dispatch_group_wait(group, DISPATCH_TIME_FOREVER);dispatch_group_enter(group);dispatch_async(serial_queue, group), ^{[self getGuestListIsFirstEnter:YES isTimer:isTimer group:group];});//阻塞线程SerialQueue1dispatch_group_wait(group, DISPATCH_TIME_FOREVER);dispatch_group_notify(group, global_queue, ^{dispatch_async(dispatch_get_main_queue(), ^{//...更新UI});});//MARK:组装数据结束
}- (void)getHallInfoGroup:(dispatch_group_t)group
{NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {} TranFail:^(NSDictionary * _Nonnull errInfo) {} showLoading:self.showLoading];
}-(void)getGuestListIsFirstEnter:(BOOL)firstEnter isTimer:(BOOL)isTimer group:(nullable dispatch_group_t)group
{NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {} TranFail:^(NSDictionary * _Nonnull errInfo) {} showLoading:self.showLoading];
}

方式三:请参考 dispatch_semaphore_t

面试官😃 :说说你项目中的哪些功能使用了dispatch_semaphore_t,解决了什么问题?

这个问题主要考察上面提到的异步任务的顺序执行。

使用信号量(dispatch_semaphore_t)可以实现异步任务的顺序执行(也就是将异步任务转换为同步任务执行)不要阻塞主线程!。也是多线程加锁的一种实现方式,保证线程安全。

dispatch_semaphore_create(intptr_t value)

创建一个队列组,传入得值>=0,传入的值控制控制并发线程的数量!!!,如果我们传入2,那么就表示当前最多有两个线程同时执行。

dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

等待当前线程,直到某个时间释放!!!

dispatch_semaphore_signal(dispatch_semaphore_t dsema);

增加信号量,使信号量的值加1!!!

如果在工作中有这样一个需求,(使用AFNetworking)请求A接口拿到A接口返回的id_a,用id_a作为参数去请求B接口,拿到B网络返回的name_b去查数据库,然后刷新页面。该怎么实现呢?

当然你可以一层层去嵌套,但是作为有点经验的程序员都会这样干。这时候可以通过信号量(dispatch_semaphore)实现。

以下是主要实现的代码,

@property (nonatomic, assign) dispatch_semaphore_t semaphore;@property (nonatomic, assign) dispatch_queue_t queue;- (void)viewDidLoad{[super viewDidLoad];//:测试[self semaphoreSync];
}- (void)semaphoreSync
{//创建信号量,传入参数0self.semaphore = dispatch_semaphore_create(0);//创建队列,这里串行和并发并无区别self.queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);//开启一个新线程,//这里之所以要创建一个新线程,而不是在当前(主线程)执行,是因为,AF的网络请求返回默认是在主线程中执行,如果我们在当前线程执行一下操作,会发生线程死锁的现象,dispatch_async(self.queue, ^{//任务Aint ida = [self requestA];//任务BNSString *name = [self requestB:ida];//任务CNSDictionary *res = [self queryDB:name];NSLog(@"%@", res);dispatch_async(dispatch_get_main_queue(), ^{//刷新页面});});
}- (int)requestA
{__block int ida = 0;//AFNSArray *paths = @[@(self.currentPage), @(pageNum), @(100)];[[LTXNetWorkManager shareManager] GetWithUrlString:TC_API(GetCourseList) paths:paths successedBlock:^(BOOL successed, id  _Nonnull jsonObject) {ida = 1;//释放信号量,信号量加1,释放当前线程,然后执行return操作dispatch_semaphore_signal(self.semaphore);} failedBlock:^(NSError * _Nonnull error) {dispatch_semaphore_signal(self.semaphore);}];//信号量减1,阻塞当前线程dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);return ida;
}- (NSString *)requestB:(int)ida
{__block NSString *name;NSArray *paths = @[@(self.currentPage), @(pageNum), @(100), @(ida)];[[LTXNetWorkManager shareManager] GetWithUrlString:TC_API(GetCourseList) paths:paths successedBlock:^(BOOL successed, id  _Nonnull jsonObject) {name = @"你好👋";dispatch_semaphore_signal(self.semaphore);} failedBlock:^(NSError * _Nonnull error) {dispatch_semaphore_signal(self.semaphore);}];dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);return name;
}- (NSDictionary *)queryDB:(NSString *)name
{//查询数据库,返回结果return @{@"name":@"name"};
}

面试官😃 :请实现一个多读单写的功能?

我们可以用dispatch_barrier_async实现

面试官😃 :说说你对NSOperation/NSOperationQueue的使用和理解?

NSOperation/NSOperationQueue 是系统提供的一套多线程实现方案。实际上NSOperation/NSOperationQueue是基于GCD更高层次的封装,完全面向对象,比GCD简单易用,代码可读性更高。

使用步骤:

1,创建操作,将操作封装到NSOperation对象中,

2,创建队列,NSOperationQueue,

3,将操作添加到队列中,

之后,系统会将队列中的操作取出,在新线程中执行操作。添加到队列中的操作,首先进入准备就绪状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作开始执行,开始执行的顺序取决于操作之间的相对优先级,操作执行结束的顺序,取决于操作本身!

我们先来理解这一部分的操作/队列/线程

* 添加操作到队列,开启新线程!

* 具体开启线程数量,有系统决定!操作在哪条线程执行有系统决定!

* 一个队列中同时能并发执行的最大操作数由maxConcurrentOperationCount 最大并发操作数。也就是一个操作队列中的操作是串行还是并发执行,由maxConcurrentOperationCount它决定!

maxConcurrentOperationCount = -1,默认,并发执行,

maxConcurrentOperationCount = 1,串行执行,

maxConcurrentOperationCount = 3,并发执行,一个队列中同时能并发执行的最大操作数是3,但是这个值不应大于系统设定的默认值,

* 操作的优先级适用于同一队列中的操作,决定了进入准备就绪状态下的操作之间的开始执行顺序,优先级不能取代依赖关系。

* 线程间的通讯 

[[NSOperationQueue mainQueue] addOperationWithBlock:^{}];

*操作的状态

op1.isReady; 是否准备就绪

op1.isExecuting; 是否正在执行

op1.isCancelled; 是否已经取消

op1.isFinished; 是否执行完成

* 取消一个操作和队列的取消

 [op1 cancel]; 取消操作,实际上是标记isCancelled状态

 [queue cancelAllOperations]; 取消队列

这里的取消不是真正意义上的取消,而是当当前的操作执行完成之后,不再进行新的操作。

使用NSOperation/NSOperationQueue实现异步任务的顺序执行是做不到的!!!

面试官😃 :你是否在定义过NSOperation?

自定义NSOperation可以通过重写main或者start方法。重写main方法,不需要管理操作的状态属性isExecuting和isFinished。重写start方法需要管理操作的状态属性。


http://chatgpt.dhexx.cn/article/kKHJmDGH.shtml

相关文章

2020,300道高级iOS开发面试题(最新整理)

这个栏目将持续更新–请iOS的小伙伴关注! 一&#xff1a;知名大厂iOS开发面试题篇 1、腾讯—最新iOS面试题总结 2、百度—最新iOS面试题总结 3、头条—最新iOS面试题总结 4、阿里—最新iOS面试题总结 5、美团—最新iOS面试题总结 6、某大厂—最新iOS面试题总结 7、抖音–最新i…

iOS经典面试题大全

1.INTERVIEW 共勉 作为一个开发者&#xff0c;有一个学习的氛围跟一个交流圈子特别重要&#xff0c;这是一个我的iOS交流群&#xff1a;638302184&#xff0c;不管你是小白还是大牛欢迎入驻 &#xff0c;分享BAT,阿里面试题、面试经验&#xff0c;讨论技术&#xff0c; 与2800i…

2021年,整理的iOS高频面试题及答案(总会有你需要的)

推荐阅读&#xff1a;关于iOS面试题汇总(栏目持续更新) 各位最近应该忙于跳槽与面试吧&#xff0c;毕竟金三银四&#xff0c;珍惜好机会&#xff0c;预祝大家面试顺利通过&#xff0c;迎接大厂offer。有需要资料可以私聊我了解 从输入url到页面展示到底发生了什么 1、输入地…

iOS面试题 2016版

2015-1-3 达内纪老师 GitHub&#xff0c;CSDN博客 说明&#xff1a; 最近为达内学员整理面试题。发现网上的面试题和答案基本都是抄来抄去的&#xff0c;甚至很多答案都是错误的。 所以整理了常见的面试题&#xff0c;对答案重新进行了筛选整理。 如果答案有错漏或者更好的答案…

iOS面试题系列之常见算法

iOS面试中熟悉常见算法 1、 对以下一组数据进行降序排序&#xff08;冒泡排序&#xff09;。“24&#xff0c;17&#xff0c;85&#xff0c;13&#xff0c;9&#xff0c;54&#xff0c;76&#xff0c;45&#xff0c;5&#xff0c;63” int main(int argc, char *argv[]) {int …

(2021年)iOS面试题及答案,以及添加Flutter 面试问题,Swift面试题

面试题的深入解析&#xff1b;​​​​​​​ 一&#xff0c;内存管理在实际开发中的应运。 1.UITableView的数据条数太多时会消耗内存&#xff0c;可以给UITableViewCell、UICollectionViewCell、UITableViewHeaderFooterView设置正确的复用ID&#xff0c;充分复用。 2.有…

iOS中高级面试题

https://blog.csdn.net/u014600626/article/details/102923706 iOS基础 1&#xff1a;讲讲你对atomic & nonatomic的理解 1、原子操作对线程安全并无任何安全保证。被 atomic 修饰的属性(不重载设置器和访问器)只保证了对数据读写的完整性&#xff0c;也就是原子性&am…

ios 面试题

1 为什么block要用copy修饰&#xff1f; 答&#xff1a;因为block在创建的时候&#xff0c;它的内存是分配在栈上的&#xff0c;而不是在堆区。栈区的特点是&#xff1a;对象随时有可能被销毁&#xff0c;一旦被销毁&#xff0c;在调用时就会造成崩溃。所以我们要使用copy吧它拷…

2022年 iOS面试题总结

前言 都说今年互联网行情很差&#xff0c;iOS行情更差。但到底怎么样呢&#xff0c;不能光听别人说&#xff0c;而要自己走出去看一看。我的面试的阶段基本都在3月份&#xff0c;准备的阶段则要再往前推个半个月吧。期间约到了不少一二线互联网公司面试机会&#xff0c;前期由…

iOS面试题(七)

iOS面试题&#xff08;一&#xff09; iOS面试题&#xff08;二&#xff09; iOS面试题&#xff08;三&#xff09; iOS面试题&#xff08;四&#xff09; iOS面试题&#xff08;五&#xff09; iOS面试题&#xff08;六&#xff09; iOS面试题&#xff08;七&#xff09; iOS面…

iOS基础面试题(一)

kaikaijia同学私信我,说想加群,我就建个iOS开发群,大家做技术交流和资源,群号:241048287(已满),群号2 :340957379(已满) 群号3:370041534 (已满) 有兴趣的同学可以加群,验证信息:iOS+姓名。 所有的群都已到人数上限,本着“与时俱进”精神,建了个"iOS面试&…

ios面试题总结

本篇主要针对面试题进行解析&#xff0c;会进行基础知识的总结和拓展&#xff0c;仅供参考&#xff0c;如有错误&#xff0c;欢迎指出&#xff0c;一起学习&#xff01; 一、关于Foundation框架中的问题 &#xff08;一&#xff09;NSCache & NSDictionary 1.NSDictiona…

iOS面试题大全2021(附答案)

1、简述你项目中常用的设计模式。它们有什么优缺点&#xff1f; 常用的设计模式有&#xff1a;代理、观察者、单例。 &#xff08;1&#xff09;单例&#xff1a;它是用来限制一个类只能创建一个对象。这个对象中的属性可以存储全局共享的数据。所有的类都能访问、设置此单例…

iOS 中高级面试题(附答案)

RunLoop 1、什么是 RunLoop? RunLoop 作用有哪些&#xff1f; RunLoop 可以称之为运行循环&#xff0c;在程序运行过程中循环做一些事情&#xff0c;如果没有 RunLoop 程序执行完毕就会立即退出&#xff0c;有 RunLoop 程序会一直运行&#xff0c;并且时时刻刻在等待用户的输…

安装MyBatis教程

简单安装MyBatis教程 1. 介绍 MyBatis简介 1&#xff09; MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架 2&#xff09; MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集 3&#xff09; MyBatis可以使用简单的XML或注解用于配置和原…

mybatis简明教程

mybatis MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射&#xff0c;本文将让您快速掌握mybatis开发 一&#xff1a; 简介 一只被烤黑了的鸟 MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBa…

Mybatis教程-实战

1.从JDBC谈起 1.1.使用IDEA创建maven工程 1.2.引入mysql依赖包 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.32</version> </dependency>1.3.准备数据 创建数据库&a…

SpringBoot使用Mybatis教程

文章目录 新建SpringBoot项目引入mybatis依赖如何使用mybatis&#xff1f;1.配置mybatis①.数据库配置②.mybatis相关配置 2.使用mybatis①.创建JavaBean②.创建mapper1).使用注解方式2&#xff09;.使用xml方式 ③.调用 新建SpringBoot项目 本文所使用的代码编辑器为IntelliJ…

MyBatis教程看这一篇就够啦,简单又全面(IDEA版)

目录 一、MyBatis简介 1.1 MyBatis介绍 为什么需要Mybatis&#xff1f; 二、MyBatis框架部署 2.1 创建Maven项目 2.2 在项目中添加MyBatis依赖 2.3 创建MyBatis配置文件 三、MyBatis框架使用 3.1 创建数据表 3.2 创建实体类 3.3 创建DAO接口&#xff0c;定义操作方法 …

MyBatis学习--完整教程

文章目录 MyBatis1、简介1.1 什么是Mybatis1.2 持久化1.3 持久层1.4 为什么需要MyBatis 2、第一个Mybatis程序2.1 搭建环境2.2 创建一个模块2.3 编写代码 3、CURD1. namespace2. select3. Insert4. update5. Delete6. 万能Map7. 模糊查询 4、配置解析1. 核心配置文件2. 环境配置…