Общий способ вызова сетевых запросов для API-клиента

Я предоставил код для моего клиента API Firebase.

Является ли разумным использовать generics, чтобы инициализировать любой объект прямо из json таким образом?

Чтобы загружать списки, мне нужен индикатор, чтобы сообщить мне, что я запрашиваю список сущностей, поскольку существует другая реализация, поэтому я добавил случай GETALL в мой перечислитель HTTPMethod, это плохо, и что-то, что будет сбивать с толку другим ?

Я также чувствую, что это не гибко, потому что я не могу получить желаемые объекты на узле, если они вложены на разные уровни. Надеюсь, это имеет смысл. Таким образом, это, вероятно, не соответствует принципу open / closed, потому что, если мне нужно вложить свои объекты в firebase, по-разному, придется снова изменить реализацию внутри FirebaseAPI.

Из открытого исходного кода, который я видел, я не совсем видел клиента для отдыха, спроектированного таким образом, и не уверен, что я использую анти-шаблон или что-то в этом роде. Любая помощь или руководство, чтобы сделать это поддерживаемым.

class FirebaseAPI { private let session: URLSession init() { self.session = URLSession.shared } /// Responsible for Making actual API requests & Handling response /// Returns an observable object that conforms to JSONable protocol. /// Entities that confrom to JSONable just means they can be initialized with json. func rx_fireRequest<Entity: JSONable>(_ endpoint: Endpoint) -> Observable<[Entity]> { return Observable.create { [weak self] observer in self?.session.dataTask(with: endpoint.request, completionHandler: { (data, response, error) in /// Parse response from request. let parsedResponse = Parser.init(data: data, response: response, error: error) .parse() switch parsedResponse { case .error(let error): observer.onError(error) return case .success(let data): var entities = [Entity]() /// Consider edge case where a list of entities are retrieved, rather than a single entity. /// Iterate through each entity and initialize. /// Denoted by 'GETALL' method. switch endpoint.method { case .GETALL: /// Key (underscored) is unique identifier for each entity, which is not needed here. /// value is k/v pairs of entity attributes. for (_, value) in data { if let value = value as? [String: AnyObject], let entity = Entity(json: value) { entities.append(entity) } else { observer.onError(NetworkError.initializationFailure) return } observer.onNext(entities) observer.onCompleted() return } default: if let entity = Entity(json: data) { observer.onNext([entity]) observer.onCompleted() } else { observer.onError(NetworkError.initializationFailure) } } } }) return Disposables.create() } } } 

Enum below Содержит все запросы, которые могут быть сделаны для firebase. Соответствует протоколу конечной точки, поэтому одним из этих членов перечисления будет вход для метода запроса FirebaseAPI. ПРОБЛЕМА: Кажется избыточным иметь несколько случаев для операций CRUD, когда единственное, что меняется, – это объект, участвующий в запросе.

 enum FirebaseRequest { case saveUser(data: [String: AnyObject]) case findUser(id: String) case removeUser(id: String) case saveItem(data: [String: AnyObject]) case findItem(id: String) case findItems case removeItem(id: String) case saveMessage(data: [String: AnyObject]) case findMessages(chatroomId: String) case removeMessage(id: String) } extension FirebaseRequest: Endpoint { var base: String { return ""https://<APPNAME>.firebaseio.com/"" } var path: String { switch self { case .saveUser(let data): return "\(Constant.users)/\(data[Constant.id])" case .findUser(let id): return "\(Constant.users)/\(id)" case .removeUser(let id): return "\(Constant.users)/\(id)" case .saveItem(let data): return "\(Constant.items)/\(data[Constant.id])" case .findItem(let id): return "\(Constant.items)/\(id)" case .findItems: return "\(Constant.items)" case .removeItem(let id): return "\(Constant.items)/\(id)" case .saveMessage(let data): return "\(Constant.messages)/\(data[Constant.id])" case .findMessages(let chatroomId): return "\(Constant.messages)/\(chatroomId)" case .removeMessage(let id): return "\(Constant.messages)/\(id)" /// This is still incomplete... Will have more request. } } var method: Method { /// URLRequest method is GET by default, so just consider PUT & DELETE methods. switch self { /// If saving, return PUT /// If removing, return DELETE default: return .GET } } var body: [String : AnyObject]? { /// If saving, get associated value from enum case, and return that. return nil } } 

Протокол конечной точки

