Swift 掌控Moya的网络请求、数据解析与缓存

栏目: Swift · 发布时间: 7年前

内容简介:解决的方案有很多,不过我比较习惯使用GitHub:
  • Moya 在Swift开发中起着重要的网络交互作用,但是还有不如之处,比如网络不可用时,返回的 Responsenil ,这时还得去解析相应的 Error
  • Codable 可以帮助我们快速的解析数据,但是一旦声明的属性类型与json中的不一致,将无法正常解析; 而且对于模型中自定义属性名的处理也十分繁琐

解决的方案有很多,不过我比较习惯使用 MoyaMapper ,不仅可以解决上述问题,还提供了多种 模型转换数据互转多种数据类型任意存储 的便捷方法。掌控Moya的网络请求、数据解析与缓存简直易如反掌。

MoyaMapper 是基于Moya和SwiftyJSON封装的工具,以Moya的plugin的方式来实现间接解析,支持RxSwift

GitHub: MoyaMapper

:book: 详细的使用请查看手册MoyaMapper.github.io

特点

  • 支持 jsonModel 自动映射 与 自定义映射
  • 无视 json 中值的类型, Model 中属性声明的是什么类型,它就是什么类型
  • 支持 json字符串Model
  • 插件方式,全方位保障 Moya.Response ,拒绝各种网络问题导致 Responsenil ,将各式各样的原因导致的数据加载失败进行统一处理,开发者只需要关注 Response
  • 可选 - 支持数据随意缓存( JSONNumberStringBoolMoya.Response )
  • 可选 - 支持网络请求缓存

数据解析

一、插件注入

附: 插件 MoyaMapperPlugin 的详细使用

Swift 掌控Moya的网络请求、数据解析与缓存

1、定义适用于项目接口的 ModelableParameterType

// statusCodeKey、tipStrKey、 modelKey 可以任意指定级别的路径,如: "error>used"
struct NetParameter : ModelableParameterType {
    var successValue = "000"
    var statusCodeKey = "retStatus"
    var tipStrKey = "retMsg"
    var modelKey = "retBody"
}
复制代码

2、在 MoyaProvider 中使用 MoyaMapperPlugin 插件,并指定 ModelableParameterType

let lxfNetTool = MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter())])
复制代码

:exclamation: 使用 MoyaMapperPlugin 插件是整个 MoyaMapper 的核心所在!

二、Model声明

Model 需遵守 Modelable 协议

MoyaMapper
Model

1、一般情况下如下写法即可

struct CompanyModel: Modelable {
    
    var name : String = ""
    var catchPhrase : String = ""
    
    init() { }
}
复制代码

2、如果自定义映射,则可以实现方法 mutating func mapping(_ json: JSON)

struct CompanyModel: Modelable {
    
    var name : String = ""
    var catchPhrase : String = ""
    
    init() { }
    mutating func mapping(_ json: JSON) {
        self.name = json["nickname"].stringValue
    }
}
复制代码

3、支持模型嵌套

struct UserModel: Modelable {
    
    var id : String = ""
    var name : String = ""
    var company : CompanyModel = CompanyModel()
    
    init() { }
}
复制代码

三、Response 解析

1、以下示例皆使用了 MoyaMapperPlugin ,所以不需要指定 解析路径

2、如果没有使用 MoyaMapperPlugin 则需要指定 解析路径 ,否则无法正常解析

ps: 解析路径 可以使用 a>b 这种形式来解决多级路径的问题

如果接口请求后 json 的数据结构与下图类似,则使用 MoyaMapper 是最合适不过了

Swift 掌控Moya的网络请求、数据解析与缓存
// Normal
let model = response.mapObject(MMModel.self)
print("name -- \(model.name)")
print("github -- \(model.github)")

// 打印json
print(response.fetchJSONString())

// Rx
rxRequest.mapObject(MMModel.self)
    .subscribe(onSuccess: { (model) in
        print("name -- \(model.name)")
        print("github -- \(model.github)")
    }).disposed(by: disposeBag)
复制代码

附: fetchJSONString的详细使用

