iOS/Swift: Good architecture approach for connecting REST APIs
我开发iOS应用程序已经很长时间了。但最终我对我的网络层的体系结构设计并不满意。尤其是在连接API时。
这里可能有一个副本,但我认为我的问题更具体,如您所见。
构建iOS网络应用程序的最佳体系结构方法(REST客户端)
我并不是在寻找"使用网络/Alamoire"之类的答案。这个问题与使用哪种第三方框架无关。
我的意思是,我们经常会遇到这样的情况:
"Develop an app X that uses API Y"
这主要包括相同的步骤——每次。
一个问题3)
在obj-c中,我使用
关于4)
如何处理HTTP状态代码?每一个错误都使用许多不同的类吗?您是否将一般错误处理集中在一个类中?您是在同一级别处理它们,还是较早地捕获服务器错误?(可能在任何第三方库的API包装中)
开发人员是如何解决这个问题的?你想出了一个"最佳匹配"的设计方案吗?如何测试API?尤其是你如何迅速做到这一点(没有真正的嘲弄可能性?).
当然:每个用例、每个应用程序、每个场景都是不同的——没有"一个解决方案适合所有人"。但我认为这些一般性的问题经常出现,所以我想说"是的,对于这些情况,可能有一个或多个解决方案,你每次都可以重用"。
期待有趣的答案!
干杯奥兰多??
But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
我正在使用一些层对API进行身份验证。
身份验证管理器此管理器负责所有与身份验证相关的功能。您可以考虑验证、重置密码、重新发送验证代码等功能。
1 2 3 4 5 6 7 8 9 | struct AuthenticationManager { static func authenticate(username:String!, password:String!) -> Promise<Void> { let request = TokenRequest(username: username, password: password) return TokenManager.requestToken(request: request) } } |
为了请求令牌,我们需要一个称为令牌管理器的新层,它管理与令牌相关的所有事情。
令牌管理器1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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) } } |
为了请求一个令牌,我们需要一个称为令牌服务的第三层来处理所有HTTP调用。我对API调用使用evreflection和promises。
令牌服务1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(或您想要截取的任何代码),并在将响应返回给用户之前修复这些错误。使用这种方法,所有东西都由这个层处理,您不再需要担心一个无效的令牌了。
In Obj-C I used NSProxy for intercepting every API Call before it was send, re-authenticated the user if the token expired and and fired the actual request. In Swift we had some NSOperationQueue where we queued an auth call if we got a 401 and queued the actual request after successful refresh. But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 } } } } |
网络服务
The last part will be the network-service. In this service layer we will do all interactor-like code. All business logic will end up here, anything related to networking. If you briefly review this service you'll note that there is no UI-logic in here, and that's for a reason.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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请求。我将向您展示如何使用上面描述的体系结构来完成这项工作。
1 2 3 4 5 6 7 8 9 | AuthenticationManager.authenticate(username: username, password: password).then { (result) -> Void in // your logic }.catch { (error) in // Handle errors } |
处理错误始终是一项棘手的任务。每个开发人员都有自己的方法来实现这一点。在网上有很多关于错误处理的文章,例如swift。显示我的错误处理不会有什么帮助,因为这只是我个人的方法,在这个答案中发布的代码也很多,所以我宁愿跳过这个。
总之…
我希望我能帮助你回到正轨。如果有关于这个架构的任何问题,我将非常乐意帮助您解决这个问题。在我看来,没有完美的体系结构,也没有可以应用于所有项目的体系结构。
这是你的团队中的偏好、项目要求和专业知识的问题。
祝您好运,如有任何问题,请随时与我联系!