SYMoyaNetwork 2.0.0

SYMoyaNetwork 2.0.0

Shannon Yang 维护。




  • ShannonYang

SYMoyaNetwork

基于 Moya 的网络抽象二次封装。保持与 Moya 相同的用法并扩展 MoyaTargetType 实现常用的数据解析,支持: ObjectMapperCodableSwiftyJSON,开发者无需编写数据解析的样板代码。您只需关注您想要的数据类型,选择数据类型。 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 为各种数据类型提供常见的类型解析,例如:JSONStringImageObjectMapperCodableSwiftyJSON,当您使用它们时,您只需关注您想要获取的数据类型,而无需关心其他事情。SYMoyaNetwork 已经为 Moya 准备了 Response 分析,您只需关注您的业务实现。

SYMoyaNetwork 不仅可以将 Moya 的响应 Response 进行转换,更重要的是,在 Moya 中,SYMoyaNetwork 帮助您实现了网络缓存。在大多数应用程序中,网络缓存非常重要。它可以加快您的 App 显示速度。它可以节省用户的数据流量。可以说,这是在网络层中一个非常重要的决定。因此,SYMoyaNetwork 提供了常用网络缓存策略的实现。请参阅[数据缓存](### 数据缓存)。

SYMoyaNetwork 支持使用 Combine,也支持使用 RxSwiftReactiveSwift 以及其他常用的响应式框架。

SYMoyaNetwork 还支持链式请求和批量请求。在大多数商业场景中,我们可能需要发送一批请求,或者需要相关链式请求。它还提供了这些功能,可以轻松快速地实现,请参阅:[链式请求](### 链式请求)和[批量请求](### 批量请求)。

特性

  • 支持:使用 CodableSwiftyJSON 数据解析,开发者只需关注想要获取的数据。
  • 扩展了 MoyaTargetType 并添加了 timeoutIntervalcdnURLallowsCellularAccesscachePolicy 等属性。
  • 支持数据缓存(磁盘和内存)并实现数据缓存策略。
  • 支持中国请求。
  • 支持批量请求。
  • 支持使用 Combine,也支持使用 RxSwiftReactiveSwift 以及其他常用的响应式框架。
  • 支持并发异步调用。
  • 支持请求日志输出,请求数据信息一目了然。

提示

为了支持不同类型的数据解析,SYMoyaNetwork 将不同类型的数据解析拆分为不同的 Framework 包。所有解析数据包都依赖于核心 Core 包。开发者可以选择要使用的解析类型进行安装,例如:如果直接需要使用 RxSwift,则可以直接安装 SYMoyaObjectMapper 包;如果还需要使用 ObjectMapper 作为数据解析,则可以安装 SYMoyaRxObjectMapper

SYMoyaNetwork

安装

Swift 包管理器

要使用 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"))
    ]
)

CocoaPods

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

Cartfile 用户可以指向此存储库并使用它们首选的构建框架 SYMoyaNetwork

以下是Cartfile中的代码:

github "Shannon-Yang/SYMoyaNetwork"

然后运行 carthage update --use-xcframeworks

如果您是第一次在一个项目中使用 Carthage,您需要执行一些额外的步骤,这些步骤在 Carthage 中有说明。

注意:目前,Carthage 不能提供只构建特定存储库子模块的方法。所有子模块及其依赖项都将使用上面的命令进行构建。但是,您不需要将未使用的框架复制到项目中。例如,如果您没有使用 ObjectMapper,那么在 carthage update 完成后,您可以自由删除与 ObjectMapper 一起从 Carthage Build 目录中删除的框架。

手动

  • 打开终端,并将其cd到项目顶层目录。如果您的项目尚未初始化为Git仓库,请运行以下命令:
$ git init
  • AlamofireMoyaSYMoyaNetwork以及您想要使用的任何数据模型库(例如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 iOSAlamofire macOSAlamofire tvOSAlamofire watchOS

  • 再次点击+按钮,为Moya添加正确的构建目标,并为SYMoyaNetwork执行相同的操作。

  • 就是这样!

这些框架将自动添加到复制文件构建阶段作为目标依赖项、链接框架和嵌入框架,这就是您在模拟器和设备上构建所需的所有内容。

使用方法

数据请求

与使用Moya一样,SYMoyaNetwork的使用方法与Moya完全相同。您无需担心其复杂的使用。

SYMoyaNetwork支持多种数据类型,如JSONStringImageObjectMapperCodableSwiftyJSON等。您可以使用SYMoyaProvider调用相应的Response方法。

JSON

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.
    }
}

String

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.
    }
}

Image

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.
    }
}

ObjectMapper

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.
    }
}

Codable

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.
    }
}

SwiftyJSON

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)。需要传递与存储相关的信息,如diskStorageConfigmemoryStorageConfig等。具体详情请参考NetworkCacheType.NetworkCacheOptionsInfo。具体示例代码如下

var networkCacheType: NetworkCacheType {
        return .cache(networkCacheOptionsInfo: .init())
    }

默认使用的networkCacheOptionsInfoNetworkConfig中的默认配置。您还可以自定义配置。您只需要初始化自定义的networkCacheOptionsInfo对象。当networkCacheType返回类型为cache时,当请求完成时,它将使用此参数验证缓存条件是否满足。如果符合缓存条件,无论是一次获取、POST或其他请求,数据都将根据缓存信息自动进行缓存。

SYMoyaProvider提供了responseCodableObjectresponseObject<T: BaseMappable>responseSwiftyJSON和其他方法。在每个方法中,都有一个如responseDataSourceType的参数。此参数主要指定数据返回的类型。目前,responseDataSourceType被分为5种数据返回类型:servercachecacheIfPossiblecacheAndServercustom

  • 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。