Шаблон Singleton и правильное использование Alamofire URLRequestConvertible

Это вопрос из двух частей, первый из которых похож на этот вопрос здесь: Правильное использование Alamofire URLRequestConvertible. Но мне нужно немного больше помощи!

1) Создаю ли я enum router, который реализует URLRequestConvertible для каждой модели на моем уровне модели?

На странице alamofire github приведен пример маршрутизатора, который я скопировал здесь:

  enum Router: URLRequestConvertible {
    static let baseURLString = "http://example.com"
    static var OAuthToken: String?

    case CreateUser([String: AnyObject])
    case ReadUser(String)
    case UpdateUser(String, [String: AnyObject])
    case DestroyUser(String)

    var method: Alamofire.Method {
        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

    var URLRequest: NSURLRequest {
        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 .CreateUser(let parameters):
            return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
        case .UpdateUser(_, let parameters):
            return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
        default:
            return mutableURLRequest
        }
    }
}

Когда я смотрю на это (я новичок в Swift, поэтому, пожалуйста, потерпите меня >_‹), я вижу операции над пользовательским объектом; они создают пользователя, обновляют пользователя и т. д. Итак, если бы у меня были объекты модели: человек, компания, местоположение на уровне модели, создал бы я маршрутизатор для каждого объекта модели?

2) При интенсивном взаимодействии с API я привык создавать синглтон «менеджер сети», чтобы абстрагироваться от сетевого уровня и хранить заголовки и базовый URL для этого API. У alamofire есть «Менеджер», описанный здесь:

Удобные методы верхнего уровня, такие как Alamofire.request, используют общий экземпляр Alamofire.Manager, для которого настроена конфигурация NSURLSessionConfiguration по умолчанию. Таким образом, следующие два утверждения эквивалентны:

Alamofire.request(.GET, "http://httpbin.org/get")

let manager = Alamofire.Manager.sharedInstance
manager.request(NSURLRequest(URL: NSURL(string: "http://httpbin.org/get")))

этот менеджер я должен использовать в качестве синглтона? Если да, то как мне установить baseurl в менеджере? Кроме того, если я использую этот менеджер, может ли он работать вместе с конструкцией маршрутизатора, показанной выше (с каждой настройкой объекта модели, это baseurl и NSURLRquest)? Если да, можете ли вы привести простой пример?

Я новичок в библиотеке Alamofire и быстро. Итак, я знаю, что в моем понимании много пробелов, но я просто пытаюсь понять как можно лучше! Любая информация помогает. Спасибо.


person SnoopyProtocol    schedule 08.07.2015    source источник


Ответы (1)


Это действительно хорошие вопросы. Попробую ответить на каждый по очереди.

Нужно ли создавать маршрутизатор enum, который реализует URLRequestConvertible для каждой модели на уровне моей модели?

Это отличный вопрос, и, к сожалению, на него нет идеального ответа. Конечно, есть несколько способов расширить шаблон Router для размещения нескольких типов объектов. Первый вариант — добавить больше случаев для поддержки другого типа объекта. Тем не менее, это становится довольно быстро, когда вы получаете более 6 или 7 случаев. Ваши операторы switch просто начинают выходить из-под контроля. Поэтому я бы не рекомендовал этот подход.

Другой способ решить эту проблему — ввести дженерики в файл Router.

Протокол RouterObject

protocol RouterObject {
    func createObjectPath() -> String
    func readObjectPath(identifier: String) -> String
    func updateObjectPath(identifier: String) -> String
    func destroyObjectPath(identifier: String) -> String
}

Объекты модели

struct User: RouterObject {
    let rootPath = "/users"

    func createObjectPath() -> String { return rootPath }
    func readObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
    func updateObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
    func destroyObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
}

struct Company: RouterObject {
    let rootPath = "/companies"

    func createObjectPath() -> String { return rootPath }
    func readObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
    func updateObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
    func destroyObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
}

struct Location: RouterObject {
    let rootPath = "/locations"

    func createObjectPath() -> String { return rootPath }
    func readObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
    func updateObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
    func destroyObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
}

Маршрутизатор

let baseURLString = "http://example.com"
var OAuthToken: String?

