iOS--NSURLSession Alamofire流程源码解析(万字详解版)

server/2024/10/20 5:51:02/

一、NSURLSession

NSURLSession的主要功能是发起网络请求获取网络数据,是Apple的网络请求原生库之一。Alamofire就是对NSURLSession的封装,如果对NSURLSession不熟悉的话,那么Alamofire源码看起来会比较费劲的。因此我们先简单学习下NSURLSession。

从NSURLSession这个名字中我们不难看出,主要是URL + Session。顾名思义,NSURLSession是用来URL会话的。iOS的NSURLSession的主要功能是通过URL与服务器交流的

一句话总结:我们的iOS客户端可以使用NSURLSession这个东西通过相应的URL与我们的服务器建立会话,然后通过此会话来完成一些交互任务(NSURLSessionTask)。Session有着不同的类型,每种类型的Session又可以执行不同类型的任务(Task)。接下来就来介绍一下Session的类型以及所执行的任务等。

1.NSURLSession的类型

在使用NSURLSession时你得知道你使用的是哪种类型的Session。从官方的NSURLSession API中不难看出,共有三种类型的Session:Default sessions,Ephemeral sessions,Background sessions这三种Session我们可以通过NSURLSessionConfiguration来指定。

  • 默认会话(Default Sessions)使用了持久的磁盘缓存,并且将证书存入用户的钥匙串中。它的特点是使用系统的缓存和凭证存储机制,适合处理需要缓存数据或保持会话状态的网络请求
  • 临时会话(Ephemeral Session)没有像磁盘中存入任何数据,与该会话相关的证书、缓存等都会存在RAM中。因此当你的App临时会话无效时,证书以及缓存等数据就会被清除掉。因此,它非常适合那些对隐私要求较高的场景。
  • 后台会话(Background sessions)除了使用一个单独的线程来处理会话之外,与默认会话类似。适合处理需要长时间执行的任务,如文件下载和上传。此类会话通常会在后台传输文件,即使应用程序被挂起或终止,网络任务仍能继续执行,且完成后会通知应用。

Session在初始化时可以指定下方的任意一种SessionConfiguration:

let defaultSession = URLSession(configuration: .default)let ephemeralSession = URLSession(configuration: .ephemeral)let backgroundSession = URLSession(configuration: .background(withIdentifier: ".background"))

2. NSURLSession的各种任务

在一个Session会话中可以发起的任务可分为三种:数据任务(Data Task)、下载任务(Download Task)、上传任务(Upload Task)。在iOS8和OS X 10.10之前的版本中后台会话是不支持Data Task。下面来简述一下这三种任务。

  • Data Task(数据任务)负责使用NSData对象来发送和接收数据。Data Task是为了那些简短的并且经常从服务器请求的数据而准备的。该任务可以每请求一次就对返回的数据进行一次处理。
  • Download task(下载任务)以表单的形式接收一个文件的数据,该任务支持后台下载。
  • Upload task(上传任务)以表单的形式上传一个文件的数据,该任务同样支持后台下载。

3、URL

(1)URL编码概述

无论是GET、POST还是其他的请求,与服务器交互的URL是需要进行编码的。因为进行URL编码的参数服务器那边才能进行解析,为了能和服务器正常的交互,我们需要对我们的参数进行转义和编码。URL就是互联网上资源的地址,用户和服务器都可以通过URL来找到其想访问的资源。RFC3986文档规定,URL中只允许包含英文字母(a-zA-Z)、数字(0-9)、“-_.~”4个特殊字符以及所有保留字符,如果你的URL中含有汉字,那么就需要对其进行转码了。RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]。

(2)URL编码的规则

在URL编码时有一定的规则,如下图是我们今天主要使用的URL格式的一个规则的一个图解:

需要注意的是图中Query的部分。其中,Path和Query之间使用的是?号进行分隔的,问号后边就是我们要传给服务器的参数了,该参数就是下方的Query的部分在GET请求中Query是存放在URL后边,而在POST中是放在Request的Body中

如果你的参数只是一个key-Value, 那么Query的形式就是key = value。如果你的参数是一个数组比如key = [itme1, item2, item3,……],那么你的Query的格式就是key[]=item1&key[itme2]&key[item3]……。如果你的参数是一个字典比如key = ["subKey1":"item1", "subKey2":"item2"], 那么Query对应的形式就是key[subKey1]=item1&key[subKey2]=item2.

