RestKit ActivityIndicator for several requests - ios

I have a IBAction which is called via a button in my iPad App.
The IBaction Calls then three different methods which initiate a RestKit Request.
What would be the best way to check of for all three requests the objects where retrieved?
The goal is to show an activityIndicator with the first request and stop/hide the activityIndicator with the retrieval of objects for all three requests.
I'm new to RestKit, so please forgive me if this is a too simple question. I tried to find a solution by myself with the help of the RestKit API - Documentation.

I suppose you use asynchronous requests. One simple solution can be a check in your RKObjectLoderDelegate's - (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects method whether all of your data is already loaded.
When initiating RestKit requests, you can add a 'tag' (called userData in RestKit) to a request and retrieve that tag later in the delegate callbacks.
For example, you can implement the following logic:
When you create your requests, add a specific user data to each one:
RKObjectLoader *loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/getMyData" objectMapping:mapping delegate:self];
[loader setUserData:#"FirstRequest"];
...
RKObjectLoader *loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/getMyData" objectMapping:mapping delegate:self];
[loader setUserData:#"ThirdRequest"];
and check the tags in didLoadObjects:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
if ([[objectLoader userData] isEqual:#"FirstRequest"]) {
self.firstRequestData = objects;
}
... //handle the remaining requests
if (self.firstRequestData && self.secondRequestData && self.thirdRequestData) {
[self hideMyActivityIndicator];
}
}
Don't forget to handle error situations when loading the requests.
Alternatively, if this approach will not fit your scenario - you can add and manage requests in a separate queue & track the progress. If you'll need more info on the second option just let me know.

There is an easy way, in your objectLoader delegate, you can use objectLoader's the built-in function like this:
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
if ([objectLoader wasSentToResourcePath:#"/path1"]) {
//handle request for /path1
//objects are loaded for request1
} else if ([objectLoader wasSentToResourcePath:#"/path2"]){
//handle request for /path2
//objects are loaded for request2
} else if ([objectLoader wasSentToResourcePath:#"/path3"]){
//handle request for /path3
//objects are loaded for request3
}
}
Its just that simple.

Related

Retrying commands with ReactiveCocoa

I'm trying to refactor my iOS app into ReactiveCocoa and ReactiveViewModel and I'm struggling with trying to work out a couple of best practices.
I'll boil this down to a simple use case - I was to push a view controller which loads some data and shoves it into a table view. If the endpoint call fails for whatever reason, I want to display a view on screen with a retry button.
I currently have this working but it looks a bit filthy. I feel like there must be a better way - am I even doing this correctly?
In my ViewModel's init method, I'm creating my command, which is called as soon as the ViewModel becomes active.
// create the command to load the data
#weakify(self);
self.loadStationsCommand = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *(id input) {
#strongify(self);
return [RACSignal createSignal:^(RACDisposable *(id<RACSubscriber subscriber) {
// load data from my API endpoint
...
BOOL succeeded = ...;
if (succeeded) {
[subscriber sendNext:nil];
[subscriber sendCompleted:nil];
} else {
// failed
[subscriber sendError:nil];
}
return nil;
}
}];
// start the command when the ViewModel's ready
[self.didBecomeActiveSignal subscribeNext:^(id x) {
#strongify(self);
[self.loadStationsCommand execute:nil];
}];
In my UIViewController, I'm subscribing to the command via -
[self.viewModel.loadStationsCommand.executionSignals subscribeNext:^(RACSignal *loadStationsSignal) {
[loadStationsSignal subscribeNext:^(id x) {
// great, we got the data, reload the table view.
#strongify(self);
[self.tableView reloadData];
} error:^(NSError *error) {
// THIS NEVER GETS CALLED?!
}];
}];
[self.viewModel.loadStationsCommand.errors subscribeNext:^(id x) {
// I actually get my error here.
// Show view/popup to retry the endpoint.
// I can do this via [self.viewModel.loadStationsCommand execute:nil]; which seems a bit dirty too.
}];
I must have some misunderstanding as to how RACCommand works, or at the very least I feel I'm not doing this as cleanly as I can.
Why doesn't the error block on my loadStationsSignal get called? Why do I need to subscribe to executionCommand.errors instead?
Is there a better way?
It is a correct way to handle errors with RACCommand. As you can read in the docs, errors of the inner signal are not sent when using executionSignals:
Errors will be automatically caught upon the inner signals, and sent upon
`errors` instead. If you _want_ to receive inner errors, use -execute: or
-[RACSignal materialize].
You can also use RAC additions to UIButton and bind self.viewModel.loadStationsCommand to rac_command of the retry button.
There is a good article which explains RACCommand and shows some interesting patterns to be used with it.

setDelegate self and using delegates in ASINetworkQueue. Selector not found when request finished

I have the following class:
File_Downloadmanager.h:
#import "ASINetworkQueue.h"
#interface File_Downloadmanager : NSObject {
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename;
-(void)initDownloadQueue; // creates a new download queue and sets delegates
-(void)startDownload; // starts the download queue
-(void)requestFinished;
-(void)requestFailed;
-(void)queueFinished;
#property(retain) ASINetworkQueue *downloadQueue;
#end
File_Downloadmanager.m:
#implementation File_Downloadmanager
#synthesize downloadQueue;
-(void)initDownloadQueue{
NSLog(#"Init DownloadQueue");
// Stop anything already in the queue before removing it
[[self downloadQueue] cancelAllOperations];
[self setDownloadQueue:[ASINetworkQueue queue]];
[[self downloadQueue] setDelegate:self];
[[self downloadQueue] setRequestDidFinishSelector:#selector(requestFinished:)];
[[self downloadQueue] setRequestDidFailSelector:#selector(requestFailed:)];
[[self downloadQueue] setQueueDidFinishSelector:#selector(queueFinished:)];
[self downloadQueue].shouldCancelAllRequestsOnFailure = NO;
}
-(void)startDownload{
NSLog(#"DownloadQueue Go");
[downloadQueue go];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// If no more elements are queued, release the queue
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Request finished");
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
//... Handle failure
NSLog(#"Request failed");
}
- (void)queueFinished:(ASINetworkQueue *)queue
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Queue finished");
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename{
...SourceCode for creating the request...
// add operation to queue
[[self downloadQueue] addOperation:request];
}
In another class a function is called an inside that function I'm doing the following:
-(void)downloadFiles{
File_Downloadmanager * downloadhandler = [[File_Downloadmanager alloc]init];
// initialize download queue
[downloadhandler initDownloadQueue];
for (int i = 0; i < [meetingObjects count]; i++) {
....some other code to get the objectID, userID, etc.
[downloadhandler addRequestToDownloadQueue:ID :[loginData stringForKey:#"userId"] :[NSString stringWithFormat:#"%#%#",currentObject.id,currentObject.name]]
}
[downloadhandler startDownload];
}
Everything works fine and the download begins. But when the first file is downloaded, I get an error in the ASINetworkQueue class that my selector "requestFinished" can't be called (I don't have the exact message, can't start the app at the moment, but the failure code was exc_bad_access code=1).
Is the time of declaration / initialization of my File_Downloadmanager object the problem? Because the function "downloadFiles" is called, the DownloadManager object created, the requests added and then the "downloadFiles" method returns because the queue works async?
I haven't used the ASI networking stuff before, but have seen lots of references to it on the net.
It sounds to me like the ASINetworkQueue class expects it's delegate to conform to a specific protocol. If it's set up correctly, you should get a warning when you try to assign yourself as the delegate of the ASINetworkQueue object but have not declared that your class conforms to the appropriate protocol. If you DO include a protocol declaration, then you should get a warning that you have not implemented required methods from that protocol.
Try cleaning your project and rebuilding, and then look carefully for warnings, specifically on your line:
[[self downloadQueue] setDelegate:self];
EDIT: I just downloaded one of the ASIHTTPRequest projects, and to my dismay, the delegate property of the ASINetworkQueue class does not have to conform to a specific protocol. This is bad programming style. If you set up a delegate, you should make the delegate pointer conform to a specific protocol.
Also, be aware that the ASI networking classes have not been maintained for several years now and are getting badly out of date. There are better alternatives out there, and you should look at moving to a different networking framework.
It looks like the downloadhandler object that ASINetworkQueue is attempting to send the requestFinished message to no longer exists at the time that the message is sent to it, as it's probably being deallocated when the downloadFiles method finishes executing. Instead of making the downloadhandler object local to the downloadFiles method, instead make it a (strong, nonatomic) property within the class that contains the downloadFiles method. That way, you can ensure that it will still exist when requestFinished is called.

NSURLConnection does not call complete across multiple Views

Earlier today I asked the following question: iOS block being stoped when view pushed
The operation I mentioned (OP1) is actually a "http get" to my server, using NSURLConnection.
After even more investigation I discovered that the block doesn't actually "die". What really happens is that the request is actually SENT (the server side logs it), even after the view is pushed (verified via [NSThread sleep:10]). The server responds but then NOTHING happens on the app side if the view2 has been pushed! almost as if the connection had lost its delegate! Another possibility im looking at is "the fact that NSURLConnection is on the rsMainLoop related?"
Can anyone help?
Pls don't forget that:
0. Everything works fine as long as the view2 is not pushed until operation completion.
1. The request is sent async
2. I set the delegate and it works as long as the view dont change
3. view1 starts the operation using the "singleton object reference" property "OP1Completed"
4. view2 checks the completion of OP1 via propertie on the "singleton object reference"
5. view2 gets the "result" by going to the "singleton.OP1Result" property
Edit 1:
Ok lets have some code. First here is the relevant code of my singleton (named "Interaction"):
-(void)loadAllContextsForUser:(NSString *)username{
userNameAux = username;
_loadingContextsCompleted = NO;
if (contextsLoaderQueue == NULL) {
contextsLoaderQueue = dispatch_queue_create("contextsLoaderQueue", NULL);
}
dispatch_async(contextsLoaderQueue, ^{
NSLog(#"Loading all contexts block started");
[self requestConnectivity];
dispatch_async(dispatch_get_main_queue(), ^{
[Util Get:[NSString stringWithFormat:#"%#/userContext?username=%#", Util.azureBaseUrl, [username stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]
successBlock:^(NSData *data, id jsonData){
NSLog(#"Loading all contexts block succeeded");
if([userNameAux isEqualToString:username]){
_allContextsForCurrentUser = [[NSSet alloc]initWithArray: jsonData];
}
} errorBlock:^(NSError *error){
NSLog(#"%#",error);
} completeBlock:^{
NSLog(#"load all contexts for user async block completed.");
_loadingContextsCompleted = YES;
[self releaseConnectivity];
}];
});
while (!_loadingContextsCompleted) {
NSLog(#"loading all contexts block waiting.");
[NSThread sleepForTimeInterval:.5];
}
});
NSLog(#"Load All Contexts Dispatched. It should start at any moment if it not already.");
}
And here is the class Util, which actually handles the request/response
-(id)initGet:(NSString *)resourceURL successBlock:(successBlock_t)successBlock errorBlock:(errorBlock_t)errorBlock completeBlock:(completeBlock_t)completeBlock;{
if(self=[super init]){
_data=[[NSMutableData alloc]init];
}
_successBlock = [successBlock copy];
_completeBlock = [completeBlock copy];
_errorBlock = [errorBlock copy];
NSURL *url = [NSURL URLWithString:resourceURL];
NSMutableURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
//[_conn scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//[_conn start];
NSLog(#"Request Started.");
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[_data setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
id jsonObjects = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingMutableContainers error:nil];
id key = [[jsonObjects allKeys] objectAtIndex:0];
id jsonResult = [jsonObjects objectForKey:key];
_successBlock(_data, jsonResult);
_completeBlock();
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
_errorBlock(error);
_completeBlock();
}
And finally here is the relevant part VC1 (pushing in VC2)
- (IBAction)loginClicked {
NSLog(#"login clicked. Preparing to exibit next view");
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
AuthenticationViewController *viewController = (AuthenticationViewController *)[storyboard instantiateViewControllerWithIdentifier:#"ContextSelectionView"];
NSLog(#"Preparation completed. pushing view now");
[self presentViewController:viewController animated:YES completion:nil];
}
You might be surprised, but there are a couple of solutions - some of which are very common and can be implemented very easily ;) Even though, this answer is ridiculous elaborate, the actual solution to your problem will not exceed a few lines of code. :)
You ran into a typical "async problem" - well, it's less than a problem, rather a typical programming task nowadays.
What you have is an asynchronous task, OP1. This will be started from within ViewController 1 (VC1), and at some indeterminate time later, it will eventually produce either a result or an error.
The eventual result of OP1 should be handled later in VC2.
There are a few approaches how a client can obtain the eventual result, for example: via KVO, delegate method, completion block, callback function, future or promise and per notification.
These approaches above have one property in common: the call-site gets notified by the asynchronous result provider (and not vice versa).
Polling for the result until it is available, is a bad approach. Likewse, hanging in a semaphore and blocking the current thread until the result is "signaled" is equally suboptimal.
You are probably familiar with completion blocks. A typical asynchronous method which notifies the call-site when the result is available looks like this:
typedef void (^completion_block_t)(id result);
- (void) doSomethingAsyncWithCompletion:(completion_block_t)completionHandler;
Note: the call-site provides the completion handler, while the async tasks calls the block when it is finished, and passes its result (or error) to the result parameter of the block. Unless otherwise stated, the execution context - that is the thread or dispatch queue or NSOperationQueue - of where the block will be executed is not known.
But when thinking about your problem, a simple async function and a completion handler doesn't yield a viable solution. You cannot pass that "method" easily from VC1 to VC2 and then later "attach" somehow a completion block in VC2.
Luckily, any asynchronous task can be encapsulated into an NSOperation. An NSOperation has a completion block as a property which can be set by the call-site or elsewhere. And an NSOperation object can be easily passed from VC1 to VC2. VC2 simply adds a completion block to the operation, and eventually gets notified when its finished and the result is available.
However, while this would be a viable solution for your problem - there are in fact a few issues with this approach - which I don't want to elaborate, but instead propose an even better one: "Promises".
A "Promise" represents the eventual result of an asynchronous task. That is, a promise will exist even though the result of the asynchronous task is not yet evaluated. A Promise is an ordinary object which you can send messages. Thus, Promises can be passed around much like NSOperations. A Promise is the return value of an asynchronous method/function:
-(Promise*) doSomethingAsync;
Don't mismatch a Promise with the asynchronous function/method/task/operation - the promise is just a representation of the eventual result of the task.
A Promise MUST be eventually resolved by the asynchronous task - that is, the task MUST send the promise a "fulfill" message along with the result value, or it MUST send the promise the "reject" message along with an error. The promise keeps a reference of that result value passed from the task.
A Promise can be resolved only once!
In order to obtain the eventual result a client can "register" a success handler and an error handler . The success handler will be called when the task fulfills the promise (that is, it was successful), and the error handler will be called when the task rejected the promise passing along the reason as an error object.
Assuming a particular implementation of a promise, resolving a promise may look like this:
- (Promise*) task {
Promise* promise = [Promise new];
dispatch_async(private_queue, ^{
...
if (success) {
[promise fulfillWithValue:result];
}
else {
NSError* error = ...;
[promise rejectWithReason:error];
}
});
return promise;
}
A client "registers" handlers for obtaining the eventual result as follows:
Promise* promise = [self fetchUsers];
promise.then( <success handler block>, <error handler block> );
The success handler and error handler block are declared as follows:
typedef id (^success_handler_block)(id result);
typedef id (^error_handler_block)(NSError* error);
In order to just "register" a success handler (for the case, the async tasks "returns" successfully) one would write:
promise.then(^id(id users) {
NSLog(#"Users:", users);
return nil;
}, nil);
If the task succeeds, the handler will be called - which prints the users to the console.
When the task fails, the success handler will not be called.
In order to just "register" an error handler (for the case, the async tasks fails) one would write:
promise.then(nil, ^id(NSError* error) {
NSLog(#"ERROR:", error);
return nil;
}, nil);
If the task succeeds, the error handler will not be called. Only if the task fails (or any children tasks), this error handler will be invoked.
When the result of the async task is eventually available, the code within the handlers will be executed "in some unspecified execution context". That means, it may execute on any thread. (Note: there are ways to specify the execution context, say the main thread).
A promise can register more than one handler pair. You can add as many handlers as you want, and where and when you want. Now, you should understand the connection with your actual problem:
You can start an asynchronous task in VC1, and get a promise. Then pass this promise to VC2. In VC2 you can add your handler, which will get invoked when the result is eventually available.
Don't worry when the result is actually already available when passing the promise to VC2, that is, when the promise has been resolved already. You can still add handlers and they get fired properly (immediately).
You can also "chain" multiple tasks - that is, invoke task2 once when task1 is finished. A "chain" or "continuation" of four async tasks looks as follows:
Promise* task4Promise =
[self task1]
.then(^id(id result1){
return [task2WithInput:result1];
}, nil)
.then(^id(id result2){
return [task3WithInput:result2];
}, nil)
.then(^id(id result3){
return [task4WithInput:result3];
}, nil);
task4Promise represents the eventual result of task4WithInput:.
One can also execute tasks in parallel, like taskB and taskC which will get started in parallel when taskA has been finished successfully:
Promise* root = [self taskA];
root.then(^id(id result){
return [self taskB];
}, nil);
root.then(^id(id result){
return [self taskC];
}, nil);
With this scheme, one can define an acyclic graph of tasks, where each is dependent on the successful execution of its successor ("parent"). "Errors" will be passed through to the root, and handled by the last error handler (if any).
There are a few implementations for Objective-C. I've written one myself: "RXPromise" (available on GitHub). One of the strongest feature is "Cancellation" - which is NOT a standard feature of promises, but implemented in RXPromise. With this, you can selectively cancel a tree of asynchronous tasks.
There is a lot more about promises. You may search the web, especially in the JavaScript community.
I'm not sure I understand the work flow that goes on in the first controller -- specifically, what the user does to initiate the download, and what else he does before the next controller gets presented (and when that controller gets instantiated). When I've made apps in the past that required doing downloads from multiple classes, I've created a download class that creates the NSURLConnection, and implements all the call backs. It has one delegate protocol method to send back the data (either raw data or error object) to its delegate.
I made a simple test case simulating what I think your work flow is, using two buttons. One instantiates a Downloader class instance, creates the next controller, sets it as the delegate of the downloader, and starts the download. The second button does the push to that second controller. This works, no matter when the push happens, but I don't know if it's relevant to your situation (I test using the Network Link Conditioner to simulate a slow connection).
The first Controller:
#import "ViewController.h"
#import "ReceivingViewController.h"
#import "Downloader.h"
#interface ViewController ()
#property (strong,nonatomic) ReceivingViewController *receiver;
#end
#implementation ViewController
-(IBAction)buttonClicked:(id)sender {
Downloader *loader = [Downloader new];
self.receiver = [self.storyboard instantiateViewControllerWithIdentifier:#"Receiver"];
loader.delegate = self.receiver;
[loader startLoad];
}
-(IBAction)goToReceiver:(id)sender {
[self.navigationController pushViewController:self.receiver animated:YES];
}
The Download class .h:
#protocol DownloadCompleted <NSObject>
-(void)downloadedFinished:(id) dataOrError;
#end
#interface Downloader : NSObject
#property (strong,nonatomic) NSMutableData *receivedData;
#property (weak,nonatomic) id <DownloadCompleted> delegate;
-(void)startLoad;
Downloader .m:
-(void)startLoad {
NSLog(#"start");
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
if (connection) self.receivedData = [NSMutableData new];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.receivedData.length = 0;
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.delegate downloadedFinished:error];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.delegate downloadedFinished:self.receivedData];
}
-(void)dealloc {
NSLog(#"In Downloader dealloc. loader is: %#",self);
}
The second controller:
#interface ReceivingViewController ()
#property (strong,nonatomic) NSData *theData;
#end
#implementation ReceivingViewController
-(void)downloadedFinished:(id)dataOrError {
self.theData = (NSData *)dataOrError;
NSLog(#"%#",self.theData);
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"%#",self.theData);
}
So, here is what I think will work for sure:
Pass the flag to the new controller. If the flag is unfinished, then start over loading in the new VC and make sure none of the data shows up until it is done loading.
I do think it is weird that the thread stops though, with the new VC being pushed, because when I dispatch asynchronous calls with AFNetworking, it does continue to load even after a new VC is pushed. Perhaps if you are using a different framework, you should use AFNetworking.
So, if your thread actually does continue after the new VC is pushed on (as I suspect it does - you just think it doesn't keep going because it crashes the code), then try the following:
a) pass flag, if operation finished, proceed normally
b) if not, don't load anything and invoke some kind of delegate method between the two that checks if the flag is set, and returns the data if so.
If you have questions on how to set up a delegate, just ask and I can fill in some details on that.
As already mentioned in a comment in you first question: you have probably two issues:
A design problem
A code issue, causing the block. (but without code this is difficult to figure out).
Lets propose a practical approach:
Say, our singleton is some "Loader" class which performs HTTP requests. Instead of polling a property which determines the state of the network request, you should return some object which you can ask for the state, or even better where VC2 can register a completion block which gets called when the request is finished.
An NSOperation could be "used" to represent the eventual result of the asynchronous network request. But this is a bit unwieldy - suppose we have a subclass RequestOperation:
RequestOperation* requestOp = [[Loader sharedLoader] fetchWithURL:url];
Now, "requestOp" represents your network request, including the eventual result.
You can obtain this operation in VC1.
You may not want to ask the shared loader about a particular operation, because it may stateless -- that is, it does not itself track the request operations. Consider, you want to use class Loader several times for starting network requests - possible in parallel. Then, which request do you mean when you ask one property of Loader which tells you something about the state of a request? (it won't work).
So, again back to a working approach and to VC1:
Suppose, in VC1 you obtained the RequestOperation object which is a subclass of NSOperation. Suppose, RequestOperation has a property responseBody - which is a NSData object representing the eventual response data of the request operation.
In order to obtain the eventual response body of the request, you cannot just ask the property: the connection could possibly still running - the you would get nil or garbage, or you might block the thread. The behavior is dependent on the implementation of RequestOperation.
The solution is as follows:
In VC2:
We assume, VC1 has "passed" the requestOp to VC2 (for example in prepareForSegue:sender:).
In order to retrieve the response body in an asynchronous correct manner, you need some extra steps:
Create a NSBlockOperation which executes a block which handles the response body, for example:
NSBlockOperation* handlerOp = [NSBlockOperation blockOperationWithBlock:^{
NSData* body = requestOp.responseBody;
dispatch_async(dispatch_get_main_queue(), ^{
self.model = body;
[self.tableView reloadData];
});
}];
Then, make the handlerOp dependent on the requestOp - that is, start executing handlerOp when requestOp finished:
[handlerOP addDependency:requestOp];
Add the handlerOp to a queue, in order to execute:
[[NSOperation mainQueue] addOperation:handlerOp];
This still requires you to think "asynchronously" - there is no way around this. The best is, to get used to the practical patterns and idioms.
An alternative approach is using RXPromise (from a third party library):
In VC1:
requestPromise = [Loader fetchWithURL:url];
Now, in VC2:
We assume, VC1 has "passed" the requestPromise to VC2 (for example in prepareForSegue:sender:).
For example in viewDidLoad:
requestPromise.thenOn(dispatch_get_main_queue(), ^id(id responseBody){
// executes on main thread!
self.model = responseBody;
[self.tableView reloadData];
return nil;
}, nil);
Bonus:
If required, you can cancel the network request at any time through sending cancel to the promise:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.requestPromise cancel];
self.requestPromise = nil;
}
I've figured it out. In my second view (where i w8 for the operation complete) I cannot w8 using ThreadSleep! I have to use [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

iOS block being stoped when view pushed [duplicate]

Earlier today I asked the following question: iOS block being stoped when view pushed
The operation I mentioned (OP1) is actually a "http get" to my server, using NSURLConnection.
After even more investigation I discovered that the block doesn't actually "die". What really happens is that the request is actually SENT (the server side logs it), even after the view is pushed (verified via [NSThread sleep:10]). The server responds but then NOTHING happens on the app side if the view2 has been pushed! almost as if the connection had lost its delegate! Another possibility im looking at is "the fact that NSURLConnection is on the rsMainLoop related?"
Can anyone help?
Pls don't forget that:
0. Everything works fine as long as the view2 is not pushed until operation completion.
1. The request is sent async
2. I set the delegate and it works as long as the view dont change
3. view1 starts the operation using the "singleton object reference" property "OP1Completed"
4. view2 checks the completion of OP1 via propertie on the "singleton object reference"
5. view2 gets the "result" by going to the "singleton.OP1Result" property
Edit 1:
Ok lets have some code. First here is the relevant code of my singleton (named "Interaction"):
-(void)loadAllContextsForUser:(NSString *)username{
userNameAux = username;
_loadingContextsCompleted = NO;
if (contextsLoaderQueue == NULL) {
contextsLoaderQueue = dispatch_queue_create("contextsLoaderQueue", NULL);
}
dispatch_async(contextsLoaderQueue, ^{
NSLog(#"Loading all contexts block started");
[self requestConnectivity];
dispatch_async(dispatch_get_main_queue(), ^{
[Util Get:[NSString stringWithFormat:#"%#/userContext?username=%#", Util.azureBaseUrl, [username stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]
successBlock:^(NSData *data, id jsonData){
NSLog(#"Loading all contexts block succeeded");
if([userNameAux isEqualToString:username]){
_allContextsForCurrentUser = [[NSSet alloc]initWithArray: jsonData];
}
} errorBlock:^(NSError *error){
NSLog(#"%#",error);
} completeBlock:^{
NSLog(#"load all contexts for user async block completed.");
_loadingContextsCompleted = YES;
[self releaseConnectivity];
}];
});
while (!_loadingContextsCompleted) {
NSLog(#"loading all contexts block waiting.");
[NSThread sleepForTimeInterval:.5];
}
});
NSLog(#"Load All Contexts Dispatched. It should start at any moment if it not already.");
}
And here is the class Util, which actually handles the request/response
-(id)initGet:(NSString *)resourceURL successBlock:(successBlock_t)successBlock errorBlock:(errorBlock_t)errorBlock completeBlock:(completeBlock_t)completeBlock;{
if(self=[super init]){
_data=[[NSMutableData alloc]init];
}
_successBlock = [successBlock copy];
_completeBlock = [completeBlock copy];
_errorBlock = [errorBlock copy];
NSURL *url = [NSURL URLWithString:resourceURL];
NSMutableURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
//[_conn scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//[_conn start];
NSLog(#"Request Started.");
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[_data setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
id jsonObjects = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingMutableContainers error:nil];
id key = [[jsonObjects allKeys] objectAtIndex:0];
id jsonResult = [jsonObjects objectForKey:key];
_successBlock(_data, jsonResult);
_completeBlock();
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
_errorBlock(error);
_completeBlock();
}
And finally here is the relevant part VC1 (pushing in VC2)
- (IBAction)loginClicked {
NSLog(#"login clicked. Preparing to exibit next view");
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
AuthenticationViewController *viewController = (AuthenticationViewController *)[storyboard instantiateViewControllerWithIdentifier:#"ContextSelectionView"];
NSLog(#"Preparation completed. pushing view now");
[self presentViewController:viewController animated:YES completion:nil];
}
You might be surprised, but there are a couple of solutions - some of which are very common and can be implemented very easily ;) Even though, this answer is ridiculous elaborate, the actual solution to your problem will not exceed a few lines of code. :)
You ran into a typical "async problem" - well, it's less than a problem, rather a typical programming task nowadays.
What you have is an asynchronous task, OP1. This will be started from within ViewController 1 (VC1), and at some indeterminate time later, it will eventually produce either a result or an error.
The eventual result of OP1 should be handled later in VC2.
There are a few approaches how a client can obtain the eventual result, for example: via KVO, delegate method, completion block, callback function, future or promise and per notification.
These approaches above have one property in common: the call-site gets notified by the asynchronous result provider (and not vice versa).
Polling for the result until it is available, is a bad approach. Likewse, hanging in a semaphore and blocking the current thread until the result is "signaled" is equally suboptimal.
You are probably familiar with completion blocks. A typical asynchronous method which notifies the call-site when the result is available looks like this:
typedef void (^completion_block_t)(id result);
- (void) doSomethingAsyncWithCompletion:(completion_block_t)completionHandler;
Note: the call-site provides the completion handler, while the async tasks calls the block when it is finished, and passes its result (or error) to the result parameter of the block. Unless otherwise stated, the execution context - that is the thread or dispatch queue or NSOperationQueue - of where the block will be executed is not known.
But when thinking about your problem, a simple async function and a completion handler doesn't yield a viable solution. You cannot pass that "method" easily from VC1 to VC2 and then later "attach" somehow a completion block in VC2.
Luckily, any asynchronous task can be encapsulated into an NSOperation. An NSOperation has a completion block as a property which can be set by the call-site or elsewhere. And an NSOperation object can be easily passed from VC1 to VC2. VC2 simply adds a completion block to the operation, and eventually gets notified when its finished and the result is available.
However, while this would be a viable solution for your problem - there are in fact a few issues with this approach - which I don't want to elaborate, but instead propose an even better one: "Promises".
A "Promise" represents the eventual result of an asynchronous task. That is, a promise will exist even though the result of the asynchronous task is not yet evaluated. A Promise is an ordinary object which you can send messages. Thus, Promises can be passed around much like NSOperations. A Promise is the return value of an asynchronous method/function:
-(Promise*) doSomethingAsync;
Don't mismatch a Promise with the asynchronous function/method/task/operation - the promise is just a representation of the eventual result of the task.
A Promise MUST be eventually resolved by the asynchronous task - that is, the task MUST send the promise a "fulfill" message along with the result value, or it MUST send the promise the "reject" message along with an error. The promise keeps a reference of that result value passed from the task.
A Promise can be resolved only once!
In order to obtain the eventual result a client can "register" a success handler and an error handler . The success handler will be called when the task fulfills the promise (that is, it was successful), and the error handler will be called when the task rejected the promise passing along the reason as an error object.
Assuming a particular implementation of a promise, resolving a promise may look like this:
- (Promise*) task {
Promise* promise = [Promise new];
dispatch_async(private_queue, ^{
...
if (success) {
[promise fulfillWithValue:result];
}
else {
NSError* error = ...;
[promise rejectWithReason:error];
}
});
return promise;
}
A client "registers" handlers for obtaining the eventual result as follows:
Promise* promise = [self fetchUsers];
promise.then( <success handler block>, <error handler block> );
The success handler and error handler block are declared as follows:
typedef id (^success_handler_block)(id result);
typedef id (^error_handler_block)(NSError* error);
In order to just "register" a success handler (for the case, the async tasks "returns" successfully) one would write:
promise.then(^id(id users) {
NSLog(#"Users:", users);
return nil;
}, nil);
If the task succeeds, the handler will be called - which prints the users to the console.
When the task fails, the success handler will not be called.
In order to just "register" an error handler (for the case, the async tasks fails) one would write:
promise.then(nil, ^id(NSError* error) {
NSLog(#"ERROR:", error);
return nil;
}, nil);
If the task succeeds, the error handler will not be called. Only if the task fails (or any children tasks), this error handler will be invoked.
When the result of the async task is eventually available, the code within the handlers will be executed "in some unspecified execution context". That means, it may execute on any thread. (Note: there are ways to specify the execution context, say the main thread).
A promise can register more than one handler pair. You can add as many handlers as you want, and where and when you want. Now, you should understand the connection with your actual problem:
You can start an asynchronous task in VC1, and get a promise. Then pass this promise to VC2. In VC2 you can add your handler, which will get invoked when the result is eventually available.
Don't worry when the result is actually already available when passing the promise to VC2, that is, when the promise has been resolved already. You can still add handlers and they get fired properly (immediately).
You can also "chain" multiple tasks - that is, invoke task2 once when task1 is finished. A "chain" or "continuation" of four async tasks looks as follows:
Promise* task4Promise =
[self task1]
.then(^id(id result1){
return [task2WithInput:result1];
}, nil)
.then(^id(id result2){
return [task3WithInput:result2];
}, nil)
.then(^id(id result3){
return [task4WithInput:result3];
}, nil);
task4Promise represents the eventual result of task4WithInput:.
One can also execute tasks in parallel, like taskB and taskC which will get started in parallel when taskA has been finished successfully:
Promise* root = [self taskA];
root.then(^id(id result){
return [self taskB];
}, nil);
root.then(^id(id result){
return [self taskC];
}, nil);
With this scheme, one can define an acyclic graph of tasks, where each is dependent on the successful execution of its successor ("parent"). "Errors" will be passed through to the root, and handled by the last error handler (if any).
There are a few implementations for Objective-C. I've written one myself: "RXPromise" (available on GitHub). One of the strongest feature is "Cancellation" - which is NOT a standard feature of promises, but implemented in RXPromise. With this, you can selectively cancel a tree of asynchronous tasks.
There is a lot more about promises. You may search the web, especially in the JavaScript community.
I'm not sure I understand the work flow that goes on in the first controller -- specifically, what the user does to initiate the download, and what else he does before the next controller gets presented (and when that controller gets instantiated). When I've made apps in the past that required doing downloads from multiple classes, I've created a download class that creates the NSURLConnection, and implements all the call backs. It has one delegate protocol method to send back the data (either raw data or error object) to its delegate.
I made a simple test case simulating what I think your work flow is, using two buttons. One instantiates a Downloader class instance, creates the next controller, sets it as the delegate of the downloader, and starts the download. The second button does the push to that second controller. This works, no matter when the push happens, but I don't know if it's relevant to your situation (I test using the Network Link Conditioner to simulate a slow connection).
The first Controller:
#import "ViewController.h"
#import "ReceivingViewController.h"
#import "Downloader.h"
#interface ViewController ()
#property (strong,nonatomic) ReceivingViewController *receiver;
#end
#implementation ViewController
-(IBAction)buttonClicked:(id)sender {
Downloader *loader = [Downloader new];
self.receiver = [self.storyboard instantiateViewControllerWithIdentifier:#"Receiver"];
loader.delegate = self.receiver;
[loader startLoad];
}
-(IBAction)goToReceiver:(id)sender {
[self.navigationController pushViewController:self.receiver animated:YES];
}
The Download class .h:
#protocol DownloadCompleted <NSObject>
-(void)downloadedFinished:(id) dataOrError;
#end
#interface Downloader : NSObject
#property (strong,nonatomic) NSMutableData *receivedData;
#property (weak,nonatomic) id <DownloadCompleted> delegate;
-(void)startLoad;
Downloader .m:
-(void)startLoad {
NSLog(#"start");
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
if (connection) self.receivedData = [NSMutableData new];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.receivedData.length = 0;
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.delegate downloadedFinished:error];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.delegate downloadedFinished:self.receivedData];
}
-(void)dealloc {
NSLog(#"In Downloader dealloc. loader is: %#",self);
}
The second controller:
#interface ReceivingViewController ()
#property (strong,nonatomic) NSData *theData;
#end
#implementation ReceivingViewController
-(void)downloadedFinished:(id)dataOrError {
self.theData = (NSData *)dataOrError;
NSLog(#"%#",self.theData);
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"%#",self.theData);
}
So, here is what I think will work for sure:
Pass the flag to the new controller. If the flag is unfinished, then start over loading in the new VC and make sure none of the data shows up until it is done loading.
I do think it is weird that the thread stops though, with the new VC being pushed, because when I dispatch asynchronous calls with AFNetworking, it does continue to load even after a new VC is pushed. Perhaps if you are using a different framework, you should use AFNetworking.
So, if your thread actually does continue after the new VC is pushed on (as I suspect it does - you just think it doesn't keep going because it crashes the code), then try the following:
a) pass flag, if operation finished, proceed normally
b) if not, don't load anything and invoke some kind of delegate method between the two that checks if the flag is set, and returns the data if so.
If you have questions on how to set up a delegate, just ask and I can fill in some details on that.
As already mentioned in a comment in you first question: you have probably two issues:
A design problem
A code issue, causing the block. (but without code this is difficult to figure out).
Lets propose a practical approach:
Say, our singleton is some "Loader" class which performs HTTP requests. Instead of polling a property which determines the state of the network request, you should return some object which you can ask for the state, or even better where VC2 can register a completion block which gets called when the request is finished.
An NSOperation could be "used" to represent the eventual result of the asynchronous network request. But this is a bit unwieldy - suppose we have a subclass RequestOperation:
RequestOperation* requestOp = [[Loader sharedLoader] fetchWithURL:url];
Now, "requestOp" represents your network request, including the eventual result.
You can obtain this operation in VC1.
You may not want to ask the shared loader about a particular operation, because it may stateless -- that is, it does not itself track the request operations. Consider, you want to use class Loader several times for starting network requests - possible in parallel. Then, which request do you mean when you ask one property of Loader which tells you something about the state of a request? (it won't work).
So, again back to a working approach and to VC1:
Suppose, in VC1 you obtained the RequestOperation object which is a subclass of NSOperation. Suppose, RequestOperation has a property responseBody - which is a NSData object representing the eventual response data of the request operation.
In order to obtain the eventual response body of the request, you cannot just ask the property: the connection could possibly still running - the you would get nil or garbage, or you might block the thread. The behavior is dependent on the implementation of RequestOperation.
The solution is as follows:
In VC2:
We assume, VC1 has "passed" the requestOp to VC2 (for example in prepareForSegue:sender:).
In order to retrieve the response body in an asynchronous correct manner, you need some extra steps:
Create a NSBlockOperation which executes a block which handles the response body, for example:
NSBlockOperation* handlerOp = [NSBlockOperation blockOperationWithBlock:^{
NSData* body = requestOp.responseBody;
dispatch_async(dispatch_get_main_queue(), ^{
self.model = body;
[self.tableView reloadData];
});
}];
Then, make the handlerOp dependent on the requestOp - that is, start executing handlerOp when requestOp finished:
[handlerOP addDependency:requestOp];
Add the handlerOp to a queue, in order to execute:
[[NSOperation mainQueue] addOperation:handlerOp];
This still requires you to think "asynchronously" - there is no way around this. The best is, to get used to the practical patterns and idioms.
An alternative approach is using RXPromise (from a third party library):
In VC1:
requestPromise = [Loader fetchWithURL:url];
Now, in VC2:
We assume, VC1 has "passed" the requestPromise to VC2 (for example in prepareForSegue:sender:).
For example in viewDidLoad:
requestPromise.thenOn(dispatch_get_main_queue(), ^id(id responseBody){
// executes on main thread!
self.model = responseBody;
[self.tableView reloadData];
return nil;
}, nil);
Bonus:
If required, you can cancel the network request at any time through sending cancel to the promise:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.requestPromise cancel];
self.requestPromise = nil;
}
I've figured it out. In my second view (where i w8 for the operation complete) I cannot w8 using ThreadSleep! I have to use [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

Waiting for several networking async calls in iOS

From a view controller, as a result of a button action, I need to create a custom object that manages a set of asynchronous remote service calls, and call the method of such object that fires those service calls. I need the view controller to wait for all the async networking operations to have finished in order to update its view. Since the networking operations are async, I don't know how I'd communicate from the custom object managing this tasks to the view controller when all operations are done.
Here is the code I currently have. The code snippet in the view controller is like this (result var is not currently used):
- (void)loadData
{
BOOL __block result = NO;
dispatch_queue_t queue = dispatch_queue_create(dataLoadQueue, NULL);
dispatch_async(queue,^{
Loader *loader = [[Loader alloc] init];
[loader loadData];
dispatch_async(dispatch_get_main_queue(), ^{
if (result) {
// Update view and notify success
}
else {
// Update view and notify error
}
});
});
dispatch_release(queue);
}
And this is the loader custom object side:
- (void)loadData
{
if ([Reachability checkNetStatus]) {
Service1 *service1 = [[Service1 alloc] init];
[service1 callAsyncService];
Service2 *service2 = [[Service2 alloc] init];
[service2 callAsyncService];
// More service calls
}
else {
// Notify network not reachable
}
}
Objects service1, service2... serviceN conform the NSURLConnectionDelegate and I notify they have finished in its connectionDidFinishLoading: by means of the NSNotificationCenter (loader object is listening for such notifications). Then, I donĀ“t know what is the correct way of making loader wait for all the networking operations, and notify back the view controller.
Thanks in advance
There are probably lots of ways you could do this. First, I don't think there's any need to use GCD in the view controller -- loader is already doing things asynchronously, so the creation of loader is fast.
As for how Loader knows when all its network operations are done, you could just keep a list of strings in a mutable array, like "1 done", "2 done", etc. that would be the same as strings sent in the user info of the notifications called in connectionDidFinishLoading:. All the services could send the same notification, but with different user info. In the selector for the observer, remove the string identical to the one in the user info, and check if the array is empty -- when it is, all your services are done. At that point, I would use a delegate method to pass back the data to the view controller. Something like this in Loader:
- (void)viewDidLoad {
[super viewDidLoad];
self.doneStrings = [#[#"1 done", #"2 done", #"3 done", #"4 done"] mutableCopy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationReceived:) name:#"SeriveFinishedNotification" object:nil];
}
-(void)notificationReceived:(NSNotification *) aNote {
[self.doneStrings removeObjectIdenticalTo:[aNote.userInfo objectForKey:#"doneString"]];
if (self.doneStrings.count == 0)
[delegate doSomethingWithTheData: theData];
}
You would probably need to some other things like handle the case where some of the network operations fail.
If you want to wait until the async tasks were done, you can use a semaphore. See the example below, the logic is pretty simply. I think you can easily adapt to your case.
//create the semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[objectManager.HTTPClient deletePath:[address addressURL] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//some code here executed in background
dispatch_semaphore_signal(semaphore); //sends a notification to release the semaphore
}failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//some other code here also executed in background
dispatch_semaphore_signal(semaphore); //sends a notification to release the semaphore
}];
//holds the thread until the dispatch_semaphore_signal(semaphore) is send
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
You haven't shared the details of how these asynchronous requests work, but another approach is to make these asynchronous requests NSOperation objects that you submit to a NSOperationQueue. (AFNetworking is an example of this sort of implementation.) When you do that, you can create yet another NSOperation to be triggered upon the completion of the network request operations, by make it dependent upon those network request operations. Thus it will only run when all of the network requests are done. Using an NSOperation-based solution enjoys other benefits, too (e.g. you can use setMaxConcurrentOperationCount to let you enjoy concurrency, but not run too many concurrent requests at any given time).
References
Ray Wenderlich's How To Use NSOperations and NSOperationQueues
Defining a Custom Operation Object in Apple's Concurrency Programming Guide

Resources