How to know when all objects are saved asynchronously using ReactiveCocoa - ios

In my app, I am using ReactiveCocoa to return signals to notify me when async api calls are completed (successfully or not). On the POST for saving the data, it only takes one object at a time:
- (RACSignal *)postJSONData:(NSDictionary *)dict toRelativeURL:(NSString *)urlString;.
The function that returns a RACSignal sends the subscriber a next:
[subscriber sendNext:json]or an Error: [subscriber sendError:jsonError].
This works great when saving a single object but I also have a scenario where I have to save multiple objects. These objects can be saved in any order (i.e. they are not dependent on one another) or sequentially - it doesn't matter to me.
I need to update the UI indicating the overall progress (Saving 1 of 4, Saving 2 of 4....) as well as a final progress update (Completed 4 of 4) and specific action to take when all have been processed (successful or not).
There are a number of ways to do this, but I'd like to do this the proper way using ReactiveCocoa. I'm thinking I either can do this with a concat: or then: with a rac_sequence map:^, but I'm not sure. On their github page, they show an example of addressing parallel work streams, but they use 2 discretely defined signals. I won't have my signals until I loop through each object I need to save. Would love some guidance or an example (even better!). Thanks in advance.

I'm doing something similar in my app where I start 3 different async network calls and combine them all into one signal that I can listen to. Basically I loop through all my objects and store the network signal in an array. I then call merge: and pass it the array of network signals.
NSMutableArray *recievedNames = [NSMutableArray new];
NSMutableArray *signals = [NSMutableArray new];
//go though each database that has been added and grab a signal for the network request
for (GLBarcodeDatabase *database in self.databases) {
[signals addObject:[[[[self.manager rac_GET:[database getURLForDatabaseWithBarcode:barcode] parameters:nil] map:^id(RACTuple *value) {
return [((NSDictionary *)value.second) valueForKeyPath:database.path];
}] doError:^(NSError *error) {
NSLog(#"Error while fetching name from database %#", error);
}]
}
//forward all network signals into one signal
return [[[RACSignal merge:signals] doNext:^(NSString *x) {
[recievedNames addObject:x];
}] then:^RACSignal *{
return [RACSignal return:[self optimalNameForBarcodeProductWithNameCollection:recievedNames]];
}];
Feel free to ask me questions about any of the operators I have used and I will do my best to explain them.

I am just learning ReactiveCocoa myself but I wanted to point out something important which also agrees with lightice11's answer. concat combines signals sequentially. Meaning you won't get anything from #2 or #3 until #1 completes. merge on the other hand interleaves the responses returning whatever comes next regardless of order. So for your scenario, it sounds like you really do want merge.
To quote the man, Justin Spahr-Summers:
concat joins the streams sequentially, merge joins the signals on an as-soon-as-values-arrive basis, switch only passes through the events from the latest signal.

Related

RACSubject and disposal

I am trying to create a signal that will cancel a NSURLSessionDataTask upon disposal. The problem is that I am not able to wait for the task to finish until I can send next values (implementing Server-sent Events), but I have to use the NSURLSessions delegate methods.
What I am doing right now is creating a RACSubject and returning it for every new request. Upon new events arrive, I sendNext: on the subject. The problem I have is figuring out when to efficiently cancel the task, if there are no more subscribers on the subject.
A workaround I found so far is creating a dummy signal and merging it with the subject (see below).
return [[RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
return [RACDisposable disposableWithBlock:^{
if ( dataTask.state != NSURLSessionTaskStateCanceling && dataTask.state != NSURLSessionTaskStateRunning ) {
[dataTask cancel];
}
}];
}]
merge:self.requests[#(dataTask.taskIdentifier)][kSubjectKey]];
But there has to be a more elegant way, or? Plus a downside is, that the signal will never complete. If I sendCompleted within the dummy signal, the dispose block will be called immediately.
I am using ReactiveCocoa 2.5.x
Have you checked out this library. Basically all you need is to turn the delegate methods into blocks and then you can use declare the blocks inside of creatSignal:call. Check out this post if you want to wrap the delegate methods into blocks yourself.

How do I properly dealloc and release objects being referred to in a while loop

The following code creates a memory leak. An asynchronous background process downloads images in tmp_pack_folder and another background thread is checking if the image count matches the total count expected, and then makes the images available to users once the download is complete.
The issue is that if the background process that is downloading images to the tmp_pack_folder fails for some reason, the following code becomes an infinite loop. This is a rare case, but when it does there is a memory leak. getAllFileNamesinFolder method is actually calling contentsOfDirectoryAtPath:bundleRoot of NSFileManager and it is called repeatedly. How to do I properly deallocate memory in this case (apart from preventing the infinite loop to begin with)
NSString *tmp_pack_folder = [packid stringByAppendingString:#"_tmp"];
if([fileMgr folderExists: tmp_pack_folder]){
NSArray *packImages = [fileMgr getAllFileNamesInFolder:tmp_pack_folder];
while(packImages.count != arrImages.count ){
packImages = [fileMgr getAllFileNamesInFolder:tmp_pack_folder]; //get the contents of the folder again.
if(cancel==YES){
break;
}
}
}
You say that you will rework this to "prevent the infinite loop." You should take that a step further and eliminate the loop altogether. If you ever find yourself with code that loops, polling some status, there's invariably an alternate, more efficient design. Bottom line, your memory situation is not the real problem: It's merely a symptom of a broader design issue.
I'd advise you move to an event-driven approach. So, rather than having a method that repeatedly performs the "am I done yet" logic, you should only check this status when triggered by the appropriate event (i.e. only when a download finishes/fails, and not before). This loop is probably causing to your memory problem, so don't fix the memory problem, but rather eliminate the loop altogether.
In answer to your question, one possible source of the memory problem arises from autorelease objects. These are objects that are allocated in such a manner that they are not released immediately when you're done with them, but rather only when the autorelease pool is drained (which generally happens for you automatically when you yield back to the app's run loop). But if you have some large loop that you repeatedly call, you end up adding lots of objects to an autorelease pool that isn't drained in a timely manner.
In some special cases, if you truly needed some loop (and to be clear, that's not the case here; you neither need nor want a loop in this case), you could employ your own custom #autoreleasepool, through which you'd effectively control the frequency of the draining of the pool.
But, at the risk of belaboring the point, this is simply not one of those situations. Don't use your own autorelease pool. Get rid of the loop. Only trigger the "am I done yet" logic when a download finishes/fails, and your problem should go away.
It's too bad Objective-C doesn't give us javascript-like promises. The way I solve this problem is by giving my asynch task a caller's interface like this:
- (void)doAsynchThingWithParams:(id)params completion:(void (^)(id))completion;
The params parameterize whatever the task is, and the completion handler takes result of the task.
This let's me treat several concurrent tasks like a todo list, with a completion handler that gets called with all the results once they've arrived.
// array is an array of params for each task e.g. urls for making url requests
// completion is called when all are complete with an array of results
- (void)doManyThingsWithParams:(NSArray *)array completion:(void (^)(NSArray *))completion {
NSMutableArray *todoList = [array mutableCopy];
NSMutableArray *results = [NSMutableArray array];
// results will always have N elements, one for each task
// nulls can be replaced by either good results or NSErrors
for (int i=0; i<array.count; ++i) results[i] = [NSNull null];
for (id params in array) {
[self doAsynchThingWithParams:params completion:^(id result) {
if (result) {
NSInteger index = [array indexOfObject:params];
[results replaceObjectAtIndex:index withObject:result];
}
[todoList removeObject:params];
if (!todoList.count) completion(results);
}];
}
}

View Controller state update on model update with ReactiveCocoa

As I'm slowly trying to wrap my head around ReactiveCocoa I wrote this piece of code and I'm fairly sure there's a better way to solve my problem. I'd appreciate input on how to improve / redesign my situation.
#weakify(self);
[RACObserve(self, project) subscribeNext:^(MyProject *project) {
#strongify(self);
self.tasks = nil;
[[[project tasks] takeUntilBlock:^BOOL(NSArray *tasks) {
if ([tasks count] > 0) {
MyTask *task = (MyTask *)tasks[0];
BOOL valid = ![task.projectID isEqualToString:self.project.objectID];
return valid;
}
return NO;
}] subscribeNext:^(NSArray *tasks) {
self.tasks = tasks;
}];
}];
What this does:
I have a View Controller with a property called project of type MyProject and a property tasks of type NSArray. A project has a tasks signal that returns an array of MyTasks. The project can be changed at any time from the outside. I want my view controller to respond and refresh itself when said case occurs.
Problem I'm trying to solve:
I used to [[project tasks] subscribeNext:...] within the first block, until I realized that if the webrequest took too long and I switched the project in the meantime, I received and assigned data from the old project in the new context! (Shortly thereafter the new data set arrived and everything went back to normal).
Nevertheless, that's the problem I had and I solved it by using the takeUntilBlock: method. My question is: How can I simplify / redesign this?
The crucial operator to most naturally take the tasks of the most recent project is -switchToLatest. This operator takes a signal of signals and returns a signal that sends only the values sent from the latest signal.
If that sounds too abstract, it will help to put it in terms of your domain. First, you have a signal of projects, specifically RACObserve(self, project). Then, you can -map: this project signal into a signal that contains the result of the call to -tasks, which happens to return a signal. Now you have a signal of signals. Applying -switchToLatest to the signal of task signals will give you a signal of tasks, but only sending tasks from the most recent project, never "old" tasks from a previously assigned project.
In code, this looks like:
[[RACObserve(self, project)
map:^(MyProject *project) {
return [project tasks];
}]
switchToLatest];
Another idiom you can apply to simplify your code is to use the RAC() macro, which assigns to a property while avoiding explicit subscription.
RAC(self, tasks) = [[RACObserve(self, project)
map:^(MyProject *project) {
return [project tasks];
}]
switchToLatest];
Update
To address the questions in the comments, here's an example of how you could initialize the tasks property to nil following an change of the project, and also a simplistic approach to handling errors in the -tasks signal.
RAC(self, tasks) = [[RACObserve(self, project)
map:^(MyProject *project) {
return [[[project
tasks]
startsWith:nil]
catch:^(NSError *error) {
[self handleError:error];
return [RACSignal return:nil];
}];
}]
switchToLatest];

Using Blocks and GCD to manage tasks

I'm learning iOS and when it comes to GCD, it's confusing.
Let's get it out of the way, I'm writing a small program that fetch data from the internet.
Here is my viewcontroller
NSMutableArray dataArray = [NSMutableArray array];
[querysomethingwithblock:(^ {
//do some stuff here
[otherquerywithblock:( ^ {
//do some stuff here
// Here I got the data from internet
// Do loop action
[dataArray addObject:data];
})];
})];
// here I want to perform some actions only after get data from internet
[self performAction:dataArray];
How can I achieve this purpose. In practical, [self performAction:dataArray] always get fired before I get the data. I tried to play with GCD but no luck.
Here is some patterns I've tried so far
dispatch_async(queue, ^{
// Do query stuff here
dispatch_async(dispatch_get_mainqueue(), ^{
//perform action here
});
{;
Or using dispatch_group_async, dispatch_group_wait, dispatch_group_notify
The only way I can handle right now is to use dispatch_after but the point is the downloading time is variable, it's not good practice to have a specific time here
Thank you so much for any advice.
The part of code called Do query stuff here i assume is async already, why put it inside a dispatch_queue then?
If instead you manage to do a synchronous query, your code (the second snippet) would work, as the dispatch to the main queue would be executed only after the query finished.
If you don't have an option to execute the query in a synchronous manner, then you need some mechanism to register either a block or a callback to be executed when the download is finished.
At the end of the day, it all depends on what kind of query you have in there and what methods it offers for you to register an action to be performed when the download is finished.

(iOS) Offline Sync DB - Server

Trying to implement an app which sends offline data stored on local db to web server when connected to internet. I use the code shown below. As far I have tested it works fine, not sure it will work fine for huge number of records. I would like to know whether any tweaking on this code may increase the performance???
NOTE
I know this would be a worst code for offline sync purpose, so trying
to tweak it better.
Its a single way synchronization, from app to server.
-(void)FormatAnswersInJSON {
DMInternetReachability *checkInternet = [[DMInternetReachability alloc] init];
if ([checkInternet isInternetReachable]) {
if ([checkInternet isHostReachable:#"www.apple.com"]) {//Change to domain
responseArray = [[NSMutableArray alloc] init];
dispatch_async(backgroundQueue, ^(void) {
NSArray *auditIDArray = [[NSArray alloc] initWithArray: [self getUnuploadedIDs]];
for (int temp = 0; temp < [auditIDArray count]; temp ++) {
// Code to post JSON to server
NSURLResponse *response;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!error) {
NSString *responseID = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
if ([responseID isEqualToString:#"ERROR"]) {
//Error uploading records
} else {
[responseArray addObject:responseID];
}
} else {
//Error
return;
}
}
dispatch_async( backgroundQueue, ^{
/* Based on return code update local DB */
for (int temp = 0; temp < [responseArray count]; temp ++) {
[self updateRecordsForID:[auditIDArray objectAtIndex:temp] withID:[responseArray objectAtIndex:temp]];
}
});
});
}
}
}
- (void)upload { //Called when internet connection available
if(backgroundQueue){
dispatch_suspend(backgroundQueue);
dispatch_release(backgroundQueue);
backgroundQueue = nil;
}
backgroundQueue = dispatch_queue_create("com.XXXX.TestApp.bgqueue", NULL);
dispatch_async(backgroundQueue, ^(void) {
[self FormatAnswersInJSON];
});
}
If this code were sitting in front of me, my approach would be:
Look at the use cases and define 'huge number of records': Will 50 record updates at a time occur regularly? Or will it be in 1s and 2s? Do my users have wifi connections or is it over the paid network?, etc.
If possible, test in the wild. If my user base was small enough, gather real data and let that guide my decisions, or only release the feature to a subset of users/beta tests and measure.
If the data tells you to, then optimize this code to be more efficient.
My avenue of optimization would be doing group processing. The rough algorithm would be something like:
for records in groups of X
collect
post to server {
on return:
gather records that updated successfully
update locally
}
This assumes you can modify the server code. You could do groups of 10, 20, 50, etc. all depends on the type of data being sent, and the size.
A group algorithm means a bit more pre-processing client side, but has the pro of reducing HTTP requests. If you're only ever going to get a small number of updates, this is YAGNI and pre-mature optimization.
Don't let this decision keep you from shipping!
Your code has a couple of issues. One convention is to always check the return value before you test the error parameter. The error parameter might be set - even though the method succeeded.
When using NSURLConnection for anything else than a quick sample or test, you should also always use the asynchronous style with handling the delegate methods. Since using NSURLConnection properly may become quickly cumbersome and error prone, I would suggest to utilize a third party framework which encapsulates a NSURLConnection object and all connection related state info as a subclass of NSOperation. You can find one example implementation in the Apple samples: QHTTPOperation. Another appropriate third party framework would be AFNetworking (on GitHub).
When you use either the async style with delegates or a third party subclass, you can cancel the connection, retrieve detailed error or progress information, perform authentication and much more - which you can't with the synchronous API.
I think, once you have accomplished this and your approach works correctly, you may test whether the performance is acceptable. But unless you have large data - say >2 MByte - I wouldn't worry too much.
If your data becomes really large, say >10 MByte you need to consider to improve your approach. For example, you could provide the POST data as file stream instead a NSData object (see NSURLRequest's property HTTPBodyStream). Using a stream avoids to load all the POST data into RAM which helps alleviate the limited RAM problem.
If you have instead smaller POST data, but possibly many of them, you might consider to use a NSOperationQueue where you put your NSOperation connection subclass. Set the maximum number of concurrent operations to 2. This then may leverage HTTP pipelining - if the server supports this, which in effect reduces latency.
Of course, there might be other parts in your app, for example you create or retrieve the data which you have to send, which may affect the overall performance. However, if your code is sound and utilizes dispatch queues or NSOperations which let things perform in paralel there aren't many more options to improve the performance of the connection.

Resources