为API客户端

时间:2017-07-31 16:07:19

标签: ios swift design-patterns client rx-swift

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

使用泛型是否明智,以这种方式直接从json初始化任何实体?

要下载列表,我需要一个指示器让我知道我正在请求实体列表 - 因为有不同的实现,所以我在我的HTTPMethod枚举中添加了一个GETALL案例,这是不是很糟糕什么东西会让别人感到困惑?

我也觉得这不灵活,因为如果嵌套在不同的级别,我就无法在节点上获得所需的实体。希望这是有道理的。因此,这可能不遵循开放/封闭原则,因为如果我必须将我的实体嵌套在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()
        }


    }
}

以下枚举包含可以对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()

        }

    }

}

0 个答案:

没有答案