Objective C: replacing a synchronous request - objective-c

I'm having problem replacing this code.
-(NSMutableArray *) GetPrices: {
NSError *error;
NSURLResponse *response;
NSData *tw_result = [NSURLConnection
sendSynchronousRequest:urlRequest
returningResponse:&response error:&error];
The problem I have is that the function that calls this code process the url and then returns data to a method that calls it.
Previously I used this like so.
ViewController calls a function to gather data by creating an operation queue (so that the UI & main thread are available)
NSOperationQueue *myQueue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self selector:#selector(loadDataWithOperation) object:nil];
[myQueue addOperation:operation];
[operation release];
[myQueue release];
The function in the operation queue calls the method to get data on an object and that method then runs the synchronous URLrequest.
-(void)loadDataWithOperation {
self.sectionPriceArray = [self.myObject GetPrices];
So myObject would return a price array.
I have tried using NSSession but but I can't figure out how to pass the result back as the method terminates prior to getting tw_result from the completion Handler.
Any thoughts I have to do this in Objective C as I don't have permission from the client to convert to swift.
EDIT of Question with more details:
Inside my GetPrices method I have tried
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:urlRequest
completionHandler:^(NSData *tw_result,
NSURLResponse *response,
NSError *error) {
result = [[NSString alloc] initWithData:tw_result encoding:NSUTF8StringEncoding];
NSArray *resultArray = [result componentsSeparatedByString:#"\n"];
}) resume];
But I cannot figure out how to make this work one level up at the calling level.

As #maddy mentioned you're going to want to use a completion block for your getPrices method instead of a return -- returns + async don't mix.
This would be the general form to convert your getPrices method to:
- (void)_getPricesWithCompletion:(void(^)(NSMutableArray *sectionPriceArray))priceCompletion;
This site: http://goshdarnblocksyntax.com has some of the common block syntax declaration usages.
Typically you'd call this async method and then set your iVar in the completion block and reload your associated UI elements after receiving the new data. Something along these lines:
[self _getPricesWithCompletion:^(NSMutableArray *sectionPriceArray) {
self.sectionPriceArray = sectionPriceArray;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// reload any UI elements dependent on your sectionPriceArray here
}];
}];
Now in the example code you show, it seems like you're using an NSOperationQueue to queue up different operations. Things can get a bit more complicated here. Subsequent queue'd operations won't wait on your async operations to finish before executing. So for example, if you have an operation after the getPrices operation which utilizes the result of the fetch of the prices, the iVar will almost definitely not contain the correct data at that point. In this case you'd need to use some sort of semaphore to handle waiting for the async operation to complete before continuing to the operation which depends upon it.
Here's an example of what I mean:
NotProperlyWaiting.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface NotProperlyWaiting : NSObject
#property (strong, nullable) NSMutableArray *sectionPriceArray;
- (void)callOperations;
- (void)fakeServerCallWithCompletion:(void(^)(NSData *tw_result, NSURLResponse *response, NSError *error))completion;
#end
NotProperlyWaiting.m
#import "NotProperlyWaiting.h"
#interface NotProperlyWaiting()
- (void)_getPricesWithCompletion:(void(^)(NSMutableArray *sectionPriceArray))priceCompletion;
- (void)_printPricesArray;
#end
#implementation NotProperlyWaiting
- (instancetype)init {
self = [super init];
if (self) {
_sectionPriceArray = [NSMutableArray array];
}
return self;
}
- (void)callOperations {
// setup our completion block to be passed in (this is what will eventually set the self.sectionPricesArray
void (^pricesCompletion)(NSMutableArray *) = ^ void (NSMutableArray *sectionPricesArrayFromCompletion){
self.sectionPriceArray = sectionPricesArrayFromCompletion;
};
NSOperationQueue *myQueue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(_getPricesWithCompletion:) object:pricesCompletion];
NSInvocationOperation *printOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(_printPricesArray) object:nil];
[myQueue addOperation:operation];
[myQueue addOperation:printOperation];
}
- (void)_getPricesWithCompletion:(void(^)(NSMutableArray *sectionPricesArray))priceCompletion {
[self fakeServerCallWithCompletion:^(NSData *tw_result, NSURLResponse *response, NSError *error) {
// check the error or response or whatever else to verify that the data is legit from your server endpoint here
// then convert the data to your mutable array and pass it through to our completion block
NSString *stringData = [[NSString alloc] initWithData:tw_result encoding:NSUTF8StringEncoding];
NSMutableArray *tempPricesArray = [NSMutableArray arrayWithArray:[stringData componentsSeparatedByString:#"\n"]];
// now our completion block passing in the result prices array
priceCompletion(tempPricesArray);
}];
}
- (void)_printPricesArray {
NSLog(#"NotWaiting -- Prices array : %#", self.sectionPriceArray);
}
// this is a fake version of NSURLSession
- (void)fakeServerCallWithCompletion:(void(^)(NSData *tw_result, NSURLResponse *response, NSError *error))completion {
NSString *fakeServerResponse = #"FirstThing\nSecondThing\nThirdThing";
NSData *fakeData = [fakeServerResponse dataUsingEncoding:NSUTF8StringEncoding];
NSURLResponse *fakeResponse = [[NSURLResponse alloc] init];
NSError *fakeError = [NSError errorWithDomain:#"FakeErrorDomain" code:33 userInfo:nil];
// never call sleep in your own code, this is just to simulate the wait time for the server to return data
sleep(3);
completion(fakeData,fakeResponse,fakeError);
}
NS_ASSUME_NONNULL_END
ProperlyWaiting.h (Subclass of NotProperlyWaiting.h to re-use callOperation and fakeServerCallWithCompletion:)
#import "NotProperlyWaiting.h"
NS_ASSUME_NONNULL_BEGIN
#interface ProperlyWaiting : NotProperlyWaiting
#end
NS_ASSUME_NONNULL_END
ProperlyWaiting.m
#import "ProperlyWaiting.h"
#interface ProperlyWaiting()
- (void)_getPricesWithCompletion:(void(^)(NSMutableArray *sectionPricesArray))priceCompletion;
- (void)_printPricesArray;
#property dispatch_semaphore_t semaphore;
#end
#implementation ProperlyWaiting
- (void)callOperations {
self.semaphore = dispatch_semaphore_create(0);
[super callOperations];
}
// identical implementations to NotProperlyWaiting, but this time we'll use a semaphore to ensure the _printPricesArray waits for the async operation to complete before continuing
- (void)_getPricesWithCompletion:(void(^)(NSMutableArray *sectionPricesArray))priceCompletion {
[self fakeServerCallWithCompletion:^(NSData *tw_result, NSURLResponse *response, NSError *error) {
// check the error or response or whatever else to verify that the data is legit from your server endpoint here
// then convert the data to your mutable array and pass it through to our completion block
NSString *stringData = [[NSString alloc] initWithData:tw_result encoding:NSUTF8StringEncoding];
NSMutableArray *tempPricesArray = [NSMutableArray arrayWithArray:[stringData componentsSeparatedByString:#"\n"]];
// now our completion block passing in the result prices array
priceCompletion(tempPricesArray);
// signal our semaphore to let it know we're done
dispatch_semaphore_signal(self.semaphore);
}];
}
- (void)_printPricesArray {
// wait for the semaphore signal before continuing (so we know the async operation we're waiting on has completed)
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"Waiting -- Prices array : %#", self.sectionPriceArray);
}
#end
With example calls of the class like this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NotProperlyWaiting *notWaiting = [[NotProperlyWaiting alloc] init];
[notWaiting callOperations];
ProperlyWaiting *waiting = [[ProperlyWaiting alloc] init];
[waiting callOperations];
}
The output in the log will be:
NotWaiting -- Prices array : (
)
And then 3 seconds later:
Waiting -- Prices array : (
FirstThing,
SecondThing,
ThirdThing
)
Some additional links to helpful documentation related to this topic:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
https://developer.apple.com/documentation/dispatch/1452955-dispatch_semaphore_create

Related

Try to change variable in singleton but it stays nullable

Just started programming on objective-c and now i have issue with which can't deal by myself. I'm receiving data from asynchronous request and try to delver it to singleton, but it's not changed.
This is where i'm trying to store my data
Data.h
#import <Foundation/Foundation.h>
#interface Data : NSObject
#property (nonatomic, strong) NSDictionary *products;
-(void)setProducts:(NSDictionary *)value;
#end
Data.m
#import "Data.h"
#implementation Data
+(Data *)sharedInstance
{
static Data *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[Data alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if ( self )
{
_products = [[NSDictionary alloc] init];
}
return self;
}
#end
This is the class, where i'm receiving data from server:
ConnectionService.m
- (void)getProductsWithCompletion:(void (^)(NSDictionary *products))completion
{
NSString *urlString = [NSString stringWithFormat:#"serverurl", [[AppDelegate instance]getUrl]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *getData = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSString *rawJson = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *value = [rawJson JSONValue];
completion(value);
}];
[getData resume];
}
This is the class where i'm calling request and try to deliver it to singleton:
viewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance] products]);//all is working, products contains data
}];
// checking received data
NSDictionary *tmp = [[Data sharedInstance] products];
NSLog(#"tmp: %#", tmp); //now it's null
}
The issue is the fact that the request is asynchronous and things aren't happening in the order you expect:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
// (2)
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);//all is working, products contains data
}];
// (1)
NSDictionary *tmp = [[Data sharedInstance]products];
NSLog(#"tmp: %#", tmp); //now it's null
}
In the code you posted, (1) will happen before (2). That's because (2) is part of the completion block and is set to run once the network request has completed and all the data has been parsed and is ready to use. While that asynchronous request is prepared and run in a background thread, the main thread ((1)) continues and executes before the request has taken place.
To resolve the issue, move your logging into the completion routine, or simply remove (1).
Another way is to use protocol, to notify your completion block is finished.So that you can simply do:
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
if(self.delegate){
[self.delegate myNotifyMethod:products];
}
}];
and your protocol method:
-(void)myNotifyMethod:(NSDictionary *)items{
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);
}
You can declare the protocol as:
#protocol MyProtocol <NSObject>
- (void)myNotifyMethod: (NSDictionary *)items;
#end
and set the delegate property as:
#property (nonatomic, weak) id<MyProtocol> delegate;

