Cancel firebase function - ios

Is it possible to cancel Firebase HTTPS Callable function during it's request?
I have a function with some predictive search. It is called each time when user inputs a character in a search field.
Code:
func startSearch(_ query: String, completion: #escaping (_ results: [SearcheResults]) -> Void) {
let data = [
"query": query
]
functions.httpsCallable("startSearch").call(data) { (result, error) in
if error != nil {
completion([])
} else if let data = result?.data {
// some data manipulations
completion(elements)
}
}
}
Or maybe somehow dismiss earlier completions? Because for now, if user is very rapid and enter text, for example "Berlin" - completion will fire 6 times. I'd like to have a way to cancel a function or cancel previous completions.
Thanks in advance.

You should try debounce, basically in debounce before you fire a request you wait for short span(eg:- 2 secs), and if user types in that span again, timer is reset to again 2 secs,check the link
Once the call is made it cannot be cancelled, It will Either get executed to completition or timedout.

Once you invoke a callable function, it can't be canceled. The function will run to completion or timeout. You will need to be sure on the client that you really want to invoke the function. You are by no means obliged to consume the result (you can ignore it if you want), but the transaction will complete, unless the client app dies in the process. In that case, the function on the backend will still complete, but it will just not be able to deliver the response.

Related

Swift Combine: combining three signals into one