接下来我们要做的就是将字典进行URL编码。

(3)将Dictionary进行URL编码

在iOS开发中,有时候我们从VC层或者VM层获取到的数据是一个字典,字典中存储的就是要发给服务器的数据参数。直接将字典转成二进制数据发送给服务器,服务器那边是没法解析iOS这边的字典的,得有一个统一的交互标准,这个标准就是URL编码。我们要做的就是讲字典进行URL编码,然后将编码后的东西在传给服务器,这样一来服务器那边就能解析到我们请求的参数了。下方折叠的这段代码就是从AlamoFire框架中摘抄出来的几个方法,位于ParameterEncoding.swift文件中。该段代码就是负责将字典类型的参数进行URL编码的,在编码过程中进行转义是少不了的。

import Foundation//MARK: Alamofire中的三个方法该方法将字典转换成URL编码的字符
/*用于将键值对转换为 URL 查询参数的形式。它递归地处理嵌套的字典和数组,并根据键和值生成合适的查询组件(键值对)。这些查询组件通常用于将复杂的字典或数组编码成 HTTP 请求 URL 中的查询字符串参数。*/private func query(_ parameters: [String: Any]) -> String {var components: [(String, String)] = []for key in parameters.keys.sorted(by: <) {let value = parameters[key]!components += queryComponents(fromKey: key, value: value)}return components.map { "\($0)=\($1)" }.joined(separator: "&")
}public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {var components: [(String, String)] = []
//如果 value 是一个字典类型 [String: Any],这里会将字典的键用方括号括起来进行拼接,比如如果传入的 key 是 "user",而 nestedKey 是 "name",那么组合后的键会变为 "user[name]",从而表示字典的嵌套结构。if let dictionary = value as? [String: Any] {for (nestedKey, value) in dictionary {components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)}} //如果 value 是数组类型 [Any],代码会遍历数组中的每个值。else if let array = value as? [Any] {for value in array {components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)}} //如果 value 是 NSNumber,首先判断它是否是布尔类型。else if let value = value as? NSNumber {if value.isBool {//如果是布尔值,使用 boolEncoding.encode 方法来处理布尔值的编码(通常布尔值会被转换为 "true" 或 "false" 字符串)。components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))} else {//如果是数字类型,则直接将其转为字符串并添加到 components 数组中。components.append((escape(key), escape("\(value)")))}} //如果 value 是布尔类型 Bool,直接使用 boolEncoding.encode 方法来进行编码,然后将结果添加到 components 数组中。else if let bool = value as? Bool {components.append((escape(key), escape(boolEncoding.encode(value: bool))))} else {//如果 value 是其他任何类型(如字符串等),直接将 value 转换为字符串并添加到 components 中。components.append((escape(key), escape("\(value)")))}return components
}

这个方法通过递归地处理字典、数组、布尔值和数字,将它们转换成一对对的查询参数。它能够处理复杂的嵌套数据结构,并将它们编码成 URL 查询字符串所需的形式。之所以进行递归,因为字典中有可能含有字典或者数组,数组中又可能嵌套着数组或者字典。所以要进行递归,直到找到key=value这种形式为止。

调用上述代码段的query()方法就可以对字典进行转义。query()方法的参数是一个[String, AnyObject]类型的字典,返回参数是一个字符串。这个返回的字符串就是将该字典进行编码后的结果。

总的来讲,就是与服务器交互发送的URL是需要编码的,而编码是需要遵从某种规则,这种规则在Alamofire中是以query()和queryComponents()方法中进行的,并且规则的本质上就是将键值对转换成一个字符串。

tips:关于数据的传输:

  • GET 请求 将数据作为查询参数附加在 URL 中发送。
  • POST 请求 将数据放在 请求体 中,并通过 Content-Type 告知服务器如何解析数据(例如:json)。

4.URLSessionDelegate 代理机制

URLSessionDelegate 代理机制是 NSURLSession 的一部分,用于处理与网络请求相关的各种事件。

NSURLSession用法:

  1.GET--从服务器请求数据