How can I get NSURLSession to process completionHandler when running as a command-line tool

I am somewhat new to Objective-C. I have some PERL scripts that download files from a site that requires client certificate authentication. I would like to port those scripts from PERL to Objective-C command line tools that I can then run from Terminal. Based on my research I have concluded that NSURLSession should work for what I need to do. However, I have been unable to get NSURLSession to work in my command line tool. It seems to build fine with no errors, but does not return anything from the completionHandler. Further, I have put this same code into a Mac OS X App tool and it seems to work fine.
Here is my main.m file:
#import <Foundation/Foundation.h>
#import "RBC_ConnectDelegate.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
// insert code here...
NSURL *myURL =[NSURL URLWithString:#"http://google.com"];
//[[[RBC_Connect alloc] init] connectGetURLSynch];
RBC_ConnectDelegate *myConnect = [[RBC_ConnectDelegate alloc] init];
[myConnect GetURL2: myURL];
}
return 0;
}
Here is my Implementation File:
#import <Foundation/Foundation.h>
#interface RBC_ConnectDelegate : NSObject
- (void)GetURL2:(NSURL *)myURL;
#property(nonatomic,assign) NSMutableData *receivedData;
//<==== note use assign, not retain
//do not release receivedData in a the dealloc method!
#end
And here is my implementation file:
#import "RBC_ConnectDelegate.h"
#implementation RBC_ConnectDelegate
- (void)GetURL2:(NSURL *)myURL2{
//create semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// Create the request.
NSLog(#"Creating Request");
NSURLRequest *theRequest =
[NSURLRequest requestWithURL:myURL2
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:10.0];
NSLog(#"Creating Session");
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSLog(#"Initializing Data Task");
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithRequest:theRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"CompletionHandler");
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger myStatusCode = [(NSHTTPURLResponse *) response statusCode];
NSLog(#"Status Code: %ld", (long)myStatusCode);
}
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
else
{
NSLog(#"Error");
}
dispatch_semaphore_signal(semaphore);
}];
NSLog(#"Resuming Data Task");
[dataTask resume];
}
#end
As you can see, I am trying to get something very simple working here first, with the idea that I can then build on it. Everything I have looked at suggests this may be related to the fact that NSURLSession runs asynchronously but I have been unable to find a solution that speaks specifically to how to address this issue when building a command-line tool. Any direction anyone can provide would be appreciated.
cheers,
NSOperationQueue's reference says that for +mainQueue, the main thread's run loop controls the execution of operations, so you need a run loop. Add this to the end of your main:
while (1)
{
SInt32 res = 0;
#autoreleasepool
{
res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, DBL_MAX, false);
}
if (kCFRunLoopRunStopped == res || kCFRunLoopRunFinished == res)
break;
}
To call -GetURL2:, use -[NSOperationQueue addOperation:] or dispatch_async. You can stop execution with CFRunLoopStop. I tested with GCD instead of NSOperations but this should work anyway.
Also, you need to call dispatch_semaphore_wait if you want to synchronize after the URL session is complete.

Passing delegate through another object with ARC

I've got 2 classes, MPRequest and MPModel.
The MPModel class has a method to lookup something from the core data store, and if not found, creates an MPRequest to retrieve it via a standard HTTP request (The method in MPModel is static and not and instance method).
What I want is to be able to get a progress of the current HTTP request. I know how to do this, but I'm getting a little stuck on how to inform the view controller. I tried creating a protocol, defining a delegate property in the MPRequest class, altering the method in MPModel to accept this delegate, and in turn passing it to the MPRequest when it is created.
This is fine, however ARC is then releasing this delegate whilst the request is running and thus doesn't do what I want. I'm trying to avoid making my delegate object a strong reference in case it throws up any reference cycles but I don't know any other way of doing this.
To start the request, from my view controller I'm running
[MPModel findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
....
} sortedBy:#"name" ascending:YES delegate:self]
Inside the findAllWithBlock method, I have
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
...
}
And in the MPRequest class I have the following property defined :
#property (nonatomic, weak) NSObject<MPRequestDelegate> *delegate;
Any ideas or suggestions?
As requested, here is some more code on how things are being called :
In the view controller :
[MPPlace findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
_placesController = controller;
[_listView reloadData];
[self addAnnotationsToMap];
[_loadingView stopAnimating];
if (_placesController.fetchedObjects.count > 0) {
// We've got our places, but if they're local copies
// only, new ones may have been added so just update
// our copy
MPSyncEngine *engine = [[MPSyncEngine alloc] initWithClass:[MPPlace class]];
engine.delegate = self;
[engine isReadyToSync:YES];
[[MPSyncManager sharedSyncManager] registerSyncEngine:engine];
[[MPSyncManager sharedSyncManager] sync];
}
} sortedBy:#"name" ascending:YES delegate:self];
Here, self is never going to be released for obvious reasons, so I don't see how this is the problem.
Above, MPPlace is a subclass of MPModel, but the implementation of the findAllWithBlock:sortedBy:ascending:delegate: is entirely in MPModel
The method within MPModel looks like this
NSManagedObjectContext *context = [[MPCoreDataManager sharedInstance] managedObjectContext];
[context performBlockAndWait:^{
__block NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([self class])];
[request setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:key ascending:asc]]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
[controller performFetch:&error];
if (!controller.fetchedObjects || controller.fetchedObjects.count == 0) {
// Nothing found or an error, query the server instead
NSString *url = [NSString stringWithFormat:#"%#%#", kMP_BASE_API_URL, [self baseURL]];
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
if (err) {
block(nil, err);
} else {
NSArray *objects = [self createListWithResponse:resp];
objects = [MPModel saveAllLocally:objects forEntityName:NSStringFromClass([self class])];
[controller performFetch:&error];
block(controller, nil);
}
}];
} else {
// Great, we found something :)
block (controller, nil);
}
}];
The delegate is simply being passed on to the MPRequest object being created. My initial concern was that the MPRequest object being created was being released by ARC (which I guess it probably is) but it didn't fix anything when I changed it. I can't make it an iVar as the method is static.
The submit method of the request looks like this :
_completionBlock = block;
_responseData = [[NSMutableData alloc] init];
[self prepareRequest];
[self prepareRequestHeaders];
_connection = [[NSURLConnection alloc] initWithRequest:_urlRequest
delegate:self];
And when the app starts downloading data, it calls :
[_responseData appendData:data];
[_delegate requestDidReceive:(float)data.length ofTotal:_contentLength];
Where _contentLength is simply a long storing the expected size of the response.
Got it working. It was partly an issue with threading, where the core data thread was ending before my request, me looking at the output from a different request entirely, and the way ARC handles memory in blocks.
Thanks for the help guys