enum Router<T where T: RouterObject>: URLRequestConvertible {
    case CreateObject(T, [String: AnyObject])
    case ReadObject(T, String)
    case UpdateObject(T, String, [String: AnyObject])
    case DestroyObject(T, String)

    var method: Alamofire.Method {
        switch self {
        case .CreateObject:
            return .POST
        case .ReadObject:
            return .GET
        case .UpdateObject:
            return .PUT
        case .DestroyObject:
            return .DELETE
        }
    }

    var path: String {
        switch self {
        case .CreateObject(let object, _):
            return object.createObjectPath()
        case .ReadObject(let object, let identifier):
            return object.readObjectPath(identifier)
        case .UpdateObject(let object, let identifier, _):
            return object.updateObjectPath(identifier)
        case .DestroyObject(let object, let identifier):
            return object.destroyObjectPath(identifier)
        }
    }

    // MARK: URLRequestConvertible

    var URLRequest: NSMutableURLRequest {
        let URL = NSURL(string: baseURLString)!
        let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
        mutableURLRequest.HTTPMethod = method.rawValue

        if let token = OAuthToken {
            mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        switch self {
        case .CreateObject(_, let parameters):
            return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
        case .UpdateObject(_, _, let parameters):
            return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
        default:
            return mutableURLRequest
        }
    }
}

Пример использования

func exampleUsage() {
    let URLRequest = Router.CreateObject(Location(), ["address": "1234 Road of Awesomeness"]).URLRequest
    Alamofire.request(URLRequest)
        .response { request, response, data, error in
            print(request)
            print(response)
            print(data)
            print(error)
    }
}

Теперь, безусловно, есть несколько компромиссов, которые вы должны сделать здесь. Во-первых, объекты вашей модели должны соответствовать протоколу RouterObject. В противном случае Router понятия не имеет, что использовать для пути. Кроме того, вам нужно убедиться, что все ваши пути могут быть построены с помощью одного identifier. Если они не могут, этот дизайн может не работать. Последняя проблема заключается в том, что вы не можете хранить baseURL или OAuthToken непосредственно внутри перечисления Router. К сожалению, статические и хранимые свойства пока не поддерживаются в универсальных перечислениях.

Несмотря на это, это, безусловно, будет правильным способом избежать создания Router для каждого объекта модели.

Следует ли использовать Alamofire.Manager.sharedInstance в качестве экземпляра singleton NetworkManager?

Это, безусловно, можно было бы использовать таким образом. Это действительно зависит от вашего варианта использования и от того, как вы спроектировали доступ к сети. Это также зависит от того, сколько различных типов сеансов вам нужно. Если вам нужны фоновые сеансы и сеансы по умолчанию, вам, вероятно, все еще нужна концепция NetworkManager, которая содержит каждый пользовательский экземпляр Manager. Однако, если вы просто подключаетесь к сети с сеансом по умолчанию, то sharedInstance, вероятно, будет достаточно.

Как можно использовать baseURL синглтона Alamofire в сочетании с шаблоном Router?

Хороший вопрос... приведенный ниже код является одним из примеров того, как это можно сделать.

Расширение Alamofire Manager

extension Manager {
    static let baseURLString = "http://example.com"
    static var OAuthToken: String?
}

URL-адрес маршрутизатораRequestConvertible Updates

var URLRequest: NSMutableURLRequest {
    let URL = NSURL(string: Alamofire.Manager.baseURLString)!
    let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
    mutableURLRequest.HTTPMethod = method.rawValue

    if let token = Alamofire.Manager.OAuthToken {
        mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    }

    switch self {
    case .CreateObject(_, let parameters):
        return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
    case .UpdateObject(_, _, let parameters):
        return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
    default:
        return mutableURLRequest
    }
}

Надеюсь, это поможет пролить свет. Удачи!

person cnoon    schedule 01.08.2015
comment
Привет @cnoon! Это очень полезно, спасибо! Однако, как это настроено, возможно ли сделать что-то вроде func readObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)/?randomParameter=true" }. Я попытался это сделать, но при преобразовании URL-адреса в mutableURLRequest он форматирует вопросительный знак следующим образом: %3F - person Thomas; 05.12.2015
comment
Неважно, я исправил это, изменив его так, чтобы путь добавлялся к baseURLString внутри URL, а не в mutableURLRequest - person Thomas; 05.12.2015