import Foundation
//MARK: GET--从服务器请求数据
let myURLString = "https://httpbin.org/get"
let url = URL(string: myURLString)!
let urlRequest = URLRequest(url: url)
let session = URLSession.shared// 创建 dataTask
let task = session.dataTask(with: urlRequest) { data, response, error in// 检查是否有错误if let error = error {print("Error: \(error.localizedDescription)")return}// 检查响应是否为 HTTP 响应if let httpResponse = response as? HTTPURLResponse {print("Status code: \(httpResponse.statusCode)")}// 确保有数据返回guard let data = data else {print("No data received")return}// 尝试将数据转换为 JSON 格式do {if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {print("JSON response: \(json)")}} catch {print("Failed to serialize JSON: \(error.localizedDescription)")}
}// 启动任务
task.resume()

在GET样例代码中,可以看到,首先我们创建了一个URL,然后构建了URLRequest对象,接着通过 URLSession.shared 创建一个共享的会话(session),这样可以复用会话中的缓存、Cookie 等信息。紧接着使用 URLSession 的 dataTask(with:) 方法创建一个任务,并为其指定回调闭包,用于处理请求完成后的响应和数据。最后,我们将数据转成JSON格式。

  2.POST--向服务器发送数据

import Foundationlet myURLString = "https://httpbin.org/post"
let url = URL(string: myURLString)!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
//设置请求头(Header)--“application/json” -> 告诉服务器我们将以 JSON 格式发送数据
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")//构建请求体(Parameters)--要发送的数据
let parameters: [String: Any] = ["name": "John","age": 30
]// 将请求体 转换为 JSON 格式 -- 使用 JSONSerialization 将字典序列化为 Data 对象(即 JSON 格式的二进制数据)
do {let jsonData = try JSONSerialization.data(withJSONObject: parameters, options: [])urlRequest.httpBody = jsonData
} catch {print("Error in JSON serialization: \(error.localizedDescription)")
}// 创建 URLSession 并发送请求
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { data, response, error in// 检查是否有错误if let error = error {print("Error: \(error.localizedDescription)")return}// 检查响应是否为 HTTP 响应if let httpResponse = response as? HTTPURLResponse {print("Status code: \(httpResponse.statusCode)")}// 确保有数据返回guard let data = data else {print("No data received")return}// 尝试将数据转换为 JSON 格式do {if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {print("Response JSON: \(json)")}} catch {print("Failed to serialize JSON: \(error.localizedDescription)")}
}// 启动任务
task.resume()

在POST样例代码中,可以看到,我们需要发送请求给服务器,请求主要分为请求头、请求体:请求头是告诉服务器如何处理请求(告诉服务器应该如何解析数据),以及携带关于客户端的环境、数据格式、认证等内容、请求体是实际要通过 HTTP 请求传递给服务器的数据。

其余与GET大差不差。。。

基本流程:

创建一个URLRequest 对象,URLRequest 对象描述了发送到服务器的 HTTP 请求,包括 URL、请求体、请求头等信息。然后通过 NSURLSession 使用 dataTask 将 URLRequest 发送到服务器。

二、Alamofire

博客中的Alamofire源码的版本是以5.6.4版本为例。其平台较少,不像最新版本新添了visionOS等一系列新平台,框架较大不容易阅读。

在上述NSURLSession的学习中我们大概了解了NSURLSession是如何工作的:先建立URLRequest, URLSession, 接着创建 dataTask ,并调用方法resume() 去唤醒. 结束以后通过回调dataTask 告诉我们结果。当然最后需要通过序列化JSONSerialization 把二进制Data转换为JSON,或者处理错误errors。

Alamofire实际上是封装了URLSession,所以Alamofire 也会创建dataTask,然后调用方法.resume() 去唤醒执行. 接下来就进一步揭露Alamofire 的神秘面纱。

我们从Alamofire的用法反推其具体流程。

以下是我在项目中Alamofire的一种用法:

AF.request("https://nlp.aliyuncs.com/v2/api/chat/send", 
method: .post, 
parameters: parameters, 
encoding: JSONEncoding.default, 
headers: headers).response 
{ response in}

我们看底层封装代码:

    open func request(_ convertible: URLConvertible,method: HTTPMethod = .get,parameters: Parameters? = nil,encoding: ParameterEncoding = URLEncoding.default,headers: HTTPHeaders? = nil,interceptor: RequestInterceptor? = nil,requestModifier: RequestModifier? = nil) -> DataRequest {let convertible = RequestConvertible(url: convertible,method: method,parameters: parameters,encoding: encoding,headers: headers,requestModifier: requestModifier)return request(convertible, interceptor: interceptor)}