 protocol Endpoint { var base: String { get } var path: String { get } var method: Method { get } var body: [String: AnyObject]? { get } // no params needed for firebase. auth token just goes in url. } extension Endpoint { private var urlComponents: URLComponents? { var components = URLComponents(string: base) components?.path = path + "auth=\(AuthService.shared.authToken)" + ".json" return components } var request: URLRequest { var request = URLRequest(url: urlComponents!.url!) request.httpMethod = self.method.description if let body = body { do { let json = try JSONSerialization.data(withJSONObject: body, options: []) request.httpBody = json } catch let error { // error! print(error.localizedDescription) } } return request } } 

Методы HTTP

 enum Method { case GET /// Indicates how JSON response should be parsed differently to abastract a list of entities case GETALL case PUT case DELETE } extension Method: CustomStringConvertible { var description: String { switch self { case .GET: return "GET" case .GETALL: return "GET" case .PUT: return "PUT" case .DELETE: return "DELETE" } } } 

AuthService

 class AuthService { private static let _shared = AuthService() static var shared: AuthService { return _shared } private let disposeBag = DisposeBag() var currentUserId: String { return Auth.auth().currentUser!.uid } var authToken: AuthCredential { return FacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString) } func rx_login(viewController: UIViewController) { /// Facebook login rx_facebookLogin(viewController: viewController) .asObservable() .subscribe(onNext: { [weak self] (credentials: AuthCredential, userInfo: [String: Any]) in /// Firebase Login self?.rx_firebaseLogin(with: credentials) .asObservable() .subscribe(onNext: { [weak self] (uid) in /// TODO: Save in firebase db.. }).addDisposableTo((self?.disposeBag)!) }).addDisposableTo(disposeBag) } // - MARK: facebook login private func rx_facebookLogin(viewController: UIViewController) -> Observable<(AuthCredential, [String: Any])> { return Observable<(AuthCredential, [String: Any])>.create { observable in let loginManager = FBSDKLoginManager() loginManager.logIn(withReadPermissions: ["public_profile", "email"], from: viewController) { (result, error) in guard error == nil else { observable.onError(AuthError.custom(message: error!.localizedDescription)) print("debugger: error: \(error!.localizedDescription)") return } guard let accessToken = FBSDKAccessToken.current() else { observable.onError(AuthError.invalidAccesToken) print("debugger: invalid access token") return } /// Facebook credentials to login with firebase. let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString) /// Build request to get user facebook info. guard let request = FBSDKGraphRequest(graphPath: "me", parameters: ["fields":"name"], tokenString: accessToken.tokenString, version: nil, httpMethod: "GET") else { observable.onError(AuthError.facebookGraphRequestFailed) print("debugger: could not create request.") return } /// - Perform Request request.start { (connection, result, error) in guard error == nil else { observable.onError(AuthError.custom(message: error!.localizedDescription)) print("debugger: error: \(error!.localizedDescription)") return } print("Debugger: profile results: \(result)") /// TODO: GET CITY FOR LOCALITY guard let result = result as? [String: AnyObject], let name = result[Constant.name] as? String else { observable.onError(AuthError.invalidProfileData) print("debugger: error converting profile results") return } /// Includes data needed to proceed with firebase login process. observable.onNext((credential, ["name": name])) observable.onCompleted() print("Debugger: Successfull login") } } return Disposables.create() } } private func rx_firebaseLogin(with credential: AuthCredential) -> Observable<String> { return Observable<String>.create { observable in Auth.auth().signIn(with: credential) { (user, error) in guard error == nil else { observable.onError(AuthError.custom(message: error!.localizedDescription)) print("error firelogin \(error!.localizedDescription)") return } guard user != nil else { observable.onError(AuthError.invalidFirebaseUser) print("debugger: error with user..") return } observable.onNext(user!.uid) observable.onCompleted() } return Disposables.create() } } } 

Давайте будем гением компьютера.