Make an AFNetworking operation complete before next code line after [operation start] and how to handle nested operations - objective-c-blocks

I have a problem with completion block in AFNetworking, my app needs to get json data about car and use id from this json file to request another api to retrieve images before displaying all cars in uitableview. But the uitableview reload is always called before retrieving information so it displays empty table.
I have a Model.m
typedef void (^CompletionBlock)(NSArray *results);
-(void)getPhotosWithCompletionBlock: (CompletionBlock)completionBlock failureBlock: (FailureBlock)failureBlock{
NSURLRequest *request = [NSURLRequest alloc] initWithURL:[NSURL alloc] initWithString:
[NSString stringWithFormat:#"api.getPhoto"]]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
completionBlock(JSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
failureBlock(JSON);
}];
[operation start];
}
ModelViewController.m
- (void)getAllModels{
NSURLRequest *request = ...;
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
for(NSDictionary *model in JSON){
Model *model = [Model alloc] initWithId:...;
[model getPhotosWithCompletionBlock: ^(NSArray *results){
model.photos = results;
}failureBlock:^(NSError *error) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
}
[_tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
But [_tableView reload] is always called before completionBlock of getPhotos method finish so there's an empty tableView, so how to make completion block finish before calling reload tableView method? I can put reload method inside completion block but it forces tableView reload many times makes app very unresponsive, I have 1 more method to retrieve model info so I cannot put reload method inside every completion blocks.
P/S: any idea for my situation without using nested afnetworking operations?

This is not an AFNetworking related question, rather a general Objective-C or
asynchronous question.
You may try to reload the table view whenever the images of one model have been loaded:
- (void)getAllModels{
NSURLRequest *request = ...;
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
for(NSDictionary *model in JSON){
Model *model = [Model alloc] initWithId:...;
[model getPhotosWithCompletionBlock: ^(NSArray *results){
dispatch_async(dispatch_get_main_queue(), ^{
model.photos = results;
[self.tableView reloadData];
});
}failureBlock:^(NSError *error) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
You likely need to fix some edge cases when an error occurs.
If there are performance issues, you need to check where they occur. One usual suspect is the creation of UIImages on the main thread. You may schedule them to another embedded async task whose completion block then forces an update of the table view, or perform this in your getPhotosWithCompletionBock method.
You may also consider to be more explicit about which cell you need to update instead just simply sending reloadData: which will also reload images which are already up to date.

Related

Setting object property inside a block trouble on Objective-C

I'm starting to learn Objective-C for iOS Development, and a got a issue that is driving me crazy.
All that I want is to do a request, retrieve e JSON and then set this JSON into an instance property.
-(NSArray *) retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
return _JSONObjectsCollection;
}
-(void) setJSONOBjectsCollectionAttribute: (NSArray *) arrayWithCollection
{
NSLog(#"Outside Method %#", arrayWithCollection);
self.JSONObjectsCollection = arrayWithCollection;
}
However, my self.JSONObjectsCollection property are ok inside the block, but outside is always null.
Can you help me guys ?
It's because the setting of JSONObjectsCollection happens asynchronously. So your method is returning JSONObjectsCollection before it is set.
Thus, it might look like:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
// do here whatever you want to do now that you have your array, e.g.
//
// [self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
}
Note, retrieveAtEndpoint now has a void return type, but in the completion block, I'm invoking whatever code you want to perform once the JSON objects collection has been updated.
If this is a method inside your model object, but you want to provide an interface by which the view controller can supply a block of code that should be executed upon successful retrieval of the JSON, use a completion block:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(void (^)(NSError *error))completion
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
if (completion)
{
completion(nil);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
if (completion)
{
completion(error);
}
}];
[operation start];
}
Or, if you want to simplify your use of a block parameter, you can define a type for the completion block at the start of your model object's .h file (before the #interface block):
typedef void (^RetrievalCompleteBlock)(NSError *);
And then the method is simplified a bit:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(RetrievalCompleteBlock)completion
{
// the code here is like it is above
}
Anyway, regardless of whether you use the typedef or not, the view controller could do something like:
ModelObject *object = ...
NSString *rootNode = ...
[object retrieveAtEndpoint:url withRootNode:rootNode completion:^(NSError *error) {
if (error)
{
// handle the error any way you want, such as
NSLog(#"%s: retrieveAtEndPoint error: %#", __FUNCTION__, error);
}
else
{
// do whatever you want upon successful retrieval of the JSON here
}
}];
The details here will vary based upon how your view controller is accessing the model object, knows that the root node should be, etc. I often will include another parameter to my completion block which is the data being retrieved, but given that you updated your model object and can access it that way, perhaps that's not necessary. I simply don't have enough details about your implementation to know what is right here, so I kept my implementation as minimalist as possible.
But hopefully this illustrates the idea. Give your retrieveAtEndpoint method a completion block parameter, which lets the view controller specify what it wants to do upon completion (or failure) of the communication with the server.

AFJsonRequest and Unit-Testing needs for synchronous request,

I'm trying to do some unit-testing on my code.
I'm facing an issue, unit-testing fonctions aren't waiting for the asynchronous request to be finished. I'd like to wait for it only if i'm testing it, and not when i run it.
I figured out how to check for this :
if([[[[NSProcessInfo processInfo] environment] objectForKey:#"TARGET"] isEqualToString:#"TEST"])
With the respective environnement variables set in my project.
But i can't find a proper way to wait for it. I tryed with a semaphore, and that works, but i'm wandering if there's any other way which would be easier, and reader-friendly.
EDIT :
Finally, i did it like this : I guess i could have do something better, but that works and looks fair enough to me.
static int statusCode;
- (void)requestContent:(NSString*)urlString
{
statusCode = 0;
NSLog(#"Request Content");
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self ParseGoogleImageSearchResponse:JSON];
statusCode = 1;
NSLog(#"Done");
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
NSLog(#"%#", [error userInfo]);
statusCode = 2;
}];
[operation start];
if([[[[NSProcessInfo processInfo] environment] objectForKey:#"TARGET"] isEqualToString:#"TEST"])
{
while(statusCode == 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
NSLog(#"WAITING");
}
}
}
One way to accomplish async unit tests for AFNetworking calls is to use GHUnit.
GHUnit handles spinning the runloop and waiting for async operations to finish automatically, so your unit test code can be much more readable.
Here is a basic tutorial that covers using AFNetworking, GHUnit, and CocoaPods all together.
Here is a basic test written using AFNetworking and GHUnit.
- (void)test
{
[self prepare];
__block NSError *responseError = nil;
__block id resposeObject = nil;
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
resposeObject = JSON;
[self notify:kGHUnitWaitStatusSuccess forSelector:_cmd];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
responseError = error
[self notify:kGHUnitWaitStatusSuccess forSelector:_cmd];
}];
[operation start];
// Wait for the async activity to complete
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:kNetworkTimeout];
// Check Error
GHAssertNil(responseError, #"");
GHAssertNotNil(resposeObservation, #"Response Observation is nil");
// Validate data...
}
Hope that helps!

Using AFJSONRequestOperation On For Loop on iPhone SDK

I have this code that I want to eager load using AFJSONRequestOperation before continuing with the for loop but somehow it doesn't work. The code below is inside my for loop. How can I prevent my loop from proceeding even if the request is not yet complete? This will help me place all the values got from a success request inside an array in the order I wanted it.
This is the code:
NSURL *url = [NSURL URLWithString:[nowShowingTitleListArray objectAtIndex:i]];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:theRequest
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
// Code when success
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
NSLog(#"Request Failed with Error: %#", [error localizedDescription]);
}];
[operation start];
Currently, I'm doing this to ensure that they'll be fetched and added to my array in order:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[nowShowingTitleListArray objectAtIndex:i]]];
NSError *error;
NSDictionary *JSONDict = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
if (!JSONDict)
{
NSLog(#"Request Failed with Error: %#", [error localizedDescription]);
}
else
{
// Rest of code
}
This will also help me minimize some of the crash issues I'm having with the request when the network connection suddenly drops out.
Why you are you fetching your data from the same URL twice?
Once using AFJSONRequestOperation and then dataWithContentsOfURL:
Are you doing this in the same for loop?
You already have your JSON object in success block
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
// Code when success
// write success code here
}
you can call AFJSONRequestOperation in loop for multiple network requests.

Pass Data from one Method to another

I using AFNetworking to populate a UITableView with youtube videos. YouTube API only allows maximum 50 result for each request. So I have to use multiple URLs to get more than 50 results.
I created a Method which will do AFNetworkings AFJSONRequestOperation on the given URLs.
I think the UITable is getting created before i receive the JSON data. Everything was working perfectly before i created the Method.
This the first time i have created a Method. I have been trying to load more than 50 youtube videos on a UITable for the last few days.Please have a look at my code.
Here is my code, you can also download the entire project from
QQViewController.m
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//[super viewDidLoad];
NSString *urlAsString = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50";
// I am not sure how i am supposed to populate the uitableview with the second link :(
NSString *urlAsString2 = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50&start-index=51";
self.myurl = [NSURL URLWithString:urlAsString];
[self getJSONfromURL:self.myurl];
[self.tableView reloadData];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.videoMetaData = [self.myJSON valueForKeyPath:#"items.video"];
NSLog(#" QQVC video Meta Data %#", self.videoMetaData);
self.allThumbnails = [self.myJSON valueForKeyPath:#"data.items.video.thumbnail"];
// The table need to be reloaded or else we will get an empty table.
[self.tableView reloadData]; // Must Reload
// NSLog(#" video Meta Data %#", self.videoMetaData);
}
// Here is the Method
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// call delegate or processing method on success
// [self.myJSON = (NSArray *)JSON];
self.myJSON = [JSON valueForKey:#"data"];
[self.tableView reloadData];
//NSLog(#" in get JSon method %#", self.myJSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
There were several problems in the getJSONfromURL: method. The main problem was that you were defining self.myJSON as [JSON valueForKey:#"data"], but then defining self.allThumbnails as [self.myJSON valueForKeyPath:#"data.items.video.thumbnail"] -- you used "data" again, so self.thumbnails was null. This seems to work ok:
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.myJSON = [JSON valueForKey:#"data"];
self.allThumbnails = [self.myJSON valueForKeyPath:#"items.video.thumbnail"];
self.videoMetaData = [self.myJSON valueForKeyPath:#"items.video"];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
If you want to load date from multiple URLs, you can do it like this. I added a counter to keep track of the progress through the array of URL strings. The table is reloaded after each download, so it doesn't have to wait for the whole thing to complete before some data shows up.
#interface QWViewController ()
#property (strong,nonatomic) NSArray *urlStrings;
#end
#implementation QWViewController {
int counter;
}
-(void)viewDidLoad {
[super viewDidLoad];
counter = 0;
NSString *urlAsString = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50";
NSString *urlAsString2 = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50&start-index=51";
self.urlStrings = #[urlAsString,urlAsString2];
self.allThumbnails = [NSMutableArray array];
self.videoMetaData = [NSMutableArray array];
[self getJSONFromURL:self.urlStrings[0]];
}
-(void)getJSONFromURL:(NSString *) urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.myJSON = [JSON valueForKey:#"data"];
[self.allThumbnails addObjectsFromArray:[self.myJSON valueForKeyPath:#"items.video.thumbnail"]];
[self.videoMetaData addObjectsFromArray:[self.myJSON valueForKeyPath:#"items.video"]];
[self.tableView reloadData];
counter += 1;
if (counter < self.urlStrings.count) [self getJSONFromURL:self.urlStrings[counter]];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
Move your reload tableview call to here.
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// call delegate or processing method on success
// [self.myJSON = (NSArray *)JSON];
self.myJSON = [JSON valueForKey:#"data"];
[self.tableView reloadData];
//NSLog(#" in get JSon method %#", self.myJSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}

Waiting for completion block to complete in an AFNetworking request

I am making a JSON request with AFNetworking and then call [operation waitUntilFinished] to wait on the operation and the success or failure blocks. But, it seems to fall right though - in terms of the log messages, I get "0", "3", "1" instead of "0", "1", "3"
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://google.com"]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:#"query", #"q", nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
NSLog(#"0");
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, id JSON) {
NSLog(#"1");
gotResponse = YES;
} failure:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"2");
gotResponse = YES;
}];
NSLog(#"Starting request");
[operation start];
[operation waitUntilFinished];
NSLog(#"3");
This works by using AFNetworking to set up the requests, but making a synchronous call then handling the completion blocks manually. Very simple. AFNetworking doesn't seem to support this https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ, though the work around is simple enough.
#import "SimpleClient.h"
#import "AFHTTPClient.h"
#import "AFJSONRequestOperation.h"
#import "AFJSONUtilities.h"
#implementation SimpleClient
+ (void) makeRequestTo:(NSString *) urlStr
parameters:(NSDictionary *) params
successCallback:(void (^)(id jsonResponse)) successCallback
errorCallback:(void (^)(NSError * error, NSString *errorMsg)) errorCallback {
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:urlStr];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:[url path] parameters:params];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(error) {
errorCallback(error, nil);
} else {
id JSON = AFJSONDecode(data, &error);
successCallback(JSON);
}
}
#end
That should (almost) work. Your call to
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
should probably not pass [url path] to the path: parameter. In AFNetworking land, that path is everything after the base url (for example the base url could be "http://google.com" and the path "/gmail" or whatever).
That being said, it's probably not a good idea to make the asynchronous operation into a thread-blocking synchronous operation with waitUntilFinished, but I'm sure you have your reasons... ;)
I just had the same problem and found a different solution. I had two operations that depend on each other, but can load in parallel. However, the completion block of the second operation can not be executed before the completion block of the first one has finished.
As Colin pointed out, it might be a bad choice to make a web request block. This was essential to me, so I did it asynchronously.
This is my solution:
// This is our lock
#interface SomeController () {
NSLock *_dataLock;
}
#end
#implementation
// This is just an example, you might as well trigger both operations in separate
// places if you get the locking right
// This might be called e.g. in awakeFromNib
- (void)someStartpoint {
AFJSONRequestOperation *operation1 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url1]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// We're done, we unlock so the next operation can continue its
// completion block
[_dataLock unlock];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// The request has failed, so we need to unlock for the next try
[_dataLock unlock];
}];
AFJSONRequestOperation *operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url2]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// The completion block (or at least the blocking part must be run in a
// separate thread
[NSThread detachNewThreadSelector:#selector(completionBlockOfOperation2:) toTarget:self withObject:data];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// This second operation may fail without affecting the lock
}];
// We need to lock before both operations are started
[_dataLock lock];
// Order does not really matter here
[operation2 start];
[operation1 start];
}
- (void)completionBlockOfOperation2:(id)data {
// We wait for the first operation to finish its completion block
[_dataLock lock];
// It's done, so we can continue
// We need to unlock afterwards, so a next call to one of the operations
// wouldn't deadlock
[_dataLock unlock];
}
#end
Use Delegate method call
Put the method inside block which will call itself when downloading/uploading completes.