Swift 掌控Moya的网络请求、数据解析与缓存
// Normal
let models = response.mapArray(MMModel.self)
let name = models[0].name
print("count -- \(models.count)")
print("name -- \(name)")

// 打印 json 模型数组中第一个的name
print(response.fetchString(keys: [0, "name"]))

// Rx
rxRequest.mapArray(MMModel.self)
    .subscribe(onSuccess: { models in
        let name = models[0].name
        print("count -- \(models.count)")
        print("name -- \(name)")
    }).disposed(by: disposeBag)
复制代码

附: mapArray的详细使用说明

Swift 掌控Moya的网络请求、数据解析与缓存
// Normal
let (isSuccess, tipStr) = response.mapResult()
print("isSuccess -- \(isSuccess)")
print("tipStr -- \(tipStr)")

// Rx
rxRequest.mapResult()
    .subscribe(onSuccess: { (isSuccess, tipStr) in
        print("isSuccess -- \(isSuccess)") // 是否为 "000"
        print("retMsg -- \(retMsg)") // "缺少必要参数"
    }).disposed(by: disposeBag)
复制代码

附: mapResult的详细使用说明

统一处理网络请求结果

在APP的实际使用过程中,会遇到各种各样的网络请求结果,如:服务器挂了、手机无网络,此时 Moya 返回的 Response 为 nil,这样我们就不得不去判断 Error 。但是使用 MoyaMapperPlugin 就可以让我们只关注 Response

// MoyaMapperPlugin 的初始化方法
public init<T: ModelableParameterType>(
    _ type: T,
    transformError: Bool = true
)

type : ModelableParameterType  用于定义字段路径,做为全局解析数据的依据
transformError : Bool  是否当网络请求失败时,自动转换请求结果,默认为 true
复制代码
  • 当请求失败的时候,此时的 result.responsenil ,根据 transformError 是否为 true 判断是否创建一个自定义的 response 并返回出去。

➡ 本来可以请求到的数据内容

Swift 掌控Moya的网络请求、数据解析与缓存

➡ 现在关闭网络,再请求数据

  • 正常情况下,即不做任何不处理的时候, Responsenil

  • 经过 MoyaMapperPlugin 处理的后可得到转换后的 Response ,如图

Swift 掌控Moya的网络请求、数据解析与缓存

这里将请求失败进行了统一处理,无论是服务器还是自身网络的问题, retStatus 都为 MMStatusCode.loadFail,但是 errorDescription 会保持原来的样子并赋值给 retMsg

  • retStatus 值会从枚举 MMStatusCode 中取 loadFail.rawValue ,即 700
  • 取 类型为 ModelableParameterTypetypestatusCodeKey 所指定的值 为键名, retMsg 也同理

ps: 这个时候可以通过判断 retStatusresponse.statusCode 是否与 MMStatusCode.loadFail.rawValue 相同来判断是否显示加载失败的空白页占位图

enum MMStatusCode: Int {
    case cache = 230
    case loadFail = 700
}
复制代码

枚举 MMStatusCode 中除了 loadFail ,还有 cache ,我们已经知道 loadFail 在数据加载失败的时候会出现,那 cache 是在什么时候出来呢?不急,看下一节就知道了。

数据缓存

一、基本使用

// 缓存
@discardableResult
MMCache.shared.cache`XXX`(value : XXX, key: String, cacheContainer: MMCache.CacheContainer = .RAM)  -> Bool
// 取舍
MMCache.shared.fetch`XXX`Cache(key: String, cacheContainer: MMCache.CacheContainer = .RAM)
复制代码

缓存成功会返回一个 Bool 值,这里可不接收

XXX 所支持类型
Bool -
Float -
Double -
String -
JSON -
Modelable [Modelable]
Moya.Response -
Int UInt
Int8 UInt8
Int16 UInt16
Int32 UInt32
Int64 UInt64

其中,除了 Moya.Response 之外,其它类型皆是通过 JSON 来实现缓存

所以,如果你想清除这些类型的缓存,只需要调用如下方法即可