方法参数:

  • convertible: URLConvertible :请求的 URL,可以是 String 或 URL 类型,Alamofire 会将其转换为 URL。
  • method: HTTPMethod = .get :HTTP 请求方法,默认为 GET,可设为 POSTPUT 等。
  • parameters: Parameters? = nil :请求体、请求的参数(字典 [String: Any]),用于发送数据。
  • encoding: ParameterEncoding = URLEncoding.default :参数的编码方式,常见有:

    URLEncoding.default :GET 请求将参数编码为 URL 查询字符串。

    JSONEncoding.default :POST 请求会将参数作为请求体的 JSON 数据发送。

  • headers: HTTPHeaders? = nil :自定义请求头,可传入 HTTPHeaders 类型对象。
  • interceptor: RequestInterceptor? = nil :拦截器,用于修改请求或处理重试策略等。
  • requestModifier: RequestModifier? = nil :请求修改器,在请求创建后进一步调整请求。

此外,可以看到方法中构建了一个RequestConvertible对象,这是一个结构体对象,是 Alamofire 内部的一个工具类,用于将 URL、请求方法、参数、编码等组合成 URLRequest

让我们看看RequestConvertible的源码:

    struct RequestConvertible: URLRequestConvertible {let url: URLConvertiblelet method: HTTPMethodlet parameters: Parameters?let encoding: ParameterEncodinglet headers: HTTPHeaders?let requestModifier: RequestModifier?func asURLRequest() throws -> URLRequest {var request = try URLRequest(url: url, method: method, headers: headers)try requestModifier?(&request)return try encoding.encode(request, with: parameters)}}

我们重点关注下asURLRequest()方法:

 var request = try URLRequest(url: url, method: method, headers: headers)

根据传入的 URL、HTTP 方法、和请求头来创建一个基础的 URLRequest 对象(可以看成一个数据包,描述了发送到服务器的 HTTP 请求,包括 URL、请求体、请求头等信息)。

return try encoding.encode(request, with: parameters)

encoding.encode:根据指定的 ParameterEncoding(如 URLEncoding 或 JSONEncoding),将请求参数编码到 URLRequest 中。

GET 请求:参数通常会被编码为 URL 查询字符串(附加在 URL 后面)。

POST 请求:参数通常会被编码为请求体中的 JSON。

• 最终返回经过编码的 URLRequest,这是发送 HTTP 请求的最终形态。

由此可见,RequestConvertible的作用是将 Alamofire 的网络请求参数(包括 URL、方法、参数、编码、请求头等)打包并生成 URLRequest

接着我们返回到AF.request()方法中,我们再来深入了解下返回值:

return request(convertible, interceptor: interceptor)

底层实现:

    open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest {let request = DataRequest(convertible: convertible,underlyingQueue: rootQueue,serializationQueue: serializationQueue,eventMonitor: eventMonitor,interceptor: interceptor,delegate: self)perform(request)return request}

其中convertible就是我们上面了解的RequestConvertible,也就是说,这里参数输入的是一个URLRequest对象,另外一个参数interceptor: RequestInterceptor?是请求拦截器,允许在请求执行之前或之后进行额外的操作,如重试请求、修改请求等。它是一个可选参数,默认值为 nil。

此外,DataRequest是 Alamofire 用于管理网络请求的核心对象。它继承自 Request,封装了请求的生命周期管理、进度跟踪、响应解析等功能。其底层是一些初始化,用于赋值,这里就不多赘述。

我们重点了解下perform(request)的底层代码:

