How to execute an array of operations in order with possible breaks in the middle with ReactiveCocoa - chaining

Suppose that I have a telephony application. I have a feature that I want to try calling an array of users one by one and break the sequence whenever one of the users accepts call, or when the complete operation is cancelled.
I will try to simplify it like this in pseudocode:
for(user in users) {
result = callUserCommand(user);
if(result == "accepted" || result == "cancelled") {
break;
}
}
Here, the callUserCommand is a RACCommand that needs to be async. And it can actually have three return values: "accepted", "cancelled", "declined".
Accepted and Cancelled will break the sequence of operations and won't execute the rest.
Declined, should continue with the execution of the rest of the sequence.
I tried with something like the following, but really couldn't accomplish exactly the thing I described above.
RACSignal *signal = [RACSignal concat:[users.rac_sequence map:^(User * user) {
return [self.callUserCommand execute:user];
}]];
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
} completed:^{
}];

If I understood correctly you would like to execute the sequence one by one until one of the call gets accepted or cancelled.
Maybe you could give takeUntil or takeWhile a try. I would write this scenario with RAC like this:
NSArray* users = #[#"decline", #"decline", #"decline", #"accept", #"decline"];
[[[[[users.rac_sequence signal]
flattenMap:^RACStream *(NSString* userAction) {
NSLog(#"Calling user (who will %#):", userAction);
// return async call signal here
return [RACSignal return:userAction];
}]
takeWhileBlock:^BOOL(NSString* resultOfCall) {
return [resultOfCall isEqualToString:#"decline"];
}]
doCompleted:^{
NSLog(#"Terminated");
}]
subscribeNext:^(NSString* userAction) {
NSLog(#"User action: %#", userAction);
}];
In the sample code above the last user who would decline the call won't be called.

Related

For-loop isn't returning the items in an array in the correct order

I have an array of Integers that I need to loop through that then make a network request for further information and with the returned information populate an array of a new object I have created.
I want the returned data to come back in the same order in which the array provides it in, however it is coming back in a different order and am assuming it might have something to do with the network request.
I'm new(ish) to development, so the answer may be very obvious, but I'm really at a dead end with what to do next.
I've tried adding delays to the network request on each loop, i've tried calling .sort() on the array to ensure the array stays in the correct order
var tacticalCoverIdArray = [Int]()
var savedTacticalCoverData = [Covers]()
for coverID in tacticalCoverIdArray {
performGetRequestForSpecificCovers(coverID: coverID, targetURL: targetURL, completion: { (data, HTTPSatusCode, error) -> Void in
if HTTPSatusCode == 200 && error == nil {
do {
if coverID != 0 {
let decodedJSON = try JSONDecoder().decode([Covers].self, from: data)
savedTacticalCoverData.append(decodedJSON[0])
} else {
let data = Covers(id: 0, game: 0, image_id: "")
savedTacticalCoverData.append(data)
}
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: {
saveTacticalCoverData()
})
} catch let error {
print("Error decoding JSON: \(error)")
}
} else {
print("HTTP status code error: \(HTTPSatusCode)")
print("Error loading data: \(String(describing: error))")
}
})
}
When putting a print statement under the very first declaration of the for-loop (i.e: print(coverID) the return is what I would expect, in which it loops through each integer and then returns them in order.
However, as soon as I put the same print statement under the 'performGetRequestForSpecificCovers' method, the coverID array is not in the order that it should be in, and therefore I get my returned values in an incorrect order when I append them to my 'savedTacticalCoverData' array.
Your hunch about the network requests having an impact on the ordering seems correct.
What I'm guessing is happening here is that when you are looping over tacticalCoverIdArray and calling performGetRequestForSpecificCovers(), that loop doesn't wait for that network request to complete and for the completion block to get called. It continues with the next iteration. Effectively, you are sending tacticalCoverIdArray.count network requests in parallel. Those completion blocks get called much later, long after the outer loop is complete, and most likely even on a different thread.
The most basic, and worst, option is to use DispatchSemaphore to hold up the outer loop until the completion block is called. You'd create a semaphore, call semaphore.signal() inside the completion handler, and call semaphore.wait() at the end of every loop iteration. The problem with this approach is that you will wait for each network request to complete before proceeding to the next one. Also, you will tie up the thread that is executing the first outer loop, and threads are a finite resource, so it's not a good idea to waste them.
A better option is to dispatch all requests at once, and handle the out-of-order responses. This will complete much faster than serially dispatching them, unless you encounter some kind of limitations with dispatching so many network requests in parallel. Instead of savedTacticalCoverData being an array, perhaps it could be a dictionary, where the key is the index of the outer loop, and the value is what you're trying to save? Each time the completion handler gets called, you could check whether or not the dictionary is full and you've accumulated all of the responses you want, and only then proceed with the final "everything is done" action, presumably saveTacticalCoverData().
You'll have to be careful to get your multithreading right. Unless performGetRequestForSpecificCovers() uses only one callback queue, and it's the same queue that this function is running on, you might get called on different threads. If that's the case, I would recommend making a new DispatchQueue and always operating on your dictionary only from that queue, to ensure consistency when those completion blocks come in on random threads. Something like this:
class MyClass {
var tacticalCoverIdArray = [Int]()
var savedTacticalCoverData = [Int: Covers]()
var queue = DispatchQueue(label: "Class Internal Queue")
func myFunc() {
// ... fill in the blanks here
for (index, coverID) in tacticalCoverIdArray.enumerated() {
performGetRequestForSpecificCovers(coverID: coverID, targetURL: targetURL, completion: { (data, HTTPSatusCode, error) -> Void in
if HTTPSatusCode == 200 && error == nil {
do {
queue.async {
if coverID != 0 {
let decodedJSON = try JSONDecoder().decode([Covers].self, from: data)
self.savedTacticalCoverData[index] = decodedJSON[0]
} else {
let data = Covers(id: 0, game: 0, image_id: "")
self.savedTacticalCoverData[index] = data
}
if self.savedTacticalCoverData.count == self.tacticalCoverIdArray.count {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: {
self.saveTacticalCoverData()
})
}
}
} catch let error {
print("Error decoding JSON: \(error)")
}
} else {
print("HTTP status code error: \(HTTPSatusCode)")
print("Error loading data: \(String(describing: error))")
}
})
}
}
}

Codename One EasyThread implementation that repeats a runnable if its result is false

Note for the readers: this question is specific for Codename One only.
I'm developing an app that needs some initial data from a server to run properly. The first shown Form doesn't need this data and there is also a splash screen on the first run, so if the Internet connection is good there is enought time to retrive the data... but the Internet connection can be slow or absent.
I have in the init a call to this method:
private void getStartData() {
Runnable getBootData = () -> {
if (serverAPI.getSomething() && serverAPI.getXXX() && ...) {
isAllDataFetched = true;
} else {
Log.p("Connection ERROR in fetching initial data");
}
};
EasyThread appInfo = EasyThread.start("APPINFO");
appInfo.run(getBootData);
}
Each serverAPI method in this example is a synchronous method that return true if success, false otherwise. My question is how to change this EasyThread to repeat again all the calls to (serverAPI.getSomething() && serverAPI.getXXX() && ...) after one second if the result is false, and again after another second and so on, until the result is true.
I don't want to shown an error or an alert to the user: I'll show an alert only if the static boolean isAllDataFetched is false when the requested data is strictly necessary.
I tried to read carefully the documentation of EasyThread and of Runnable, but I didn't understand how to handle this use case.
Since this is a thread you could easily use Thread.sleep(1000) or more simply Util.sleep(1000) which just swallows the InterruptedException. So something like this would work:
while(!isAllDataFetched) {
if (serverAPI.getSomething() && serverAPI.getXXX() && ...) {
isAllDataFetched = true;
} else {
Log.p("Connection ERROR in fetching initial data");
Util.sleep(1000);
}
}

Method For Ending A Loop With Completion Handler?

Perhaps I have an underlying issue understanding the overall premise, but I am trying to figure out the best approach to do something with an array of items, and end the test early once a certain criteria is found.
For example, I have an array of names;
var names = ["Bob", "Billy", "Sarah", "Brandon", "Brian", "Rick"]
I would like to test each name in the array and see if it exists in a database. To do this, I'm calling another function with a completion handler;
for name in names {
TestName(name) { response in
if response {
// END THE LOOP
} else {
// KEEP GOING
}
}
I've not been able to figure out the // END THE LOOP. For the purposes of this example, I'm only concerned when the response is true the first time (if Billy exists in the array, I have no further interest in testing Sarah, Brandon, Brian, or Rick).
Thank you!
Before you start the loop, set a flag:
var exitEarly = false
for name in names {
Test the flag each time thru the loop:
for name in names {
if exitEarly {
break
}
In the TestName response block, set the flag:
TestName(name) { response in
if response {
exitEarly = true
} else {
// KEEP GOING
}
}
Note, however, that if TestName's block is executed asynchronously, that won't work, because the whole loop precedes the calling of any of the asynchronous blocks (that is the nature of asynchronous-ness).
Your case isn't really what a loop is designed for. It's possible that the loop may finish before the closures within the loop are executed.
Instead, try a recursive function with a completion block:
func databaseHasAName(names: [String], index: Int, completion: (String?) -> ()) {
guard index < names.count else{
completion(nil)
return
}
let name = names[index]
TestName(name) { response in
if response {
completion(name)
} else {
databaseHasName(names, index: index + 1, completion: completion)
}
}
}
This insures that only one call is happening at a time, regardless of the synchronicity of the response block
Add the #noescape attribute to TestName's closure parameter to indicate that the closure does not escape the call.
Use a variable outside the TestName call, set to false, and set it to true inside the loop if you want to stop.
After the TestName call, check the variable to see if you need to break.
or
Change TestName to return a value indicating if it should proceed or not
I had to do the same thing as PEEJWEEJ. My async tasks are web queries and parsing and quite intensive and the for loop always finished before those tasks did so there was no way to stop them.
So I converted it to recursive and set a flag when I wanted it to stop and it works fine now.
// ************************************************************************************************************
// MARK: Recursive function
// I changed the loop to be recursive so I could stop when I need to - i.e. when selecting another race
// I set the stopFetching flag in the seque when changing races and the current set of BG queries stops
// ************************************************************************************************************
func getRaceResultsRecursive(race: Race, bibs: [Bib], index: Int, completion: #escaping (Bool?) -> ())
{
guard index < bibs.count else{
completion(nil)
return
}
var url: URL
self.stopFetching = false
let bibID = bibs[index]
url = self.athleteResultAPI.formatURL(baseURL: (race.baseURL)!,
rd: (race.rdQueryItem)!,
race: (race.raceQueryItem)!,
bibID: "\(bibID.bib!)",
detail: (race.detailQueryItem)!,
fragment: (race.detailQueryItem)!,
webQueryType: (race.webQueryType!))
self.athleteResultAPI.fetchAthleteResults(url: url, webQueryType: race.webQueryType!)
{
(athleteReturnCode) in
switch athleteReturnCode
{
case let .success(athleteResult):
self.athleteResults.append(athleteResult) // Add to collection
self.delegate?.athleteResultsUpdated() // update Delegate to notify of change
case let .failure(error):
print(error)
break
}
if self.stopFetching {
completion(true)
}
else {
self.getRaceResultsRecursive(race: race, bibs: bibs, index: index + 1, completion: completion)
}
}
}
// ************************************************************************************************************
// getRaceResults
// Get all the bibs to track for this race from the iCloudKit database
// ************************************************************************************************************
func getRaceResults(race: Race)
{
// get all the races and bibs an put in the Races Store
raceStore.fetchBibsForRace(race: race, foreignKey: race.recordID)
{
(results, error) in
if let error = error {
print("Error in fetchBibsForRace(): \(error)")
return
}
// clear the store since we are on a new race
self.athleteResults.removeAll()
self.getRaceResultsRecursive(race: race, bibs: race.bibs, index: 0)
{
(stopFetching) in
return
}
}
}

Get results from cloudkit-query

First: I'm really new at CloudKit and the "dispatch"-mechanisms.
But I'd like to write a function in swift which will return the query-results as an array. Now my problem is, that the function doesn't wait for the query to finish and so I receive an empty array. There are records in my DB but I don't get it back properly.
So as I said I don't really understand the whole dispatch_async-mechanism etc. I've also read the tutorial on raywenderlich.com about CloudKit but I still don't get, how I can return the array properly.
This is my actual code. I've often seen people using dispatch_async-methods but I really don't understand how they can return my array.
func loadMyShopsCoolAwesome() ->[Shops]{
let container = CKContainer.defaultContainer()
var publicDB = container.publicCloudDatabase
let myQuery = CKQuery(recordType: "Shops", predicate: NSPredicate(value: true))
var myShops = [MyShops]()
publicDB.performQuery(myQuery, inZoneWithID: nil) {
results, error in
if error != nil {
println(error)
} else {
for record in results{
let shop = MyShops(nameElementAt: record.objectForKey("nameElementAt") as Int, nameElementFromSplit: record.objectForKey("nameElementFromSplit") as Int, nameSplitString: record.objectForKey("nameSplitString"), priceElementAt: record.objectForKey("priceElementAt") as Int, priceSplitString: record.objectForKey("priceSplitString"), shopName: record.objectForKey("shopName"), shopURL: record.objectForKey("shopURL"), xPathName: record.objectForKey("xPathName"), xPathPrice: record.objectForKey("xPathPrice"))
myShops.append(shop)
}
return myShops
}
}
}
You should not let your function return data. This way it will only work if you implement a wait mechanism (usually done with semaphores). If you would do that, you would block your app during the data fetch.
Instead you should return void and instead of returning your shops you should call a new function with the shops. There you should continue processing of the shops. So your function will be:
func loadMyShopsCoolAwesome() {
...
And instead of the
return myShops
You should call a new function. something like:
processShops(myShops)
Which would then call a new function that looks like:
func processShops(shops: [Shops]) {
// do work with shops
...
}
Also be aware that this will be called on a background queue. So if you want to do something with the interface you have call that on the main queue like this:
NSOperationQueue.mainQueue().addOperationWithBlock {
...
}

Angular promises run conditionally

I'd like to perform an asynch function conditionally, but I think I'm missing the correct syntax to say what I want.
this.doUpToThreeThings = function(skipTheMiddleStep) {
return doFirstThing().then(function (result) {
if (skipTheMiddleStep) {
return doThirdThing();
} else {
return doSecondThing();
}
}).then(function (result) {
if (skipTheMiddleStep) {
return "ok"; // return what?
} else {
return doThirdThing();
}
});
}
By the time we get to the second then, I don't know if the first block did the middle step, so I'm forced into repeating the condition. And the second block reads weird: it should say, if skip the middle step, then do the third thing, but since it we know that the previous block must have done the third thing, it just returns. So I have to repeat the condition and write pretty wrong-looking code in the second then.
I realize I can write a function called doSecondAndThirdThings, and just call that from the condition in the first block, but that's not really DRY, it's just hiding the non-DRYness. (or maybe I'm wrong about that?)
Also, I'm still a little confused about returning a completed promise on that "ok" branch. Is that right how it is, or should I say something like resolve? -- Thanks
The deferred in thefourtheye's answer is pointless and is considered an anti pattern with promises.
Here is how I would do it:
this.doUpToThreeThings = function(skipTheMiddleStep) {
return doFirstThing().then(function (result) {
return (skipTheMiddleStep) ? doThirdThing() : doSecondThing().then(doThirdThing);
});
}

Resources