LoginPage 界面的工作终于弄完了,终于到写逻辑的地步了。真的是不容易,学的过程中一步一个坑。
我们新建一个 Api 的文件夹用于存放我们工程所有用到的接口,新建一个 UserLoginApi.swift 的接口文件。
swift
struct UserLoginApi {
let userName:String
let password:String
}
extension UserLoginApi: APIConfig {
var path: String { "/api/winplus/mobile/auth/signIn" }
var method: HTTPMethod {.post}
var parameters: [String : Any]? {
[
"userName": userName,
"password": password
]
}
}
解析用户信息
为了解析我们请求回来的数据,我们新建一个 Model文件夹,存放我们需要解析的模型。我们随后新建一个 UserLoginResponse.swift 文件。
swift
/// UserLoginResponse.swift
struct UserLoginResponse: Codable {
/// 代表用户唯一标识符
let gatwayUserName:String?
/// 用户信息
let user:UserInfoModel?
}
swift
/// UserInfoModel.Swift
struct UserInfoModel: Codable {
/// 用户名称
let userName:String?
}
为了可以解析我们的数据,我们新建一个在Common 文件夹新建一个 BaseModel.swift。
swift
/// BaseModel.swift
struct BaseModel<T: Codable> {
/// 接口返回信息
let message:String?
/// 接口 code
let code:Int
/// 接口是否成功
let success:Bool
/// 数据源
let data:T?
}
extension BaseModel: Model {
var _isSuccess: Bool {isSuccess}
var _code: Int {code}
var _message: String {message ?? ""}
}
封装接口请求
我们为了其他人调用刚才的 UserLoginApi 接口方便,我们做一个封装。
swift
extension Api {
static func userLogin(api:UserLoginApi) async -> BaseModel<UserLoginResponse> {
do {
return try await request(type: BaseModel<UserLoginResponse>.self,
config: api)
} catch (let e) {
let error = e as NSError
return BaseModel(message: nil,
code: error.code,
isSuccess: false,
data: nil)
}
}
}
但是这段代码看起来,感觉不是很简洁,假设我们有100个接口,每次都有很多重复封装失败的 BaseModel的,岂不是用起来很麻烦。
我们先给 APIConfig 封装一个通用解析的代码,用于方便调用。
swift
/// Api.swift
extension APIConfig {
func request<M:Codable>(model:M.Type) async -> BaseModel<M> {
do {
return try await Api.request(type: BaseModel<M>.self, config: self)
} catch(let e) {
let error = e as NSError
return BaseModel(message: nil,
code: error.code,
isSuccess: false,
data: nil)
}
}
}
那么我们的 UserLoginApi 的扩展方法可以修改为
swift
/// UserLoginApi.swift
extension Api {
static func userLogin(api:UserLoginApi) async -> BaseModel<UserLoginResponse> {
await api.request(model: UserLoginResponse.self)
}
}
这样调用起来是不是很方便了。
await 调用 async 异步方法
我们完善一下LoginPageViewModel的登陆逻辑,看看我们的接口是否可以调试通过。
swift
func login() async {
let api = UserLoginApi(userName: userName, password: password)
let model = await Api.userLogin(api: api)
}
Task 同步方法执行 async 方法
将 LoginPage 的登陆按钮执行 LoginPageViewModel 的 Login 方法。
swift
Task {
await viewModel.login()
}
因为按钮的点击方法是同步的,调用不了我们的 async 的方法,我们需要通过 Task 去运行。
开启允许 HTTP 请求
经过测试,请求失败,打印出下面日志。
shell
2021-11-22 17:21:01.436782+0800 Win+[71044:731637] App Transport Security has blocked a cleartext HTTP connection since it is insecure. Use HTTPS instead or add Exception Domains to your app's Info.plist.
因为新建的工程都是默认不支持 HTTP 请求的,我们需要修改一下 Info.plist 文件。
swift
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
添加完毕之后,我们再次重试一下我们的登录功能。但是我们的接口报 502 错误,关于 502 错误一般是网关开代理缘故。
我们确实开了代理,关闭代理,请求成功。
swift
{"gateway_user_name":"admin","user":{"id":1,"userName":"admin","mobile":"13588667361","account":"admin","orgCode":"0000","accountType":0,"email":null,"password":null}}}
JSONDecoder 设置 keyDecodingStrategy 解析转义
我们看一下数据源,解析 gatwayUserName 但是返回了 gateway_user_name。这个和我们的不一致,对于 JsonDecoder 可以设置解析类型。
swift
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
但是我们请求 Api 的地方没有自持自定义的 JSONDecoder 的地方,那么我们就需要修改我们的 Request 库来支持我们的功能。
swift
/// APIConfig.swift
/// 解析器
var decoder: JSONDecoder {get}
public extension APIConfig {
var decoder: JSONDecoder { CleanJSONDecoder() }
}
我们发布一个小版本,更新一下我们的 Request 库。
我们重写 UserLoginApi 的 decoder 属性。
swift
var decoder: JSONDecoder {
let decoder = CleanJSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}
到此为止,我们的登陆接口算是调试通过了。