Swift 开发 wanandroid 客户端——网络请求模块编写

1,098 阅读5分钟

这是我参与更文挑战的第17天,活动详情查看: 更文挑战

前面一些简单总结:

前面花了些篇幅介绍以下几点:

  • 项目需求开始了,iOS开发应该提前做好的准备知识。

  • 介绍了一些基础库的用法:R.Swift、SnapKit、Alamofire、Moya。

  • 分享了我自己的JSON解析经历:直接从OC搬过来的生搬硬套,到使用ObjectMapper,最后使用原生的Codable协议。

  • 插播了Swift与JS交互、Flutter与JS交互。

如果不算插播的与JS的交互,我已经对App的资源管理、UI布局、网络请求做了一定的铺垫与讲解,虽然与JS交互在这次App编写中没有用,不过总有会用上的地方。

其实这个项目,我是使用RxSwift编写的,对于RxSwift也算是新手入门,边写边学习,所以这一块会在写App的时候,边写边探索,所以如果有什么不对和不好的地方,还请各位大佬指点。

好了,既然前面已经花费了这么多笔墨,那么就开始今天的主题吧——网络请求模块的编写。

网络请求模块编写前的注意事项

由于本次我们编写的App是其他网站的开发API,所以在网站上已经有非常不错的接口文档了:

玩Android 开放API

作为App开发人员,在开始编写网络请求模块的注意事项

  • 通读后端给你的接口文档。

  • 了解每个接口的请求方式,get还是post还是其他。

  • JSON的格式观察,通过观察,可以总结发现规律,编写带有泛型的BaseModel,同时灵活进行复用。

  • 如果可以,可以通过Postman这样的工具进行简单的调试工作。

玩安卓中项目相关接口例子:

玩安卓的接口还是不少的,受限于篇幅与接口的相似性,我这里仅将项目的两个接口进行分析与编码。

项目

  • 项目分类
https://www.wanandroid.com/project/tree/json

方法: GET
参数: 无

JSON样例:

{
    "data":[
        {
            "children":[

            ],
            "courseId":13,
            "id":294,
            "name":"完整项目",
            "order":145000,
            "parentChapterId":293,
            "userControlSetTop":false,
            "visible":0
        },
    ],
    "errorCode":0,
    "errorMsg":""
}
  • 项目列表数据
https://www.wanandroid.com/project/list/1/json?cid=294

方法:GET
参数:
cid 分类的id,项目分类接口中获取的data数组中单个元素的id
页码:拼接在链接中,从1开始。

JSON样例:

{
    "data":{
        "curPage":1,
        "datas":[
            {
                "apkLink":"",
                "audit":1,
                "author":"wo5813288",
                "canEdit":false,
                "chapterId":294,
                "chapterName":"完整项目",
                "collect":false,
                "courseId":13,
                "desc":"更新学习flutter,所以系统的做一款应用来实践一下。这款应用也开发了很多内容了,后续还要继续更新功能。开发这个项目主要也是熟悉flutter的树形结构的写法和UI组件,项目中也用到了flutter比较流行的第三方框架。",
                "descMd":"",
                "envelopePic":"https://www.wanandroid.com/blogimgs/e092cd25-3e43-42c4-a7eb-b1ebc60ce02a.png",
                "fresh":true,
                "host":"",
                "id":18624,
                "link":"https://www.wanandroid.com/blog/show/3020",
                "niceDate":"10小时前",
                "niceShareDate":"10小时前",
                "origin":"",
                "prefix":"",
                "projectLink":"https://github.com/wo5813288/wan_giao",
                "publishTime":1623768707000,
                "realSuperChapterId":293,
                "selfVisible":0,
                "shareDate":1623768707000,
                "shareUser":"",
                "superChapterId":294,
                "superChapterName":"开源项目主Tab",
                "tags":[
                    {
                        "name":"项目",
                        "url":"/project/list/1?cid=294"
                    }
                ],
                "title":"Flutter开发的WanAndroid",
                "type":0,
                "userId":-1,
                "visible":1,
                "zan":0
            }
        ],
        "offset":0,
        "over":false,
        "pageCount":18,
        "size":15,
        "total":261
    },
    "errorCode":0,
    "errorMsg":""
}