I'm dealing with a legacy libraries where I'm not at liberty to modify their code, and am trying to use Combine to weave them into something more easy to use. My situation is that a method call can either return a response, or a response and two notifications. The response-only is a success scenario, the response + 2 notifications is an error scenario. I want to combine both response and payload from the two notifications into an error that I can pass on to my app. The really fun thing is that I don't have a guarantee if the response or notifications come first, nor which of the notifications comes first. The notifications come in on a different thread than the response. The good thing is that they come in "just about the same time".
For handling a notification, I do
firstNotificationSink = notificationCenter.publisher(for: .firstErrorPart, object: nil)
.sink { [weak self] notification in
// parse and get information about the error
}
secondNotificationSink = notificationCenter.publisher(for: .secondErrorPart, object: nil)
.sink { [weak self] notification in
// parse and get more information about the error
}
and asking the legacy library for a response is:
func doJob() -> String {
let resultString = libDoStuff(reference)
}
Is there a way for me to use Combine to merge these three signals into one, given i.e. a 50ms timeframe? Meaning, if I get the result and two notifications, I have an error response I can pass on to my app, and if I have only the result and no notifications arrived in 50ms, then I can pass that success response to my app?
The part about combining the three signals is easy: use .zip. That's not very interesting. The interesting part of the problem is that you want a pipeline that signals whether a notification arrived within a certain time limit. Here's an example of how to do that (I'm not using your actual numbers, it's just a demo):
import UIKit
import Combine
enum Ooops : Error { case oops }
class ViewController: UIViewController {
var storage = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
print("start")
NotificationCenter.default.publisher(for: Notification.Name("yoho"))
.map {_ in true}
.setFailureType(to: Ooops.self)
.timeout(0.5, scheduler: DispatchQueue.main) { Ooops.oops }
.replaceError(with: false)
.sink {print($0)}
.store(in: &self.storage)
DispatchQueue.main.asyncAfter(deadline:.now()+0.2) {
NotificationCenter.default.post(name: Notification.Name("yoho"), object: self)
}
}
}
If the asyncAfter delay is 0.2, we get true (followed by false, but that's not important; we could change that if we wanted to). If the delay is 0.9, we get false. So the point is, the first value we get distinguishes correctly whether we got a signal in the required time.
Okay, so the rest is trivial: you just hook up your three signals with .zip, as I said before. It emits a tuple after all three publishers have emitted their first signal — and that's all the information you need, because you've got the result from the method call plus Bools that tell you whether the notifications arrived within the time limit. You can now read that tuple and analyze it, and do whatever you like. The .zip operator has a map function so you can emit the result of your analysis in good order. (If you wanted to transform the result of the map function into an error, that would require a further operator, but again, that's easy.)

Make multiple asynchronous requests but wait for only one

I have a question concerning asynchronous requests. I want to request data from different sources on the web. Each source might have the data I want but I do not know that beforehand. Because I only want that information once, I don't care about the other sources as soon as one source has given me the data I need. How would I go about doing that?
I thought about doing it with a didSet and only setting it once, something like this:
var dogPicture : DogPicture? = nil {
didSet {
// Do something with the picture
}
}
func findPictureOfDog(_ sources) -> DogPicture? {
for source in sources {
let task = URL.Session.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
dogPicture = data.getPicture()
}
}
task.resume()
}
}
sources = ["yahoo.com", "google.com", "pinterest.com"]
findPictureOfDog(sources)
However it would be very helpful, if I could just wait until findPictureOfDog() is finished, because depending on if I find something or not, I have to ask the user for more input.
I don't know how I could do it in the above way, because if I don't find anything the didSet will never be called, but I should ask the user for a picture then.
A plus: isWhatIWanted() is rather expensive, so If there was a way to abort the execution of the handler once I found a DogPicture would be great.
I hope I made myself clear and hope someone can help me out with this!
Best regards and thank you for your time
A couple of things:
First, we’re dealing with asynchronous processes, so you shouldn’t return the DogPicture, but rather use completion handler pattern. E.g. rather than:
func findPictureOfDog(_ sources: [String]) -> DogPicture? {
...
return dogPicture
}
You instead would probably do something like:
func findPictureOfDog(_ sources: [String], completion: #escaping (Result<DogPicture, Error>) -> Void) {
...
completion(.success(dogPicture))
}
And you’d call it like:
findPictureOfDog(sources: [String]) { result in
switch result {
case .success(let dogPicture): ...
case .failure(let error): ...
}
}
// but don’t try to access the DogPicture or Error here
While the above was addressing the “you can’t just return value from asynchronous process”, the related observations is that you don’t want to rely on a property as the trigger to signal when the process is done. All of the “when first process finishes” logic should be in the findPictureOfDog routine, and call the completion handler when it’s done.
I would advise against using properties and their observers for this process, because it begs questions about how one synchronizes access to ensure thread-safety, etc. Completion handlers are unambiguous and avoid these secondary issues.
You mention that isWhatIWanted is computationally expensive. That has two implications:
If it is computationally expensive, then you likely don’t want to call that synchronously inside the dataTask(with:completionHandler:) completion handler, because that is a serial queue. Whenever dealing with serial queues (whether main queue, network session serial queue, or any custom serial queue), you often want to get in and out as quickly as possible (so the queue is free to continue processing other tasks).
E.g. Let’s imagine that the Google request came in first, but, unbeknownst to you at this point, it doesn’t contain what you wanted, and the isWhatIWanted is now slowly checking the result. And let’s imagine that in this intervening time, the Yahoo request that came in. If you call isWhatIWanted synchronously, the result of the Yahoo request won’t be able to start checking its result until the Google request has failed because you’re doing synchronous calls on this serial queue.
I would suggest that you probably want to start checking results as they came in, not waiting for the others. To do this, you want a rendition of isWhatIWanted the runs asynchronously with respect to the network serial queue.
Is the isWhatIWanted a cancelable process? Ideally it would be, so if the Yahoo image succeeded, it could cancel the now-unnecessary Pinterest isWhatIWanted. Canceling the network requests is easy enough, but more than likely, what we really want to cancel is this expensive isWhatIWanted process. But we can’t comment on that without seeing what you’re doing there.
But, let’s imagine that you’re performing the object classification via VNCoreMLRequest objects. You might therefore cancel any pending requests as soon as you find your first match.
In your example, you list three sources. How many sources might there be? When dealing with problems like this, you often want to constrain the degree of concurrency. E.g. let’s say that in the production environment, you’d be querying a hundred different sources, you’d probably want to ensure that no more than, say, a half dozen running at any given time, because of the memory and CPU constraints.
All of this having been said, all of these considerations (asynchronous, cancelable, constrained concurrency) seem to be begging for an Operation based solution.
So, in answer to your main question, the idea would be to write a routine that iterates through the sources, and calling the main completion handler upon the first success and make sure you prevent any subsequent/concurrent requests from calling the completion handler, too:
You could save a local reference to the completion handler.
As soon as you successfully find a suitable image, you can:
call that saved completion handler;
nil your saved reference (so in case you have other requests that have completed at roughly the same time, that they can’t call the completion handler again, eliminating any race conditions); and
cancel any pending operations so that any requests that have not finished will stop (or have not even started yet, prevent them from starting at all).
Note, you’ll want to synchronize the the above logic, so you don’t have any races in this process of calling and resetting the completion handler.
Make sure to have a completion handler that you call after all the requests are done processing, in case you didn’t end up finding any dogs at all.
Thus, that might look like:
func findPictureOfDog(_ sources: [String], completion: #escaping DogPictureCompletion) {
var firstCompletion: DogPictureCompletion? = completion
let synchronizationQueue: DispatchQueue = .main // note, we could have used any *serial* queue for this, but main queue is convenient
let completionOperation = BlockOperation {
synchronizationQueue.async {
// if firstCompletion not nil by the time we get here, that means none of them matched
firstCompletion?(.failure(DogPictureError.noneFound))
}
print("done")
}
for source in sources {
let url = URL(string: source)!
let operation = DogPictureOperation(url: url) { result in
if case .success(_) = result {
synchronizationQueue.async {
firstCompletion?(result)
firstCompletion = nil
Queues.shared.cancelAllOperations()
}
}
}
completionOperation.addDependency(operation)
Queues.shared.processingQueue.addOperation(operation)
}
OperationQueue.main.addOperation(completionOperation)
}
So what might that DogPictureOperation might look like? I might create an asynchronous custom Operation subclass (I just subclass a general purpose AsynchronousOperation subclass, like the one here) that will initiate network request and then run an inference on the resulting image upon completion. And if canceled, it would cancel the network request and/or any pending inferences (pursuant to point 3, above).
If you care about only one task use a completion handler, call completion(nil) if no picture was found.
var dogPicture : DogPicture?
func findPictureOfDog(_ sources, completion: #escaping (DogPicture?) -> Void) {
for source in sources {
let task = URL.Session.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
let picture = data.getPicture()
completion(picture)
}
}
task.resume()
}
}
sources = ["yahoo.com", "google.com", "pinterest.com"]
findPictureOfDog(sources) { [weak self] picture in
if let picture = picture {
self?.dogPicture = picture
print("picture set")
} else {
print("No picture found")
}
}
You can use DispatchGroup to run a check when all of your requests have returned:
func findPictureOfDog(_ sources: [String]) -> DogPicture? {
let group = DispatchGroup()
for source in sources {
group.enter()
let task = URLSession.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
dogPicture = data.getPicture()
}
group.leave()
}
task.resume()
}
group.notify(DispatchQueue.main) {
if dogPicture == nil {
// all requests came back but none had a result.
}
}
}