AFNetworking Synchronous Operation in NSOperationQueue on iPhone

My app is working this way :
- create an album and take pictures
- send them on my server
- get an answer / complementary information after picture analysis.
I have some issue with the sending part. Here is the code
#interface SyncAgent : NSObject <SyncTaskDelegate>
#property NSOperationQueue* _queue;
-(void)launchTasks;
#end
#implementation SyncAgent
#synthesize _queue;
- (id)init
{
self = [super init];
if (self) {
self._queue = [[NSOperationQueue alloc] init];
[self._queue setMaxConcurrentOperationCount:1];
}
return self;
}
-(void) launchTasks {
NSMutableArray *tasks = [DataBase getPendingTasks];
for(Task *t in tasks) {
[self._queue addOperation:[[SyncTask alloc] initWithTask:t]];
}
}
#end
and the SyncTask :
#interface SyncTask : NSOperation
#property (strong, atomic) Task *_task;
-(id)initWithTask:(Task *)task;
-(void)mainNewID;
-(void)mainUploadNextPhoto:(NSNumber*)photoSetID;
#end
#implementation SyncTask
#synthesize _task;
-(id)initWithTask:(Task *)task {
if(self = [super init]) {
self._task = task;
}
return self;
}
-(void)main {
NSLog(#"Starting task : %#", [self._task description]);
// checking if everything is ready, sending delegates a message etc
[self mainNewID];
}
-(void)mainNewID {
__block SyncTask *safeSelf = self;
[[WebAPI sharedClient] createNewPhotoSet withErrorBlock:^{
NSLog(#"PhotoSet creation : error")
} andSuccessBlock:^(NSNumber *photoSetID) {
NSLog(#"Photoset creation : id is %d", [photoSetID intValue]);
[safeSelf mainUploadNextPhoto:photoSetID];
}];
}
-(void)mainUploadNextPhoto:(NSNumber*) photoSetID {
//just admit we have it. won't explain here how it's done
NSString *photoPath;
__block SyncTask *safeSelf = self;
[[WebAPI sharedClient] uploadToPhotosetID:photoSetID withPhotoPath:photoPath andErrorBlock:^(NSString *photoPath) {
NSLog(#"Photo upload error : %#", photoPath);
} andSuccessBlock:^(NSString *photoPath) {
NSLog(#"Photo upload ok : %#", photoPath);
//then we delete the file
[safeSelf mainUploadNextPhoto:photoSetID];
}];
}
#end
Every network operations are done using AFNetworking this way :
-(void)myDummyDownload:(void (^)(NSData * data))successBlock
{
AFHTTPClient* _httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://www.google.com/"]];
[_httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
NSMutableURLRequest *request = [_httpClient requestWithMethod:#"GET" path:#"/" nil];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
AFHTTPRequestOperation *operation = [_httpClient HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:^(AFHTTPRequestOperation *operation, id data) {
if(dataBlock)
dataBlock(data);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Cannot download : %#", error);
}];
[operation setShouldExecuteAsBackgroundTaskWithExpirationHandler:^{
NSLog(#"Request time out");
}];
[_httpClient enqueueHTTPRequestOperation:operation];
}
My problem is : my connections are made asynchronously, so every task are launched together without waiting fo the previous to finish even with [self._queue setMaxConcurrentOperationCount:1] in SyncAgent.
Do I need to perform every connection synchronously ? I don't think this is a good idea, because a connection never should be done this way and also because I might use these methods elsewhere and need them to be performed in background, but I cannot find a better way. Any idea ?
Oh and if there is any error/typo in my code, I can assure you it appeared when I tried to summarize it before pasting it, it is working without any problem as of now.
Thanks !
PS: Sorry for the long pastes I couldn't figure out a better way to explain the problem.
EDIT: I found that using a semaphore is easier to set up and to understand : How do I wait for an asynchronously dispatched block to finish?
If you have any control over the server at all, you should really consider creating an API that allows you to upload photos in an arbitrary order, so as to support multiple simultaneous uploads (which can be quite a bit faster for large batches).
But if you must do things synchronized, the easiest way is probably to enqueue new requests in the completion block of the requests. i.e.
// If [operations length] == 1, just enqueue it and skip all of this
NSEnumerator *enumerator = [operations reverseObjectEnumerator];
AFHTTPRequestOperation *currentOperation = nil;
AFHTTPRequestOperation *nextOperation = [enumerator nextObject];
while (nextOperation != nil && (currentOperation = [enumerator nextObject])) {
currentOperation.completionBlock = ^{
[client enqueueHTTPRequestOperation:nextOperation];
}
nextOperation = currentOperation;
}
[client enqueueHTTPRequestOperation:currentOperation];
The following code works for me, but I am not sure of the drawbacks. Take it with a pinch of salt.
- (void) main {
NSCondition* condition = [[NSCondition alloc] init];
__block bool hasData = false;
[condition lock];
[[WebAPI sharedClient] postPath:#"url"
parameters:queryParams
success:^(AFHTTPRequestOperation *operation, id JSON) {
//success code
[condition lock];
hasData = true;
[condition signal];
[condition unlock];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure code
[condition lock];
hasData = true;
[condition signal];
[condition unlock];
}];
while (!hasData) {
[condition wait];
}
[condition unlock];
}

Objective-C: Async/Background POST without using delegate method?

I need to make some POST calls to my server, but I need to not block the main thread. As I understand, NSMutableURLRequest and NSURLConnection are not thread safe, so it is best to use the async method of NSURLConnection.
My question about this is, how I can package it up nicely into a method, instead of having to use the delegate method? I would prefer to do:
NSData *returnedData = [Utility postDataToURL:#"some string of data"];
This is how it is easy done with the following method:
[NSURLConnection sendSynchronousRequest:serviceRequest returningResponse:&serviceResponse error:&serviceError];
It is so nice keeping everything within one method, then just having my data returned from it!
Are there any block based methods for this? It becomes an issue when I need to write methods for about 50 different calls and each one needs to use the same delegate method. Am I going about this the wrong way?
This will only need to be for iOS5.
iOS 5 adds sendAsynchronousRequest:queue:completionHandler: which does what I think you want. I've got my code set up to use that if available, but to fall back on performing a synchronous fetch on a background GCD queue and hopping onto the main thread with the result if it doesn't. The latter will be less power efficient but it's just to maintain legacy support.
if([NSURLConnection respondsToSelector:#selector(sendAsynchronousRequest:queue:completionHandler:)])
{
// we can use the iOS 5 path, so issue the asynchronous request
// and then just do whatever we want to do
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error)
{
[self didLoadData:data];
}];
}
else
{
// fine, we'll have to do a power inefficient iOS 4 implementation;
// hop onto the global dispatch queue...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
// ... perform a blocking, synchronous URL retrieval ...
NSError *error = nil;
NSURLResponse *urlResponse = nil;
NSData *responseData =
[NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
// ... and hop back onto the main queue to handle the result
dispatch_async(dispatch_get_main_queue(),
^{
[self didLoadData:responseData];
});
});
}
In production code you'd actually check the errors and HTTP response codes (as a server 404 response is probably just as much an error from your point of view as a connection failure), obviously.
iOS 5.0 > you can use sendAsynchronousRequest method look at NSURLConnection Class and it uses blocks. If you want to support iOS 4.0 > too then you have to write one of your own block based Asynchronous URL loading which is fairly easy to write. You are better off by using MKNetworkKit.
but I need to not block the main thread. As I understand, NSMutableURLRequest and NSURLConnection are not thread safe, so it is best to use the async method of NSURLConnection.
You don't want to do Synchronous network connection it blocks thread whichever it is called from (its even worse if its main thread). You can do Asynchronous network connection on main thread. If you want to do call NSURLConnection on non-main thread then have to create a RunLoop on that thread (if you don't then the delegate methods of NSURLConnection never gets called).
I had this problem pre-5.0 so I made a little class to handle the NSURLConnection delegate protocol and offer callers an interface with closures:
BetterNSURLConnection.h
#property (retain, nonatomic) NSURLRequest *request;
BetterNSURLConnection.m
#property (retain, nonatomic) NSURLConnection *connection;
#property (retain, nonatomic) NSHTTPURLResponse *response;
#property (retain, nonatomic) NSMutableData *responseData;
#property (copy, nonatomic) void (^completionBlock)(id, NSHTTPURLResponse *);
#property (copy, nonatomic) void (^errorBlock)(NSError *);
... You can add typedefs to make those block signatures prettier...then:
#synthesize connection = _connection;
#synthesize response = _response;
#synthesize responseData = _responseData;
#synthesize completionBlock = _completionBlock;
#synthesize errorBlock = _errorBlock;
#synthesize request=_request;
- (void)startWithCompletion:(void (^)(id, NSHTTPURLResponse *))completionBlock error:(void (^)(NSError *))errorBlock {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.completionBlock = completionBlock;
self.errorBlock = errorBlock;
self.responseData = [NSMutableData data];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
self.connection = connection;
[self.connection start];
[connection release];
}
... then do the delegate like this:
- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSHTTPURLResponse *)response {
[self.responseData setLength:0];
self.response = response;
}
- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data {
[self.responseData appendData:data];
}
- (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.errorBlock(error);
self.connection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (self.response.statusCode >= 400) {
self.errorBlock(error);
} else {
// i do json requests, and call a json parser here, but you might want to do something different
id result = [self parseResponse:self.responseData];
self.completionBlock(result, self.response);
}
self.connection = nil;
}
I use a facade method to op-queue an internal worker which issues the synchronous call. Depending on the rate at which you send the calls, it might work. Example:
// Presented in #interface
-(void)sendPostRequest {
// Last chance to update main thread/UI
NSInvocationOperation *op = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(sendPostRequest_internal) object:nil] autorelease];
[opQueue addOperation:op];
}
// Hidden in #implementation
-(void)sendPostRequest_internal {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURLRequest *request = // yadda, you might use NSURLMutableRequest
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
// process data, retain things as needed, post results using performSelectorOnMainThread:
[pool release];
}
It works pretty well for my purposes but you might need to delve a little deeper in to the async stuff, which really isn't too bad.