我正在开发iOS应用程序已有很长一段时间了。但最后我对网络层的架构设计从不满意。特别是当它连接API时。
这里可能存在重复,但我认为我的问题更多是具体的,你会看到。
Best architectural approaches for building iOS networking applications (REST clients)
我不是在寻找像#34;使用AFNetworking / Alamofire"这样的答案。这个问题与使用哪个第三方框架无关。
我的意思是,我们经常有这样的情景:
"开发一个使用API Y"
的应用X.
这主要包括相同的步骤 - 每次。
3)
的一个问题在Obj-C中,我使用NSProxy
在发送之前拦截每个API调用,如果令牌过期则重新验证用户并触发实际请求。
在Swift中,我们有一些NSOperationQueue
,如果我们得到一个401并且在成功刷新后将实际请求排队,我们将auth调用排队。但这限制了我们使用Singleton(我不太喜欢),我们还必须将并发请求限制为1。
我更喜欢第二种方法 - 但是有更好的解决方案吗?
关于 4)
您如何处理http状态代码?每个错误都使用许多不同的类吗?您是否将一般错误处理集中在一个类中?您是在同一级别处理它们还是更早地捕获服务器错误? (可能在任何第三方库的API包装中)
您是如何开发人员尝试解决此问题的?你有没有找到最好的比赛"设计? 你如何测试你的API?特别是你如何在Swift中做到这一点(没有真正的嘲弄可能性?)。
当然:每个用例,每个应用,每个场景都是不同的 - 没有"一个解决方案适合他们所有"。但我认为这些一般性问题经常出现,所以我很想说"是的,对于这些情况 - 可能有一个或多个解决方案 - 你可以每次都重复使用"。
期待有趣的答案!
干杯
奥兰多
答案 0 :(得分:2)
但这限制了我们使用Singleton(我不太喜欢),我们还必须将并发请求限制为1.我更喜欢第二种方法 - 但是有更好的解决方案吗?
我使用几层来验证API。
此经理负责所有与身份验证相关的功能。您可以考虑身份验证,重置密码,重新发送验证码功能等。
struct AuthenticationManager
{
static func authenticate(username:String!, password:String!) -> Promise<Void>
{
let request = TokenRequest(username: username, password: password)
return TokenManager.requestToken(request: request)
}
}
为了请求令牌,我们需要一个名为TokenManager的新图层,它管理与token相关的所有内容。
struct TokenManager
{
private static var userDefaults = UserDefaults.standard
private static var tokenKey = CONSTANTS.userDefaults.tokenKey
static var date = Date()
static var token:Token?
{
guard let tokenDict = userDefaults.dictionary(forKey: tokenKey) else { return nil }
let token = Token.instance(dictionary: tokenDict as NSDictionary)
return token
}
static var tokenExist: Bool { return token != nil }
static var tokenIsValid: Bool
{
if let expiringDate = userDefaults.value(forKey: "EXPIRING_DATE") as? Date
{
if date >= expiringDate
{
return false
}else{
return true
}
}
return true
}
static func requestToken(request: TokenRequest) -> Promise<Void>
{
return Promise { fulFill, reject in
TokenService.requestToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
let today = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)
userDefaults.setValue(tomorrow, forKey: "EXPIRING_DATE")
fulFill()
}.catch { error in
reject(error)
}
}
}
static func refreshToken() -> Promise<Void>
{
return Promise { fulFill, reject in
guard let token = token else { return }
let request = TokenRefresh(refreshToken: token.refreshToken)
TokenService.refreshToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
fulFill()
}.catch { error in
reject(error)
}
}
}
private static func setToken (token:Token!)
{
userDefaults.setValue(token.toDictionary(), forKey: tokenKey)
}
static func deleteToken()
{
userDefaults.removeObject(forKey: tokenKey)
}
}
为了请求令牌,我们需要一个名为TokenService的第三层来处理所有HTTP调用。我为我的API调用使用EVReflection和Promises。
struct TokenService: NetworkService
{
static func requestToken (request: TokenRequest) -> Promise<Token> { return POST(request: request) }
static func refreshToken (request: TokenRefresh) -> Promise<Token> { return POST(request: request) }
// MARK: - POST
private static func POST<T:EVReflectable>(request: T) -> Promise<Token>
{
let headers = ["Content-Type": "application/x-www-form-urlencoded"]
let parameters = request.toDictionary(.DefaultDeserialize) as! [String : AnyObject]
return POST(URL: URLS.auth.token, parameters: parameters, headers: headers, encoding: URLEncoding.default)
}
}
我正在使用授权服务来处理您在此处描述的问题。此层负责拦截服务器错误,例如401(或您要拦截的任何代码),并在将响应返回给用户之前修复它们。使用这种方法,所有内容都由此图层处理,您不必再担心无效令牌。
在Obj-C中,我使用NSProxy在发送之前拦截每个API调用,如果令牌过期则重新验证用户并触发实际请求。在Swift中,我们有一些NSOperationQueue,如果我们得到一个401并在成功刷新后将实际请求排队,我们将auth调用排队。但这限制了我们使用Singleton(我不太喜欢),我们还必须将并发请求限制为1.我更喜欢第二种方法 - 但是有更好的解决方案吗?
struct AuthorizationService: NetworkService
{
private static var authorizedHeader:[String: String]
{
guard let accessToken = TokenManager.token?.accessToken else
{
return ["Authorization": ""]
}
return ["Authorization": "Bearer \(accessToken)"]
}
// MARK: - POST
static func POST<T:EVObject> (URL: String, parameters: [String: AnyObject], encoding: ParameterEncoding) -> Promise<T>
{
return firstly
{
return POST(URL: URL, parameters: parameters, headers: authorizedHeader, encoding: encoding)
}.catch { error in
switch ((error as NSError).code)
{
case 401:
_ = TokenManager.refreshToken().then { return POST(URL: URL, parameters: parameters, encoding: encoding) }
default: break
}
}
}
}
最后一部分是network-service。在此服务层中,我们将执行所有类似于交互器的代码。所有业务逻辑都将在这里结束,与网络相关的任何事情。如果你简要回顾一下这项服务,你会注意到这里没有用户界面逻辑,这是有原因的。
protocol NetworkService
{
static func POST<T:EVObject>(URL: String, parameters: [String: AnyObject]?, headers: [String: String]?, encoding: ParameterEncoding) -> Promise<T>
}
extension NetworkService
{
// MARK: - POST
static func POST<T:EVObject>(URL: String,
parameters: [String: AnyObject]? = nil,
headers: [String: String]? = nil, encoding: ParameterEncoding) -> Promise<T>
{
return Alamofire.request(URL,
method: .post,
parameters: parameters,
encoding: encoding,
headers: headers).responseObject()
}
}
此体系结构的示例实现是用于登录用户的身份验证HTTP请求。我将使用上述架构向您展示如何完成此操作。
AuthenticationManager.authenticate(username: username, password: password).then { (result) -> Void in
// your logic
}.catch { (error) in
// Handle errors
}
处理错误始终是一项混乱的任务。每个开发人员都有自己的方式来做到这一点。在网上有大量关于错误处理的文章,例如swift。显示我的错误处理将没有多大帮助,因为它只是我个人的方式,它也是很多代码在这个答案中发布,所以我宁愿跳过它。
总之...
我希望通过这种方法帮助您重回正轨。如果对这个架构有任何疑问,我将非常乐意帮助您解决这个问题。在我看来,没有完美的架构,也没有可以应用于所有项目的架构。
这是团队内部的偏好,项目要求和专业知识。
祝您好运,如果有任何问题,请随时与我联系!