How do I ensure that all network calls have been made before accessing my core data model?

I am making multiple api calls in succession and when I finally push to my next view controller my data comes up completely blank from my core data model. In ViewController A I have made the following requests in this order:
Api.verifyOtp(email, otp).continueWith { (task) -> Any? in
if task.succeed {
self.apiCallOne()
self.apiCallTwo()
self.apiCallThree()
self.apiCallFour()
self.apiCallFive()
} else {
Hud.hide()
task.showError()
}
return nil
}
Now all of these calls are made asynchronously. However the last method which is self.apiCallFive() is the method that pushes to ViewController B. Here is the call:
Api.apiCallFive().continueOnSuccessWith { (task) -> Any? in
Hud.hide()
if task.succeed {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewB storyboard.instantiateViewController(withIdentifier: "ViewControllerB" self.navigationController?.pushViewController(viewB, animated: true)
}
My guess is that since all of these requests are happening asynchronously then there's no guarantee on which call will finish first. So the apiCallFive() is pushing and loading ViewController B before the others are able to finish. How can I make it so the next view will not be loaded or pushed to until all of the tasks have been completed?
Thank you!
I have faced the same issue. Fix it by using DispatchGroup.
Code:
Define as property
let APIGroup = DispatchGroup()
Execute below code when any API Calling starts.
APIGroup.enter()
Execute below code when any API Calling Completed.
downloadGroup.leave()
Notify Block:
APIGroup.notify(queue: DispatchQueue.main) {
print("All APIs called successfully: Perform required operation")
}
There no need to manage by any counter or other variables. notify block call automatically when all task completed successfully.
What’s really important here is the enter-leave pairs. You have to be
very careful and make sure that you leave the group. It would be easy
to introduce a bug in the code above. Let’s say that we didn’t leave
the group in that guard statement above, just before the return. If
the API called failed, or the JSON was malformed, the number of groups> entries would not match the number of leaves. So the group completion
handler would never get called. If you’re calling this method from the
UI and displaying an activity indicator while your networking requests
are running, you would never get a callback from the method, and you
would keep on spinning 🙂
Apple documents
To solve this you need a way of getting notified when each call is finished.
The easiest way of doing this is using completion blocks on each call.
func apiCall(completion: #escaping () -> Void) {
....
}
After adding completion blocks to the api calls, your blocks could look like this:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
apiCallOne {
dispatchGroup.leave()
}
dispatchGroup.enter()
apiCallTwo {
dispatchGroup.leave()
}
...
dispatchGroup.enter()
apiCallN {
dispatchGroup.leave()
}
dispatchGroup.wait(timeout: Constants.timeout)
Keep in mind that the wait statement will block the thread where you call it until all the leave() statements are executed, so be careful that you don't end up with a deadlock.

Get the latest result from DispatchGroup wait

Problem Desctiption:
I want to do a bunch of asynchronous tasks by 'DispatchGroup' and when all of them finished it returned the result. In addition, I want to set timeout that limits the process and send me back the successful results by that time. I used the following structure:
Code Block
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
// Timeout for 10 seconds
myGroup.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))
myGroup.notify(queue: .main) {
return result
}
How can I get the latest result if timeout happened?
Ok, so you are correctly using the enter/leave functionality of the DispatchGroup, but are having trouble with how to access the results of these. I think you are going wrong by trying to use both wait and notify, these two functions provide two different pieces of functionality not usually used together. After having setup up your work items, as you have done, you have two options:
The wait approach
This function blocks the calling queue and wait synchronously for either, the passed in wall time to elapse, or all work items in the group to leave. Because it is blocking the caller, it is important to always have a timeout in this function.
The notify approach
The function takes a target queue, and a block to be run when all work items in your group have completed. Here you are basically asking the system to notify you, asynchronously once all work items have been completed. Since this is asynchronous we are usually less worried about the timeout, it's not blocking anything.
Asynchronous wait (this appears to be what you want?)
If, as it seems you do, we want to be notified once all work items are complete, but also have a timeout, we have to do this ourselves, and it's not all that tricky. We can add a simple extension for the DispatchGroup class...
extension DispatchGroup {
func notifyWait(target: DispatchQueue, timeout: DispatchTime, handler: #escaping (() -> Void)) {
DispatchQueue.global(qos: .default).async {
_ = self.wait(timeout: timeout)
target.async {
handler()
}
}
}
}
This simple function dispatches asynchronously on a global background queue, then calls wait, which will wait for all work items to complete, or the specified timeout, whichever comes first. Then it will call back to your handler on the specified queue.
So that's the theory, how can you use this. We can keep your initial setup exactly the same
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
and then use our new function to wait for the end
myGroup.notifyWait(target: .main,
timeout: DispatchTime.now() + 10) {
// here you can access the `results` list, with any data that has
// been appended by the work items above before the timeout
// was reached
}

