Ответ 1
Но это ограничило нас использованием Singleton (чего мне не очень нравится), и нам также пришлось ограничить параллельные запросы на 1. Мне больше нравится второй подход - но есть ли лучшее решение?
Я использую несколько уровней для аутентификации с помощью API.
Менеджер проверки подлинности
Этот менеджер отвечает за все функции, связанные с проверкой подлинности. Вы можете подумать об аутентификации, reset password, повторить проверку кода кода и т.д.
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-вызовы. Я использую EVReflection и Promises для вызовов API.
Служба токена
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, где мы поставили в очередь вызов auth, если получили 401 и поставили в очередь фактический запрос после успешного обновления. Но это ограничило нас использованием 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. В этом слое сервиса мы сделаем все интерактивно-подобный код. Здесь все деловая логика, все, что связано с сетью. Если вы кратко просмотрите эту услугу, вы заметите, что здесь нет UI-логики, и это по какой-то причине.
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. Показ моей обработки ошибок будет не очень полезен, так как это мой личный способ сделать это, а также много кода для публикации в этом ответе, поэтому я скорее пропущу это.
В любом случае...
Надеюсь, я помог вам вернуться к этому пути. Если есть какие-либо вопросы относительно этой архитектуры, я буду более чем счастлив помочь вам в этом. На мой взгляд, нет идеальной архитектуры и нет архитектуры, которая может быть применена ко всем проектам.
Это вопрос предпочтений, требований проекта и опыта в вашей команде.
Желаем удачи и, пожалуйста, не стесняйтесь обращаться ко мне, если есть какие-либо проблемы!