基于 Moya 的网络抽象二次封装。保持与 Moya 相同的用法并扩展 Moya 的 TargetType
实现常用的数据解析,支持: ObjectMapper、Codable、SwiftyJSON,开发者无需编写数据解析的样板代码。您只需关注您想要的数据类型,选择数据类型。 SYMoyaNetwork
已完成所有这些。 SYMoyaNetwork
还实现了网络缓存,并配置了常见的缓存策略,只需实现所需的缓存策略。根据策略 Response
将执行缓存同步。开发者无需花费大量时间做这类工作。 SYMoyaNetwork
使数据请求变得更加简单,减少了开发者编写样板代码的需求,使他们有更多时间关注业务。
有关更多信息,请参阅文档
您可能会像大多数 iOS 开发者一样,将 Moya 作为网络请求的抽象,Moya 是一个出色的框架,它标准化了您的数据请求,并允许您足够简单地完成数据请求。 SYMoyaNetwork
基于 Moya 的二次封装,它并没有改变 Moya 的使用方式,只是对 Moya 的进一步扩展和更友好的封装。
您可能会用 Moya 来编写请求如下:
provider = MoyaProvider<GitHub>()
provider.request(.zen) { result in
switch result {
case let .success(moyaResponse):
let data = moyaResponse.data
let statusCode = moyaResponse.statusCode
// do something with the response data or statusCode
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
当数据请求完成时,我们需要手动将 moyaResponse
转换为我们想要的数据对象。例如,当使用 ObjectMapper 作为数据模型时,每次返回响应时都需要这样做。也许您可以封装一个统一的方法来完成这项工作,但您仍然需要手动调用其中一些转换方法,这会让用户做得很繁琐。因此,SYMoyaNetwork
做了这样的事情,您只需关注您想要获取的数据,SYMoyaNetwork
将为您提供所需的数据返回,例如,当使用 ObjectMapper 作为数据模型时,我们可以这样获取数据对象:
provider = SYMoyaProvider<GitHub>()
/// Note: `BaseMappable `here is the data type in which you implement `BaseMappable`, such as a `Class` or `Struct` or `Other`
provider.responseObject(.zen) { (response: SYMoyaNetworkDataResponse<BaseMappable>) in
switch response.result {
case let .success(mappable):
// The mappable will be the data you want to get, you can use it directly, you don’t need to do any conversion
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
现在使用 SYMoyaNetwork
,您不再需要担心如何将响应转换为所需的数据,您只需关注您想要得到的数据,SYMoyaNetwork
已经完成了这一切。
SYMoyaNetwork
为各种数据类型提供常见的类型解析,例如:JSON
、String
、Image
、ObjectMapper
、Codable
、SwiftyJSON
,当您使用它们时,您只需关注您想要获取的数据类型,而无需关心其他事情。SYMoyaNetwork
已经为 Moya 准备了 Response
分析,您只需关注您的业务实现。
SYMoyaNetwork
不仅可以将 Moya 的响应 Response
进行转换,更重要的是,在 Moya 中,SYMoyaNetwork
帮助您实现了网络缓存。在大多数应用程序中,网络缓存非常重要。它可以加快您的 App 显示速度。它可以节省用户的数据流量。可以说,这是在网络层中一个非常重要的决定。因此,SYMoyaNetwork
提供了常用网络缓存策略的实现。请参阅[数据缓存](### 数据缓存)。
SYMoyaNetwork
支持使用 Combine,也支持使用 RxSwift、ReactiveSwift 以及其他常用的响应式框架。
SYMoyaNetwork
还支持链式请求和批量请求。在大多数商业场景中,我们可能需要发送一批请求,或者需要相关链式请求。它还提供了这些功能,可以轻松快速地实现,请参阅:[链式请求](### 链式请求)和[批量请求](### 批量请求)。
- 支持:使用 Codable、SwiftyJSON 数据解析,开发者只需关注想要获取的数据。
- 扩展了 Moya 的
TargetType
并添加了timeoutInterval
、cdnURL
、allowsCellularAccess
、cachePolicy
等属性。 - 支持数据缓存(磁盘和内存)并实现数据缓存策略。
- 支持中国请求。
- 支持批量请求。
- 支持使用 Combine,也支持使用 RxSwift、ReactiveSwift 以及其他常用的响应式框架。
- 支持并发异步调用。
- 支持请求日志输出,请求数据信息一目了然。
为了支持不同类型的数据解析,SYMoyaNetwork
将不同类型的数据解析拆分为不同的 Framework 包。所有解析数据包都依赖于核心 Core
包。开发者可以选择要使用的解析类型进行安装,例如:如果直接需要使用 RxSwift,则可以直接安装 SYMoyaObjectMapper
包;如果还需要使用 ObjectMapper 作为数据解析,则可以安装 SYMoyaRxObjectMapper
。
要使用 Apple 的 Swift 包管理器进行集成,请将以下内容添加为您的 Package.swift
依赖项:
.package(url: "https://github.com/Shannon-Yang/SYMoyaNetwork", .upToNextMajor(from: "2.0.0"))
然后为您的 Taeget
指定 SYMoyaNetwork
依赖项。以下是一个 PackageDescription
实例:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
dependencies: [
.package(url: "https://github.com/Shannon-Yang/SYMoyaNetwork", .upToNextMajor(from: "2.0.0"))
]
)
将 SYMoyaNetwork
添加到您的 Podfile 中
pod 'SYMoyaNetwork', '~> 2.0'
# or
pod 'SYMoyaNetwork/SYMoyaReactiveObjectMapper', '~> 2.0'
#or
pod 'SYMoyaNetwork/SYMoyaRxObjectMapper', '~> 2.0'
#or
pod 'SYMoyaNetwork/SYMoyaObjectMapper', '~> 2.0'
#or
pod 'SYMoyaNetwork/ReactiveSYMoyaNetwork', '~> 2.0'
#or
pod 'SYMoyaNetwork/RxSYMoyaNetwork', '~> 2.0'
然后运行 pod install
。
在您想使用 SYMoyaNetwork
的任何文件中,使用 import SYMoyaNetwork
来导入框架。
Cartfile 用户可以指向此存储库并使用它们首选的构建框架 SYMoyaNetwork
以下是Cartfile中的代码:
github "Shannon-Yang/SYMoyaNetwork"
然后运行 carthage update --use-xcframeworks
。
如果您是第一次在一个项目中使用 Carthage,您需要执行一些额外的步骤,这些步骤在 Carthage 中有说明。
注意:目前,Carthage 不能提供只构建特定存储库子模块的方法。所有子模块及其依赖项都将使用上面的命令进行构建。但是,您不需要将未使用的框架复制到项目中。例如,如果您没有使用
ObjectMapper
,那么在carthage update
完成后,您可以自由删除与ObjectMapper
一起从 Carthage Build 目录中删除的框架。
- 打开终端,并将其
cd
到项目顶层目录。如果您的项目尚未初始化为Git仓库,请运行以下命令:
$ git init
- 将
Alamofire
、Moya
、SYMoyaNetwork
以及您想要使用的任何数据模型库(例如ObjectMapper)添加为git 子模块。
$ git submodule add https://github.com/Alamofire/Alamofire.git
$ git submodule add https://github.com/Moya/Moya.git
$ git submodule add https://github.com/Shannon-Yang/SYMoyaNetwork
$ git submodule add 'The data model library you want to use, such as ObjectMapper, SwiftyJSON'
- 打开新创建的
Alamofire
文件夹,将Alamofire.xcodeproj
拖放到Xcode项目的导航中。在Moya文件夹下按照相同的方法处理Moya.xcodeproj
,在SYMoyaNetwork文件夹下按照相同的方法处理SYMoyaNetwork.xcodeproj
,对其他数据模型库也进行相同的操作。
它们应嵌套在您的应用程序蓝色项目图标下方,位于其他Xcode组之上或之下,这无关紧要。
- 请确认
xcodeproj
的部署target
与项目导航中的应用程序target
一致。 - 接下来,在项目导航(蓝色项目图标)中选择您的应用程序项目,然后转到目标配置窗口,并在侧边栏的“ Targets”标题下选择应用程序
target
。 - 在窗口顶部的标签栏中,打开“General”面板。
- 在“Embedded Binaries”字段下方点击
+
按钮。 - 您将看到两个不同的
Alamofire.xcodeproj
文件夹。每个文件夹在“Products”文件夹中包含两个不同的版本的Alamofire.framework
。
您选择的“Products”文件夹无关紧要,重要的是您选择的是“Products”文件夹中哪个位置的
Alamofire.framework
。
- 对于iOS选择上面的
Alamofire.framework
,对于macOS选择下面的。
您可以通过检查项目的构建日志来验证您选择了哪个:构建目标将被列为准
Alamofire iOS
、Alamofire macOS
、Alamofire tvOS
或Alamofire watchOS
。
-
再次点击
+
按钮,为Moya
添加正确的构建目标,并为SYMoyaNetwork
执行相同的操作。 -
就是这样!
这些框架将自动添加到复制文件构建阶段作为目标依赖项、链接框架和嵌入框架,这就是您在模拟器和设备上构建所需的所有内容。
与使用Moya
一样,SYMoyaNetwork
的使用方法与Moya
完全相同。您无需担心其复杂的使用。
SYMoyaNetwork
支持多种数据类型,如JSON
、String
、Image
、ObjectMapper
、Codable
、SwiftyJSON
等。您可以使用SYMoyaProvider
调用相应的Response
方法。
provider = SYMoyaProvider<GitHub>()
provider.responseJSON(.zen) { (response: SYMoyaNetworkDataResponse<Any>) in
switch response.result {
case let .success(json):
// do something with the response json data. You can use the json object directly without conversion
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
provider = SYMoyaProvider<GitHub>()
provider.responseString(.zen) { (response: SYMoyaNetworkDataResponse<String>) in
switch response.result {
case let .success(string):
// do something with the response string data. You can use the string object directly without conversion
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
provider = SYMoyaProvider<GitHub>()
provider.responseImage(.zen) { (response: SYMoyaNetworkDataResponse<Image>) in
switch response.result {
case let .success(image):
// do something with the response image data. You can use the image object directly without conversion
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
provider = SYMoyaProvider<GitHub>()
provider.responseObject(.zen) { (response: SYMoyaNetworkDataResponse<T: BaseMappable>) in
switch response.result {
case let .success(mappableObject):
// do something with the response mappableObject data. You can use the mappableObject object directly without conversion
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
provider = SYMoyaProvider<GitHub>()
provider.responseObject(.zen) { (response: SYMoyaNetworkDataResponse<T: Decodable>) in
switch response.result {
case let .success(codableObject):
// do something with the response codableObject data. You can use the codableObject object directly without conversion
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
provider = SYMoyaProvider<GitHub>()
provider.responseSwiftyJSON(.zen) { (response: SYMoyaNetworkDataResponse<SwiftyJSON.JSON>) in
switch response.result {
case let .success(swiftyjson):
// do something with the response swiftyjson data. You can use the swiftyjson object directly without conversion
case let .failure(error):
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
}
在大多数业务场景中,我们需要将服务器返回的响应本地缓存,例如:长时间未更新的资源或用户没有网络时需要显示的内容。针对这些情况,SYMoyaNetwork
已经做了所有这些。SYMoyaNetwork
可以进行两种类型的存储,一种是内存存储(MemoryStorage),另一种是磁盘存储(DiskStorage)。需要传递与存储相关的信息,如diskStorageConfig
、memoryStorageConfig
等。具体详情请参考NetworkCacheType.NetworkCacheOptionsInfo
。具体示例代码如下
var networkCacheType: NetworkCacheType {
return .cache(networkCacheOptionsInfo: .init())
}
默认使用的networkCacheOptionsInfo
是NetworkConfig
中的默认配置。您还可以自定义配置。您只需要初始化自定义的networkCacheOptionsInfo
对象。当networkCacheType
返回类型为cache
时,当请求完成时,它将使用此参数验证缓存条件是否满足。如果符合缓存条件,无论是一次获取、POST或其他请求,数据都将根据缓存信息自动进行缓存。
SYMoyaProvider
提供了responseCodableObject
、responseObject<T: BaseMappable>
、responseSwiftyJSON
和其他方法。在每个方法中,都有一个如responseDataSourceType
的参数。此参数主要指定数据返回的类型。目前,responseDataSourceType
被分为5种数据返回类型:server
、cache
、cacheIfPossible
、cacheAndServer
和custom
。
server
:直接从服务器获取数据,不会检索缓存数据cache
:如果有缓存,则直接从缓存中获取数据并做出回调。当成功时,将调用success
结果。如果没有缓存,将调用failure
结果并返回相应的error
信息。不会发起网络请求,只会从缓存中检索。cacheIfPossible
:如果有缓存,则直接从缓存获取数据。如果缓存获取成功,将执行success
回调。如果缓存获取失败,将发起网络请求。网络请求成功后,将执行success
回调。在请求失败后,将执行success
回调并执行failure
回调。cacheAndServer
:如果有缓存,将首先获取缓存数据并做出回调,然后发起网络请求,然后再次调用。custom
:自定义模式的回调需要实现ResponseDataSourceCustomizable
协议,它将首先从缓存中获取缓存数据。在获取到缓存数据后,将通过shouldSendRequest
方法回调当前缓存数量,通过回调的缓存数据可以进行判断,是否需要通过shouldUpdateCache
方法回调更新缓存。这种数据回调模式更常用于请求获取相对大量数据。
“custom”场景如下。例如:我们有一本书包含很多书详细信息。当我们第一次获取书详细信息时,一个更聪明的做法是将当前书详细信息保存在本地,以便此书的详细信息均缓存。下次打开应用程序时,将首先显示此书的缓存数据,然后请求最新书详细信息并更新本地缓存。这确实可以达到预期的效果,但它并非最优解。在正常情况下,如果完整请求书详细信息会覆盖本地缓存,由于书详细信息数据可能相对较大,网络请求响应时间会非常长,用户的流量也会浪费,因此,更好的解决方案是只请求当前书的某些基本信息,使用基本信息的一些关键字段判断本地缓存的当前书数据是否最新,然后确定是否需要更新本地缓存。如果书的详细数据是最新的,那么将不需要请求数据,例如基本信息中的《code》字段和其他字段。可以通过这些字段传递给服务器验证当前缓存是否最新。如果当前缓存不是最新的,则发起网络请求以获取最新书详细信息数据。这样不仅可以首先向用户展示数据,还可以节省用户流量并减少在无需更新数据时对该大数据的请求。
在某些情况下,我们可能需要发送一组网络请求。SYMoyaNetwork
提供批量发起网络请求的操作。SYMoyaBatchProviderSession
主要用于启动批量网络请求操作。在发起网络请求之前,需要初始化和实现SYBatchMoyaProviderType
数组对象,默认情况下,SYMoyaBatchProvider
已经实现了SYBatchMoyaProviderType
。在批量请求期间,SYMoyaBatchProviderSession
维护一个SYMoyaBatchProvider
请求数组。所有请求完成后,将返回一个SYMoyaProviderSessionResponse
数组。
注:在批量请求过程中,只要其中一个请求失败,Provider将调用
failure
方法。只有当所有请求都成功时,才会回调success
。
例如:
var session: SYMoyaBatchProviderSession?
let provider = SYMoyaBatchProvider<HTTPBinDynamicData>(targetTypes: [.getDelay(delay: 1), .stream(n: 1)])
let provider2 = SYMoyaBatchProvider<HTTPBinResponseFormats>(targetTypes: [.brotli, .json, .gzipped])
session = SYMoyaBatchProviderSession(providers: [provider, provider2])
session?.request { [weak self] progress in
// do something with the response batch data. You can use the batchData directly without conversion
} completion: { [weak self] result in
// this means there was a network failure - either the request
// wasn't sent (connectivity), or no response was received (server
// timed out). If the server responds with a 4xx or 5xx error, that
// will be sent as a ".success"-ful response.
}
用于管理相互依赖的网络请求,实际上最终可以用来管理多个拓扑排序的网络请求。
例如,我们有一个业务需要在注册时先发送注册API
- 如果注册成功,发送读取用户信息的API。此外,读取用户信息的API需要使用成功注册返回的用户ID。
- 如果注册失败,将不会发送读取用户信息的API。
例如:
let chainProvider = SYMoyaChainProvider(targetType: HTTPBinAuth.bearer) { progress in
debugPrint("🏃🏻♀️🏃🏻♀️🏃🏻♀️🏃🏻♀️----> \(progress) <---- < Class: \(type(of: self)) Function:\(#function) Line: \(#line) >🏃🏻♀️🏃🏻♀️🏃🏻♀️🏃🏻♀️")
}
SYMoyaChainProviderSession.request(chainMoyaProviderType: chainProvider) { response in
let targetType = response.targetType
let result = response.result
switch targetType {
case HTTPBinAuth.bearer:
let json = result.serializerSwiftyJSON().value
let authenticated = json?["authenticated"].boolValue ?? false
if authenticated {
return SYMoyaChainProvider(targetType: HTTPBinDynamicData.getDelay(delay: 1))
}
case HTTPBinDynamicData.getDelay:
let responseString = result.serializerStringDataResponse(atKeyPath: nil)
self.contentLabel.text = responseString.value
self.contentLabel.isHidden = false
self.indicator.stopAnimating()
default:
break
}
return nil
} completion: {
self.indicator.stopAnimating()
debugPrint("🔥🔥🔥🔥🔥----> <---- < Class: \(type(of: self)) Function:\(#function) Line: \(#line) >🔥🔥🔥🔥🔥")
}
SYMoyaNetwork是在MIT许可证下发布的。有关更多信息,请参阅License.md。