通过上面的两个接口与JSON的样例我们可以得出以下的结论:

    1. baseUrl确定为https://www.wanandroid.com/
    1. 两个接口:project/tree/jsonproject/list/页面数/json?cid=传入的id
    1. JSON的基本通用范本:
{
    "data":业务具体数据
    "errorCode":0,
    "errorMsg":""
}

于是我们可以在代码里面新建一个Api.swift文件,整理好相关Api:

struct Api {
    /// baseUrl
    static let baseUrl = "https://www.wanandroid.com/"
    
    /// 私有初始化方法,避免被实例化
    private init() {}

    /// 项目 均是get请求
    enum Project {
        static let tags = "project/tree/json"
        
        static let tagList = "project/list/"
    }
}

通过Moya来定义Project服务

import Foundation

import Moya

enum ProjectService {
    case tags
    /// 枚举带参,传递id与page页码
    case tagList(_ id: Int, _ page: Int)
}

extension ProjectService: TargetType {
    var baseURL: URL {
        return URL(string: Api.baseUrl)!
    }
    
    var path: String {
        switch self {
        case .tags:
            return Api.Project.tags
        case .tagList(_, let page):
            return Api.Project.tagList + page.toString + "/json"
        }
    }
    
    var method: Moya.Method {
        return .get
    }
    
    var sampleData: Data {
        return Data()
    }
    
    var task: Task {
        switch self {
        case .tags:
            return .requestParameters(parameters: Dictionary.empty, encoding: URLEncoding.default)
        case .tagList(let id, _):
            return .requestParameters(parameters: ["cid": id.toString], encoding: URLEncoding.default)
        }
        
    }
    
    var headers: [String : String]? {
        return nil
    }
}

在上面的代码中,我简单的使用了Int与String的两个分类,也附上:

extension Int {
    var toString: String { "\(self)" }
}

extension Dictionary {
    static var empty: Dictionary { [:] }
}

最后编写一个独立的Provider:

let projectProvider: MoyaProvider<ProjectService> = {
    let stubClosure = { (target: ProjectService) -> StubBehavior in
        return .never
    }
    return MoyaProvider<ProjectService>(stubClosure: stubClosure, plugins: [RequestLoadingPlugin()])
}()

RequestLoadingPlugin我在介绍Moya时已经提到过,是一个插件,这里就不多说了。

根据JSON的格式,写出一个基础的BaseModel:

玩安卓后台返回JSON的基础格式,后面不同的业务只是data中的值不同罢了:

import Foundation

struct BaseModel<T: Codable>: Codable {
    /// T为具体业务数据,遵守Codable协议
    let data : T?
    let errorCode : Int?
    let errorMsg : String?
}

Moya的基本调用:

这里编写Moya的原生调用,并没有使用RxMoya,请求返回的是Result<Response, MoyaError>类型,在成功的结果中我们可以拿到response,通过response.data获取数据的Swift.Data类型,再通过Codable协议转为对应的模型。

projectProvider.request(ProjectService.tags) { (result: Result<Response, MoyaError>) in
    switch result {
    case .success(let response):
        let data = response.data
    case .failure(let error):
        break
    }
}

总结

本篇说明了写网络请求模块的注意事项。

通过举例玩安卓项目的两个接口,编写了Api、Moya服务和BaseModel,而后续的其他的接口都可以依葫芦画瓢进行编写。

原生Moya调用并不能直接转基于Codable的Model,但是其Moya.Response中有扩展方法可以做转换。后续会通过RxMoya进行优化。

明日继续

下一篇,我会通过玩安卓中的一些模型,抽象一些基本的Model。基本上到了边写代码边总结文章的时候了。

大家加油!