第十五章 Task|NSAppTransportSecurity|keyDecodingStrategy

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 的登陆按钮执行 LoginPageViewModelLogin 方法。

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

我们重写 UserLoginApidecoder 属性。

swift 复制代码
var decoder: JSONDecoder {
    let decoder = CleanJSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    return decoder
}

到此为止,我们的登陆接口算是调试通过了。

相关推荐
君赏4 小时前
第十八章 封装HUD和完善登录界面逻辑
swiftui
君赏4 小时前
第十四章 async/await|overlay|PreferencrKey|Anchor
swiftui
君赏4 小时前
第十三章 Button|cornerRadius
swiftui
君赏4 小时前
第六章 Published|ObservedObject|EnvironmentObject|Environment
swiftui
君赏4 小时前
第八章 封装MVVM|onTapGesture|AppStorage
swiftui
君赏4 小时前
第十二章 TextField|EmptyView|SecureField
swiftui
君赏4 小时前
第四章 Preview Device|Expand|Alignment|LineLimit|Rectangle|ForegroundColor
swiftui
君赏4 小时前
第七章 组件提炼|代码清爽|Padding
swiftui
君赏4 小时前
第五章 如何使用Xcode Package Injection加速依赖Swift Package Manager
swiftui