ReactiveCocoa 4 - Delaying and filtering signal events

I am implementing a search textfield using ReactiveCocoa 4, and want to only hit the search API after no text has been inputted for X amount of time. I have done this previously by canceling previously scheduled and firing off a "executeSearch" selector in the textDidChange delegate method. This ensures that every time I type, any previously scheduled "executeSearch" selector is canceled, and a new one is scheduled to fire in X seconds.
I now want to do this same behavior, but from a signal producer bound to my input text. My current implementation is close, but not the same. This behavior merely throttles the text input event to only fire every 0.5 seconds, instead of canceling the previous event.
searchTextInput.producer.delay(0.3, onScheduler: RACScheduler.currentScheduler())
.throttle(0.5, onScheduler: RACScheduler.currentScheduler())
.producer.startWithNext({ [unowned self] searchText in
self.executeSearch(searchText)
})
I'm having a hard time sifting through the ReactiveCocoa 4 documentation to know which signal functions I should be using! Thank you!
You need use DateSchedulerType. For example:
textField.rac_textSignal()
.toSignalProducer()
.map { $0 as! String }
.flatMapError { _ in SignalProducer<String, NoError>.empty }
.throttle(2.0, onScheduler: QueueScheduler.mainQueueScheduler)
.filter { $0.isEmpty }
.startWithNext { text in
print("t: \(text)")
}
Also you can write your executeSearch as SignalProducer and use flatMap(.Latest) for create signal-chains.
And don't forget using mainQueueSheduler for get result to UI

Resources