var activeRequests: Set<Request> = []   
public let rootQueue: DispatchQueuefunc perform(_ request: Request) {rootQueue.async {guard !request.isCancelled else { return }self.activeRequests.insert(request)//将当前请求加入到 activeRequests 集合中,这个集合存储所有正在执行的请求,便于管理和追踪。self.requestQueue.async {switch request {case let r as UploadRequest: self.performUploadRequest(r) // UploadRequest must come before DataRequest due to subtype relationship.case let r as DataRequest: self.performDataRequest(r)case let r as DownloadRequest: self.performDownloadRequest(r)case let r as DataStreamRequest: self.performDataStreamRequest(r)default: fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))")}}}}

perform(_ request: Request) 方法接收一个 Request 对象,并根据具体的请求类型(DataRequest、UploadRequest、DownloadRequest 等)选择合适的执行逻辑来处理该请求

我们看下switch这条语句,可以看到Alamofire根据 request 的类型进行分派,选择合适的处理方法。其中request 的类型有:

  • UploadRequest:执行上传请求逻辑,调用 performUploadRequest(_:)。
  • DataRequest:执行普通的数据请求,调用 performDataRequest(_:),这是最常用的请求类型。
  • DownloadRequest:执行下载请求逻辑,调用 performDownloadRequest(_:)。
  • DataStreamRequest:处理数据流请求,调用 performDataStreamRequest(_:)。
  • default:如果遇到不支持的请求类型,直接抛出致命错误。

其中performUploadRequestperformDataRequest这些方法都是由performSetupOperations()这个方法实现的。

我们来看下performSetupOperations()的底层实现:

    func performSetupOperations(for request: Request,convertible: URLRequestConvertible,shouldCreateTask: @escaping () -> Bool = { true }) {dispatchPrecondition(condition: .onQueue(requestQueue))//确保请求操作被安全地调度到指定队列上。let initialRequest: URLRequestdo {initialRequest = try convertible.asURLRequest()//将 URLRequestConvertible 转换为实际的 URLRequest。try initialRequest.validate()//验证生成的 URLRequest,检查 URL 是否合法、方法是否合理等。} catch {rootQueue.async { request.didFailToCreateURLRequest(with: error.asAFError(or: .createURLRequestFailed(error: error))) }return}rootQueue.async { request.didCreateInitialURLRequest(initialRequest) }//该回调通知请求对象,已经成功创建了初始的 URLRequest,为后续执行做好准备。guard !request.isCancelled else { return }guard let adapter = adapter(for: request) else {guard shouldCreateTask() else { return }rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) }return}let adapterState = RequestAdapterState(requestID: request.id, session: self)adapter.adapt(initialRequest, using: adapterState) { result indo {let adaptedRequest = try result.get()try adaptedRequest.validate()self.rootQueue.async { request.didAdaptInitialRequest(initialRequest, to: adaptedRequest) }guard shouldCreateTask() else { return }self.rootQueue.async { self.didCreateURLRequest(adaptedRequest, for: request) }} catch {self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: .requestAdaptationFailed(error: error)) }}}}

这个方法的主要作用是设置网络请求的初始配置,包括将传入的 URLRequestConvertible 转换为 URLRequest,进行请求验证,适配器操作(如果有),并最终为网络请求创建 URLSessionTask。该方法对请求生命周期中的准备阶段做了全面的处理。

我们重点看下这一段代码:

guard let adapter = adapter(for: request) else {guard shouldCreateTask() else { return }rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) }return
}

adapter(for: request):检查是否为请求配置了 RequestAdapter(适配器),适配器允许在发出请求前对 URLRequest 进行修改(如添加认证信息、修改 URL 等)。

如果没有的话,则执行request的请求,也就是self.didCreateURLRequest(initialRequest, for: request)

self.didCreateURLRequest(initialRequest, for: request)的代码实现:

    func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) {dispatchPrecondition(condition: .onQueue(rootQueue))request.didCreateURLRequest(urlRequest)guard !request.isCancelled else { return }let task = request.task(for: urlRequest, using: session)requestTaskMap[request] = taskrequest.didCreateTask(task)updateStatesForTask(task, request: request)}func updateStatesForTask(_ task: URLSessionTask, request: Request) {dispatchPrecondition(condition: .onQueue(rootQueue))request.withState { state inswitch state {case .initialized, .finished:// Do nothing.breakcase .resumed:task.resume()rootQueue.async { request.didResumeTask(task) }case .suspended:task.suspend()rootQueue.async { request.didSuspendTask(task) }case .cancelled:// Resume to ensure metrics are gathered.task.resume()task.cancel()rootQueue.async { request.didCancelTask(task) }}}}

看到这里是不是就很熟悉了,没错这里就是NSURLSession中的URLSessionTask(NSURLSession的各种任务)。上述方法创建RequestTask,最后调用task.resume()。其实本质上和NSURLSession一样,只不过是封装了而已。

小结:

1. Alamofire 概述:

• Alamofire 是对 NSURLSession 的封装,简化了网络请求的处理流程,提供更便捷的 API。

• 主要流程包括创建 URLRequest、发起请求、处理响应等,内部依然使用 URLSession 和 URLSessionTask。

2. 核心步骤:

AF.request() 方法是入口,参数包括 URL、请求方法(如 GET/POST)、请求参数、编码方式、请求头等。

• 该方法内部会创建一个 RequestConvertible 对象,将所有请求参数封装成 URLRequest。

3. 执行请求:

• DataRequest 是 Alamofire 处理网络请求的核心对象。它继承自 Request,管理请求的生命周期。

• perform(request) 根据不同请求类型(DataRequest、UploadRequest 等)调用相应的处理方法。

4. 请求最终执行:

• 通过 performSetupOperations 设置请求参数并验证生成的 URLRequest,接着通过 URLSessionTask 发起请求,最后调用 .resume() 来执行任务。

总结:Alamofire 的封装本质上是对 NSURLSession 工作流的简化和扩展,主要增强了参数编码、请求头管理、响应处理等功能,便于开发者更方便地进行网络操作。

参考:

GitHub - Alamofire/Alamofire: Elegant HTTP Networking in Swift

Alamofire 5源码解析一: 执行HTTP Request_alamofire5.0 interceptor-CSDN博客

iOS开发之Alamofire源码解析前奏--NSURLSession全家桶-腾讯云开发者社区-腾讯云

NSURLSession | Apple Developer Documentation


http://www.ppmy.cn/server/133259.html

相关文章

wpf grid 的用法

WPF中的Grid是一种布局控件&#xff0c;可用于将子控件按照行和列的方式排列。 以下是Grid控件的用法&#xff1a; 在XAML文件中&#xff0c;添加一个Grid控件&#xff1a; <Grid> </Grid>在Grid控件中&#xff0c;添加行和列定义&#xff1a; <Grid><…

ue5 扇形射线检测和鼠标拖拽物体

这里的NumTrace是要发射几根射线&#xff0c;Degrees Per Trace是每根射线之间的角度&#xff0c; 例如 要在角色面前实现一个180度的扇形射线检测&#xff0c;就需这两个变量乘起来等于180 TraceLength是射线的长度 下面是鼠标拖动物体逻辑&#xff0c;很简单 这里的Floor和…

HTML(五)列表详解

在HTML中&#xff0c;列表可以分为两种&#xff0c;一种为有序列表。另一种为无序列表 今天就来详细讲解一下这两种列表如何实现&#xff0c;效果如何 1.有序列表 有序列表的标准格式如下&#xff1a; <ol><li>列表项一</li><li>列表项二</li>…

Ubuntu22.04中安装英伟达驱动并部署Pytorch深度学习环境

安装英伟达驱动 本文基于windows10ubuntu22.04双系统&#xff0c;给ubuntu22.04安装英伟达驱动。 安装必要。依赖 sudo apt update # 获取最新的软件包信息 sudo apt upgrade # 升级软件包 sudo apt install g sudo apt install gcc sudo apt install make禁用ubuntu默认驱动…

Vue3工程基本创建模板

创建vue工程 执行这两个绿色的命令 输入 code . 打开vscode 安装依赖 Element - plus npm install element-plus --save 在vscode的 main.js 换这个代码 // main.ts import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/inde…

【Kafka】Kafka Producer的缓冲池机制原理

如何初始化的bufferPool的 在初始化的时候 初始化BufferPool对象 // 设置缓冲区 this.accumulator new RecordAccumulator(xxxxx,其他参数,new BufferPool(this.totalMemorySize, config.getInt(ProducerConfig.BATCH_SIZE_CONFIG), metrics, time, PRODUCER_METRIC_GROUP_N…

在QT中使用V4L2获取传上来的yuyv(4:2:2)的数据转换为QImage显示在屏幕上

背景 项目需要用到OV3703 USB免驱摄像头在I.MX6ULL的平台上&#xff0c;但是勾八QCamera的库只能去处理RGB格式的数据,yuyv的处理不了&#xff0c;所以只能自己去把yuyv&#xff08;4:2:2&#xff09;的数据转换为RGB去显示。幸好有个德国牙医写了个V4L2的中间件可以获取到yuy…

逍遥安卓模拟器命令行合集(memuc命令)

逍遥安卓模拟器命令行合集&#xff08;memuc命令&#xff09; 用cmd自行测试 模拟器配合工具&#xff1a;memuc是v6.0.0版本推出的命令行工具&#xff0c;它封装了MEmuConsole、MEmu、MEmuManage的接口&#xff0c;支持多开管理、修改配置、android通信、adb命令等功能。 memu…