How do I avoid callback hell on swift functions th

2019-07-21 01:30发布

I don't want this to be misconstrued as a duplicate. I want to deal with callback hell arising from API calls from Firestore (database by Firebase, a Google platform). I can't modify their function declarations, so I assume I will have to wrap their functions with some of my code.

For example, in the code below, the function eventCreatedSuccessfully() can only be called after the asynchronous function completes. eventCreatedSuccessfully() also contains a function call to firebase that has a closure, which another function relies on and etc...Though this isn't causing me problems right now, it probably will as my App grows larger and larger. I researched online and found solutions like Futures and Streams from third-party frameworks, but I didn't know how to integrate them into code I have no control over (the API calls).

batch.commit { (error) in
    self.dismiss(animated: true) {
        if error == nil {
            self.eventCreatedSuccessfully()
            print("Event created successfully")
        } else {
            print(error!.localizedDescription)
        }
    }
}

2条回答
叼着烟拽天下
2楼-- · 2019-07-21 01:46

Wrap the calls in promises. Any of the popular libraries will do the trick. The one that comes to mind is PromiseKit, available (at the time of this writing) at https://github.com/mxcl/PromiseKit.

Here is code I wrote for a work project (it's open source) which wraps a function that takes a completion, and returns a Promise which will signal with the result when the completion is called. It's using an internal Promise implementation, but the process can be adapted to other implementations.

public func promise<Return>(_ task: (@escaping (Return?, Error?) -> ()) -> ()) -> Promise<Return> {
    let p = Promise<Return>()

    task { (value: Return?, error: Error?) -> Void in
        if let error = error {
            p.signal(error)
        }

        if let value = value {
            p.signal(value)
        }
    }

    return p
}

The completion is expected to be called with a result of some kind, or an error. Adapt as required for your use-case.

An example usage follows.

public typealias BalanceCompletion = (Balance?, Error?) -> Void

func balance(completion: @escaping BalanceCompletion) {
    guard deleted == false else {
        completion(nil, KinError.accountDeleted)

        return
    }

    Stellar.balance(account: stellarAccount.publicKey!, asset: asset, node: node)
        .then { balance -> Void in
            completion(balance, nil)
        }
        .error { error in
            completion(nil, KinError.balanceQueryFailed(error))
    }
}

func balance() -> Promise<Balance> {
    return promise(balance)
}
查看更多
放我归山
3楼-- · 2019-07-21 01:53

I researched online and found solutions like Futures and Streams (...)

In most cases futures and streams is all about PromiseKit and RxSwift frameworks.

If you have only big amount of closures try to use PMK. It's very simple and easy to use. PMK also has nice documentation section on github.

RxSwift is more advanced level, because it requires you to write code fully to paradigm of itself - starts from server/firebase request and ends with ui. Also, there is a good note at PMK github about difference of these two.

Also, should be note, that google also has nice library called promises. According to their benchmarks google's library is leader in almost all nominations.

查看更多
登录 后发表回答