Правильное использование URLRequestConvertible от Alamofire
Я прочитал пару учебников, README от @mattt, но не могу понять пару вещей.
что такое правильное использование
URLRequestConvertible
в реальном мире API? Похоже, если я создам один маршрутизатор, реализовавURLRequestConvertible
протокол для всех API - он будет едва читаемым. Должен ли я создать один маршрутизатор на конечную точку?
второй вопрос, скорее всего, вызван отсутствием опыта работы с языком Swift. Я не могу понять, почему это для построения маршрутизатора? Почему мы не используем класс со статическими методами?
вот пример (из README Alamofire)
enum Router: URLRequestConvertible {
static let baseURLString = "http://example.com"
static let perPage = 50
case Search(query: String, page: Int)
// MARK: URLRequestConvertible
var URLRequest: NSURLRequest {
let (path: String, parameters: [String: AnyObject]?) = {
switch self {
case .Search(let query, let page) where page > 1:
return ("/search", ["q": query, "offset": Router.perPage * page])
case .Search(let query, _):
return ("/search", ["q": query])
}
}()
let URL = NSURL(string: Router.baseURLString)!
let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(path))
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(URLRequest, parameters: parameters).0
}
}
есть 2 способа передачи параметров:
case CreateUser([String: AnyObject])
case ReadUser(String)
case UpdateUser(String, [String: AnyObject])
case DestroyUser(String)
и (скажем, пользователь имеет 4 параметра)
case CreateUser(String, String, String, String)
case ReadUser(String)
case UpdateUser(String, String, String, String, String)
case DestroyUser(String)
@mattt использует первый в Примере. Но это приведет к" жесткому кодированию " имен параметров вне маршрутизатора (например, в UIViewControllers).
Опечатка в имени параметра может привести к ошибке.
Другие люди используя 2-й вариант, но в таком случае совершенно не очевидно, что представляет собой каждый параметр.
Что будет правильный способ сделать это?
5 ответов:
большие вопросы. Давайте разберем каждый из них по отдельности.
как правильно использовать URLRequestConvertible в реальном мире API?
The
URLRequestConvertible
протокол-это легкий способ гарантировать, что данный объект может создать действительныйNSURLRequest
. На самом деле не существует строгого набора правил или рекомендаций, которые заставляют вас использовать этот протокол каким-либо конкретным образом. Это просто удобный протокол, позволяющий другим объектам хранить требуемое состояние чтобы правильно создатьNSURLRequest
. Более подробную информацию о Alamofire можно найти здесь.должен ли я создать один маршрутизатор на конечную точку?
наверняка нет. Что бы победить всю цель использования
Enum
. Объекты Swift Enum удивительно мощны, позволяя вам делиться большим количеством общего состояния и переключаться на части, которые на самом деле отличаются. Будучи в состоянии создатьNSURLRequest
С чем-то таким же простым, как следующее действительно мощно!let URLRequest: NSURLRequest = Router.ReadUser("cnoon")
я не могу понять, почему enum используется для построения маршрутизатора? Почему мы не используем класс со статическими методами?
перечисление используется, потому что это гораздо более краткий способ выражения нескольких связанных объектов в общем интерфейсе. Все методы являются общими для всех случаев. Если вы использовали статический метод, вы должны иметь статический метод для каждого случая для каждого метода. Или вам придется использовать перечисление в стиле Obj-C внутри объекта. Вот краткий пример того, что я имею в виду.
enum Router: URLRequestConvertible { static let baseURLString = "http://example.com" case CreateUser([String: AnyObject]) case ReadUser(String) case UpdateUser(String, [String: AnyObject]) case DestroyUser(String) var method: Alamofire.HTTPMethod { switch self { case .CreateUser: return .post case .ReadUser: return .get case .UpdateUser: return .put case .DestroyUser: return .delete } } var path: String { switch self { case .CreateUser: return "/users" case .ReadUser(let username): return "/users/\(username)" case .UpdateUser(let username, _): return "/users/\(username)" case .DestroyUser(let username): return "/users/\(username)" } } }
чтобы получить метод любой из различных конечных точек, вы можете вызвать один и тот же метод без необходимости передавать какие-либо параметры, чтобы определить, какой тип конечной точки вы ищете, он уже обрабатывается выбранным вами случаем.
let createUserMethod = Router.CreateUser.method let updateUserMethod = Router.UpdateUser.method
или если вы хотите получить путь, те же типы звонков.
let updateUserPath = Router.UpdateUser.path let destroyUserPath = Router.DestroyUser.path
теперь давайте попробуем тот же подход, используя статические методы.
struct Router: URLRequestConvertible { static let baseURLString = "http://example.com" static var method: Method { // how do I pick which endpoint? } static func methodForEndpoint(endpoint: String) -> Method { // but then I have to pass in the endpoint each time // what if I use the wrong key? // possible solution...use an Obj-C style enum without functions? // best solution, merge both concepts and bingo, Swift enums emerge } static var path: String { // bummer...I have the same problem in this method too. } static func pathForEndpoint(endpoint: String) -> String { // I guess I could pass the endpoint key again? } static var pathForCreateUser: String { // I've got it, let's just create individual properties for each type return "/create/user/path" } static var pathForUpdateUser: String { // this is going to get really repetitive for each case for each method return "/update/user/path" } // This approach gets sloppy pretty quickly }
Примечание: Если у вас не так много свойств или функций, которые включают случаи, то перечисление не дает много преимуществ перед структурой. Это просто альтернативный подход с различным синтаксическим сахаром.
перечисления могут максимизировать состояние и повторное использование кода. Связанные значения также позволяют вам делать некоторые действительно мощные вещи, такие как группировка объектов, которые несколько похожи, но имеют невероятно разные требования...такие как
NSURLRequest
создание.Как правильно построить параметры для случаев перечисления, чтобы улучшить читаемость? (пришлось помять это вместе)
это потрясающий вопрос. Вы уже изложили два возможных варианта. Позвольте мне добавить третий, который может удовлетворить ваши потребности лучше.
case CreateUser(username: String, firstName: String, lastName: String, email: String) case ReadUser(username: String) case UpdateUser(username: String, firstName: String, lastName: String, email: String) case DestroyUser(username: String)
в случаях, когда у вас есть связанные значения, я думаю, что может быть полезно добавить явные имена для всех значений в кортеже. Это действительно так помогает построить контекст. Недостатком является то, что вам нужно повторно объявить эти значения в своих операторах switch.
static var method: String { switch self { case let CreateUser(username: username, firstName: firstName, lastName: lastName, email: email): return "POST" default: return "GET" } }
хотя это дает вам хороший, последовательный контекст, он становится довольно многословным. Эти ваши три варианта на данный момент в Swift, который является правильным для использования, зависит от вашего варианта использования.
обновление
С выпуском Alamofire 4.0,
URLRequestConvertible
теперь может быть гораздо умнее, а также может бросить. Мы добавили полная поддержка в Alamofire для обработки недопустимых запросов и генерации разумных ошибок через обработчики ответов. Эта новая система подробно описана в нашей README.
почему бы вам не попробовать использовать SweetRouter. Это поможет вам удалить весь шаблон, который у вас есть при объявлении маршрутизатора, а также поддерживает такие вещи, как несколько сред, и ваш код будет действительно читаемым.
вот пример маршрутизатора со сладким маршрутизатор:
struct Api: EndpointType { enum Environment: EnvironmentType { case localhost case test case production var value: URL.Environment { switch self { case .localhost: return .localhost(8080) case .test: return .init(IP(126, 251, 20, 32)) case .production: return .init(.https, "myproductionserver.com", 3000) } } } enum Route: RouteType { case auth, me case posts(for: Date) var route: URL.Route { switch self { case .me: return .init(at: "me") case .auth: return .init(at: "auth") case let .posts(for: date): return URL.Route(at: "posts").query(("date", date), ("userId", "someId")) } } } static let current: Environment = .localhost }
и вот как бы вы его использовали:
Alamofire.request(Router<Api>(at: .me)) Alamofire.request(Router<Api>(.test, at: .auth)) Alamofire.request(Router<Api>(.production, at: .posts(for: Date())))
Я нашел способ работать с ним, я создал класс с маршрутизатором в нем: наследовать классы от запроса
запрос файл.Свифт
class request{ func login(user: String, password: String){ /*use Router.login(params)*/ } /*...*/ enum Router: URLRequestConvertible { static let baseURLString = "http://example.com" static let OAuthToken: String? case Login([String: AnyObject]) /*...*/ var method: Alamofire.Method { switch self { case .Login: return .POST /*...*/ } var path: String { switch self { case .Login: return "/login" /*...*/ } } var URLRequest: NSURLRequest { switch self { case .Login(let parameters): return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 /*...*/ default: return mutableURLRequest } } } }
файл requestContacts.Свифт
class requestContacts: api{ func getUser(id: String){ /*use Router.getUser(id)*/ } /*...*/ enum Router: URLRequestConvertible { case getUser(id: String) case setUser([String: AnyObject]) var method: Alamofire.Method { switch self { case .getUser: return .GET case .setUser: return .POST /*...*/ } } var path: String { switch self { case .getUser(id: String): return "/user\(id)/" case .setUser(id: String): return "/user/" /*...*/ } } // MARK: URLRequestConvertible var URLRequest: NSURLRequest { //use same baseURLString seted before let URL = NSURL(string: Router.baseURLString)! let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path)) mutableURLRequest.HTTPMethod = method.rawValue if let token = Router.OAuthToken { mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } switch self { /*...*/ case .setUser(let parameters): return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 default: //for GET methods, that doesent need more return mutableURLRequest } } } }
таким образом, класс son получит параметры маршрутизатора от родителя, и вы даже можете использовать маршрут.войдите в любой сын. тем не менее, не знаю, есть ли способ получить короткий URLRequest, поэтому мне не нужно устанавливать параметры снова и снова
вот до настоящего времени
enum Router
в Swift 3, который рекомендуется на Alamofire это на GitHub. Я надеюсь, что вы найдете его полезным с точки зрения того, как правильно реализовать маршрутизатор сURLRequestConvertible
.import Alamofire enum Router: URLRequestConvertible { case createUser(parameters: Parameters) case readUser(username: String) case updateUser(username: String, parameters: Parameters) case destroyUser(username: String) static let baseURLString = "https://example.com" var method: HTTPMethod { switch self { case .createUser: return .post case .readUser: return .get case .updateUser: return .put case .destroyUser: return .delete } } var path: String { switch self { case .createUser: return "/users" case .readUser(let username): return "/users/\(username)" case .updateUser(let username, _): return "/users/\(username)" case .destroyUser(let username): return "/users/\(username)" } } // MARK: URLRequestConvertible func asURLRequest() throws -> URLRequest { let url = try Router.baseURLString.asURL() var urlRequest = URLRequest(url: url.appendingPathComponent(path)) urlRequest.httpMethod = method.rawValue switch self { case .createUser(let parameters): urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters) case .updateUser(_, let parameters): urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters) default: break } return urlRequest } }
типы, принимающие протокол URLRequestConvertible, могут использоваться для построения запросов URL.
вот пример, взятый из www.raywenderlich.com
public enum ImaggaRouter : URLRequestConvertible{ static let baseURL = "http://api.imagga.com/v1" static let authenticationToken = "XAFDSADGDFSG DAFGDSFGL" case Content, Tags(String), Colors(String) public var URLRequest: NSMutableURLRequest { let result: (path: String, method: Alamofire.Method, parameters: [String: AnyObject]) = { switch self { case .Content: return ("/content", .POST, [String: AnyObject]()) case .Tags(let contentID): let params = [ "content" : contentID ] return ("/tagging", .GET, params) case .Colors(let contentID): let params = [ "content" : contentID, "extract_object_colors" : NSNumber(int: 0) ] return ("/colors", .GET, params) } }() let URL = NSURL(string: ImaggaRouter.baseURL)! let URLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(result.path)) URLRequest.HTTPMethod = result.method.rawValue URLRequest.setValue(ImaggaRouter.authenticationToken, forHTTPHeaderField: "Authorization") URLRequest.timeoutInterval = NSTimeInterval(10 * 1000) let encoding = Alamofire.ParameterEncoding.URL return encoding.encode(URLRequest, parameters: result.parameters).0 } }
и мы можем использовать этот ImmageRouter следующим образом:
Alamofire.request(ImaggaRouter.Tags(contentID)) .responseJSON{ response in
Comments