调用API客户端的networking请求的通用方法

我已经提供了我的firebase API客户端的代码。

使用generics,使用json直接初始化任何实体是否聪明?

要下载列表,我需要一个指示器让我知道,我正在请求一个实体列表 – 因为有不同的实现,所以我添加了一个GETALL的情况下,我的HTTPMethod枚举,这是不好的,和一些会混淆到其他?

我也觉得这是不灵活的,因为如果嵌套在不同的层次上,我不能得到所需的实体。 希望这是有道理的。 因此,这可能不遵循开放/封闭的原则,因为如果我必须将我的实体嵌套在Firebase中,则不同的是,必须再次更改FirebaseAPI内的实现。

从我见过的开源代码中,我还没有看到像这样devise的rest客户端,也不知道我是否使用了反模式。 任何帮助或指导,使这个维护。

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请求方法的input。 问题:当请求中唯一发生变化的实体涉及到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() } } }