@discardableResult
func removeJSONCache(_ key: String, cacheContainer: MMCache.CacheContainer = .RAM) -> Bool

@discardableResult
func removeAllJSONCache(cacheContainer: MMCache.CacheContainer = .RAM) -> Bool
复制代码

清除 Moya.Response 则使用如下两个方法

@discardableResult
func removeResponseCache(_ key: String) -> Bool

@discardableResult
func removeAllResponseCache() -> Bool
复制代码

再来看看MMCache.CacheContainer

enum CacheContainer {
    case RAM 	// 只缓存于内存的容器
    case hybrid // 缓存于内存与磁盘的容器
}
复制代码

这两种容器互不相通,即 即使key相同,使用 hybrid 来缓存后,再通过 RAM 取值是取不到的。

  • RAM : 仅缓存于内存之中,缓存的数据在APP使用期间一直存在
  • hybrid :缓存于内存与磁盘中,APP重启后也可以获取到数据

二、缓存网络请求

内部缓存过程:

  1. APP首次启动并进行网络请求,网络数据将缓存起来
  2. APP再次启动并进行网络请求时,会先返回缓存的数据,等请求成功后再返回网络数据
  3. 其它情况只会加载网络数据
  4. 每次成功请求到数据后,都会对缓存的数据进行更新
// Normal
func cacheRequest(
    _ target: Target, 
    cacheType: MMCache.CacheKeyType = .default, 
    callbackQueue: DispatchQueue? = nil, 
    progress: Moya.ProgressBlock? = nil, 
    completion: @escaping Moya.Completion
) -> Cancellable

// Rx
func cacheRequest(
    _ target: Base.Target, 
    callbackQueue: DispatchQueue? = nil, 
    cacheType: MMCache.CacheKeyType = .default
) -> Observable<Response>
复制代码

实际上是对 Moya 请求后的 Response 进行缓存。 其实与 Moya 自带的方法相比较只多了一个参数 cacheType: MMCache.CacheKeyType ,定义着缓存中的 key ,默认为 default

下面是 MMCache.CacheKeyType 的定义

/**
 let cacheKey = [method]baseURL/path
 
 - default : cacheKey + "?" + parameters
 - base : cacheKey
 - custom : cacheKey + "?" + customKey
 */
public enum CacheKeyType {
    case `default`
    case base
    case custom(String)
}
复制代码

如果你想缓存 多页 列表数据的 最新一页 数据,此时使用 default 是不合适的,因为 default 使用的 key 包含了 pageIndex ,这样就达不到只缓存 最新一页数据 的目的, 所以这里应该使用 base 或者 custom(String)

我们可以来试一下带缓存的请求

/*
 * APP第一次启动并进行网络请求,网络数据将缓存起来
 * APP再次启动并进行网络请求时,会先加载缓存,再加载网络数据
 * 其它情况只会加载网络数据
 * 每次成功请求到数据都会进行数据更新
 */
lxfNetTool.rx.cacheRequest(.data(type: .all, size: 10, index: 1))
    .subscribe(onNext: { response in
        log.debug("statusCode -- \(response.statusCode)")
    }).disposed(by: disposeBag)

// 传统方式
/*
let _ = lxfNetTool.cacheRequest(.data(type: .all, size: 10, index: 1)) { result in
    guard let resp = result.value else { return }
    log.debug("statusCode -- \(resp.statusCode)")
}
*/
复制代码

打印结果

// 首次使用APP
statusCode -- 200

// 关闭并重新打开APP,再请求一下
statusCode -- 230
statusCode -- 200

// 然后再请求一下
statusCode -- 200
复制代码

这里的 230 就是 MMStatusCode.cache.rawValue


以上所述就是小编给大家介绍的《Swift 掌控Moya的网络请求、数据解析与缓存》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Think Python

Think Python

Allen B. Downey / O'Reilly Media / 2012-8-23 / GBP 29.99

Think Python is an introduction to Python programming for students with no programming experience. It starts with the most basic concepts of programming, and is carefully designed to define all terms ......一起来看看 《Think Python》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具