RACDisposable is teared down before NSURLSessionDownloadTask finishes - ios

I am fetching a list of images that I want to download from a server. After filtering the list , i add all my network calls to an array of signals and merge them. The thing is that the network calls start, but meanwhile the racdisposable is destroyed and in the destroy method I call cancel on the download task. Calling cancel stops the request and the image is not pulled. Any suggestions on how I should do this? I am pretty new to IOS and ReactiveCocoa
This method starts the requests
- (void)fetchTagsFromServer{
[self CreateIfNotExistsTagsFolder];
//define URL object
NSString *urlString = [NSString stringWithFormat:#"http://studiotest.cloudapp.net:5508/api/sideviewapi/gettags"];
NSURL *url = [NSURL URLWithString:urlString];
//fetch tag list from server. After the json is here, start filtering and pulling the images
//from the server
[[self fetchJSONFromURL:url] subscribeNext:^(id x){
NSDictionary* json = (NSDictionary*)x;
//filter and pull images
[[self handleTagsFromJson:json] subscribeNext:^(id x) {
initTagsDone = false;
} error:^(NSError *error) {
NSLog([error description]);
}] ;
}];
}
This ia a generic method to pull json from server
- (RACSignal *)fetchJSONFromURL:(NSURL *)url {
NSLog(#"Fetching: %#",url.absoluteString);
// RAC signal
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//Create session task
NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (! error) {
NSError *jsonError = nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
if (! jsonError) {
//if json returned with success sent to subscriber
[subscriber sendNext:json];
}
else {
// send json error to subscriber
[subscriber sendError:jsonError];
}
}
else {
// send general error
[subscriber sendError:error];
}
// send request completed
[subscriber sendCompleted];
}];
//Starts the the network request once someone subscribes to the signal.
[dataTask resume];
// Creates and returns an RACDisposable object which handles any cleanup when the signal when it is destroyed.
return [RACDisposable disposableWithBlock:^{
[dataTask cancel];
}];
}] doError:^(NSError *error) {
// log error
NSLog(#"%#",error);
}] ;
}
Here I combine all the signals
(RACSignal *) handleTagsFromJson:(NSDictionary*) json {
//hold tags dto's
NSMutableArray* tags = [[NSMutableArray alloc] init];
NSMutableArray* signals = [[NSMutableArray alloc]init];
//create dto object from json
for (NSDictionary * item in json) {
TagsDTO* dto = [[TagsDTO alloc]init];
dto.Id = [[item valueForKey:#"Id"] intValue];
dto.BigIcon = [item valueForKey:#"BigIcon"];
dto.SmallIcon = [item valueForKey:#"SmallIcon"];
dto.SmallIconRaster = [item valueForKey:#"SmallIconRaster"];
dto.Date = [TagsDTO dateWithJSONString:[item valueForKey:#"Modified"]];
dto.Type = [[item valueForKey:#"Type"] intValue];
[tags addObject:dto];
}
//foreach dto do stuff with db
for (TagsDTO* tagDTO in tags){
//get DB tag by external ID
TAG* dbTag = [self getTagsByExternalID:tagDTO.Id];
//create holder strings for image names
NSString* pickerTag;
NSString* overlay;
NSString* raster;
//if db tag is null create null,else set values from server
if(dbTag == NULL){
TAG* tag = [NSEntityDescription insertNewObjectForEntityForName:#"TAG" inManagedObjectContext:self.managedObjectContext];
tag.externalId = [NSNumber numberWithInt:tagDTO.Id];
tag.pickerITag = pickerTag = tagDTO.BigIcon;
tag.overlayTag = overlay = tagDTO.SmallIcon;
tag.rasterTag = raster = tagDTO.SmallIconRaster;
tag.modified = tagDTO.Date;
tag.type = [NSNumber numberWithInt:tagDTO.Type];
}else{
dbTag.externalId = [NSNumber numberWithInt:tagDTO.Id];
dbTag.pickerITag = pickerTag = tagDTO.BigIcon;
dbTag.overlayTag = overlay = tagDTO.SmallIcon;
dbTag.rasterTag = raster = tagDTO.SmallIconRaster;
dbTag.modified = tagDTO.Date;
dbTag.type = [NSNumber numberWithInt:tagDTO.Type];
}
NSError *error ;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
//start downloading images
//because there are 3 different types for each images, download from coressponding serveer folder
//get picker picture
[signals addObject: [self fetchTagImage:pickerTag area:#"picker"]];
//get overlay picture
[signals addObject: [self fetchTagImage:pickerTag area:#"overlay"]];
//get raster picture
[signals addObject:[self fetchTagImage:pickerTag area:#"raster"]];
}
return [RACSignal combineLatest:signals];
}
And this is the signal for pulling the images from the server
(RACSignal *) fetchTagImage:(NSString*)tag area: (NSString*) area {
//create Url object, area is the folder on the server
NSString *urlStringIcon = [NSString stringWithFormat:#"http://studiotest.cloudapp.net:5508/content/uploads/%#/%#",area,tag];
NSURL *urlIcon = [NSURL URLWithString:urlStringIcon];
NSLog(#"Fetching: %#",urlIcon.absoluteString);
// RAC signal
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//Create session task
NSLog(#"internal image fetch: %#",urlIcon.absoluteString);
NSURLSessionDownloadTask* dataTask = [self.session downloadTaskWithURL:urlIcon completionHandler:^(NSURL*location, NSURLResponse *response, NSError *error) {
if(error == nil || error.code == NSURLErrorCancelled)
{
NSLog(#"Temporary file =%#",location);
NSError *err = nil;
//create file mananger
NSFileManager *fileManager = [NSFileManager defaultManager];
//get app document folder path
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//create actual store path
NSString *dataPath = [docsDir stringByAppendingPathComponent:#"/tags"];
//compile filename
NSString* filename = [NSString stringWithFormat:#"%#-%#",area,[response suggestedFilename]];
//create URL to move temporary item to
NSURL *docsDirURL = [NSURL fileURLWithPath:[dataPath stringByAppendingPathComponent:filename]];
if ([fileManager moveItemAtURL:location
toURL:docsDirURL
error: &err])
{
[subscriber sendNext:filename];
NSLog(#"File is saved to =%#",docsDir);
}
else
{
[subscriber sendError:err];
NSLog(#"failed to move: %#",[err userInfo]);
}
}else {
// send general error
[subscriber sendError:error];
}
// send request completed
[subscriber sendCompleted];
}];
//Starts the the network request once someone subscribes to the signal.
[dataTask resume];
// Creates and returns an RACDisposable object which handles any cleanup when the signal when it is destroyed.
return [RACDisposable disposableWithBlock:^{
[dataTask cancel];
}];
}] doError:^(NSError *error) {
// log error
NSLog(#"%#",error);
}];
}

Let's check a couple things first:
racdisposable is destroyed
It's OK for a RACDisposable to be deallocated. Note that that doesn't call the disposable's dispose block.
I don't see any obvious reason that the disposable would be disposed of. Is the signal erroring? Could you set a breakpoint and see the stack trace that calls the cancel?

Related

EXC_BAD_ACCESS NSLog(#"Error decoding JSON data : %#", error.localizedDescription); on OpenWeather API

I am trying to use a location-based Weather app and it is crashing as soon as I open the view for that page.
This is the method;
- (WeatherModel *) parseWeatherData: (NSData *) data {
WeatherModel *weatherModel = [[WeatherModel alloc] init];
NSError *error;
id jsonObject = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingAllowFragments error: &error];
if (error) {
NSLog(#"Error decoding JSON data : %#", error.localizedDescription);
[self->_delegate didFailedWithError: error];
return nil;
}
if ([jsonObject isKindOfClass: [NSDictionary class]]) {
NSMutableDictionary *dicJsonObject = [[NSMutableDictionary alloc] initWithDictionary: jsonObject];
NSString *cityName = [dicJsonObject valueForKey: #"name"];
double temperature = [[[dicJsonObject valueForKey: #"main"] valueForKey: #"temp"] doubleValue];
NSMutableArray *arrWeatherData = [[NSMutableArray alloc] initWithArray: [dicJsonObject valueForKey: #"weather"]];
weatherModel.strCityName = cityName;
weatherModel.temperature = temperature;
weatherModel.weatherConditionID = [[[arrWeatherData objectAtIndex: 0] valueForKey: #"id"] intValue];
}
return weatherModel;
}
It is crashing on this line;
if (error) {
NSLog(#"Error decoding JSON data : %#", error.localizedDescription);
[self->_delegate didFailedWithError: error];
return nil;
}
If I comment out the NSLog section and [self->_delegate didFailedWithError: error];
the app does not crash, but then does not function as expected either...
The second part of the crash using debug is here;
- (void) fetchWeatherForCity: (NSString *) cityName {
_strWeatherURL = [NSString stringWithFormat: #"https://api.openweathermap.org/data/2.5/weather?q=%#&appid=bfea07812845ff9cb7e", cityName];
NSURL *weatherURL = [[NSURL alloc] initWithString: _strWeatherURL];
NSURLSessionConfiguration *urlSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration: urlSessionConfiguration];
NSURLSessionDataTask *task = [urlSession dataTaskWithURL: weatherURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(#"Error fetching weather data : %#", error);
[self->_delegate didFailedWithError: error];
return;
}
WeatherModel *weatherModel = [[WeatherModel alloc] init];
weatherModel = [self parseWeatherData: data];
if (weatherModel != nil) {
[self->_delegate didUpdateWeather: weatherModel];
}
}];
[task resume];
}
On this line;
weatherModel = [self parseWeatherData: data];

How to integrate NSUrlsession in ios

Hi I am very new to ios and in my app I am using NSUrlSession for integrating services.
Here my main problem is when I get a response from the server, I can't handle them properly.
When I get a correct response, then see the below json stucture:-
responseObject = {
{
Name = Broad;
DeptNo = A00;
BatchNo = 23;
DeptId = 120;
},
{
Name = James;
DeptNo = B00;
BatchNo = 23;
DeptId = 123;
},
}
when I get a wrong response, see the below json stucture:-
responseObject = {
error = 1;
message = "Invalid Code";
}
when I get a correct response from the server, I am getting an exception in my below if block(like __NSCFArray objectForKey:]: unrecognized selector sent to instance 0x1611c200') and when I get a wrong response then T get exception in my else block
Please help me how to handle them
my code:-
(void) GetCallService1: (id)MainResponse{
dispatch_async(dispatch_get_main_queue(), ^{
NameArray = [[NSMutableArray alloc]init];
IdArray = [[NSMutableArray alloc]init];
if([MainResponse objectForKey:#"error"] != nil)
{
NSLog(#"No data available");
}
else{
for (NSDictionary *obj in MainResponse) {
if([obj objectForKey:#"Name"] && [obj objectForKey:#"DeptNo"]) {
NSString * Name = [obj objectForKey:#"Name"];
[NameArray addObject:Name];
NSString * Id = [obj objectForKey:#"id"];
[IdArray addObject:Id];
}
}
}
});
}
1)Change Your implementation like below
2)I checked is it dictionary type & error key has some value
3)Earlier you were calling objectForKey on Array, therefore it was crashing
-(void) GetCallService1: (id)MainResponse{
dispatch_async(dispatch_get_main_queue(), ^{
NameArray = [[NSMutableArray alloc]init];
IdArray = [[NSMutableArray alloc]init];
//here I checked is it dictionary type & error key has some value
if ([MainResponse isKindOfClass:[NSDictionary class ]] &&[MainResponse objectForKey:#"error"])
{
NSLog(#"No data available");
}
else{
for (NSDictionary *obj in MainResponse) {
if([obj objectForKey:#"Name"] && [obj objectForKey:#"DeptNo"]) {
NSString * Name = [obj objectForKey:#"Name"];
[NameArray addObject:Name];
NSString * Id = [obj objectForKey:#"id"];
[IdArray addObject:Id];
}
}
}
});
}
Try this:
//Result Block
typedef void (^ResultBlock)(id, NSError*);
//URL request
-(void)requestURL:(NSURLRequest *)request withResult:(ResultBlock)resultHandler{
//URLSession
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
if(!error){
NSError *jsonError = nil;
id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
if([result isKindOfClass:[NSArray class]]){
//Success
resultHandler(result,nil);
}
else if([result isKindOfClass:[NSDictionary class]]){
if([[result objectForKey:#"error"] integerValue]){
//Failure.
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:[result objectForKey:#"message"] forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:#"Error" code:100 userInfo:errorDetail];
resultHandler(nil, errorDetail);
}
}
}
}];
[task resume];
}
//Call your requestURL method:
[self requestURL:request withResult:^(id result, NSError *error){
if(!error){
//Success, Read & update your list
}
else{
//Error
// NSLog(error.localizedDescription());
}
}];

block in ios: what is this method map: before a block?

I don't understand what map: is, in the tutorial using blocks, I could not find a map method in RACSignal (link) or RACDisposable (link) .
- (RACSignal *)fetchJSONFromURL:(NSURL *)url {
NSLog(#"Fetching: %#",url.absoluteString);
// 1
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 2
NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// TODO: Handle retrieved data
}];
// 3
[dataTask resume];
// 4
return [RACDisposable disposableWithBlock:^{
[dataTask cancel];
}];
}] doError:^(NSError *error) {
// 5
NSLog(#"%#",error);
}];
}
- (RACSignal *)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate {
// 1
NSString *urlString = [NSString stringWithFormat:#"http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=imperial",coordinate.latitude, coordinate.longitude];
NSURL *url = [NSURL URLWithString:urlString];
// 2
return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
// 3
return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:json error:nil];
}];
}
The link of the tutorial from raywenderlich website : link
Thanks
//...'map' callback
- (RACSignal *)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate {
// 1
NSString *urlString = [NSString stringWithFormat:#"http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=imperial",coordinate.latitude, coordinate.longitude];
NSURL *url = [NSURL URLWithString:urlString];
// 2
return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
// 3
return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:json error:nil];
}];
}
//... 'map' method
- (instancetype)map:(id (^)(id value))block {
NSCParameterAssert(block != nil);
Class class = self.class;
return [[self flattenMap:^(id value) {
return [class return:block(value)];
}] setNameWithFormat:#"[%#] -map:", self.name];
}
apply the block to every item in the given array and then return the results of each mapping
It's defined in RACSignal's superclass, RACStream:
/// Maps `block` across the values in the receiver.
///
/// This corresponds to the `Select` method in Rx.
///
/// Returns a new stream with the mapped values.
- (instancetype)map:(id (^)(id value))block;

authorizeRequest oAuth not called from another class?

I'm currently using Oauth2SampleTouch by Google, so people can log-in with their google accounts into my app. However whenever I call a method from The SampleRootViewController it doesn't go through the authorizeRequest method (only if I call it from another class.).
Here's the method in SampleRootViewController that I'm calling form another class.(the user is already logged in by this time)
-(NSString *)hasLikedVideo:(NSString *)videoID {
liked = #"NULL";
NSString *clientID = #"myClientID";
NSString *clientSecret = #"myClientSecret";
self.auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:clientID
clientSecret:clientSecret];
NSString *urlStr = [NSString stringWithFormat:#"https://www.googleapis.com/youtube/v3/videos/getRating?id=%#&key=AIzaSyB437bMtpbJh-OrkieCDRtYLe6L1Ijb3Ww", videoID];
NSLog(#"URL FOR LIKE : %# auth:(%#)", urlStr, self.auth);
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSLog(#"stage 1");
[self.auth authorizeRequest:request
completionHandler:^(NSError *error) {
if (error == nil) {
// the request has been authorized
NSLog(#"ERROR LOADING AUTH 1 : %#", [error description]);
} else {
NSLog(#"ERROR LOADING AUTH 2 : %#", [error description]);
}
NSLog(#"stage 2");
NSString *output = nil;
if (error) {
output = [error description];
NSLog(#"ERROR FROM LOADING LIKE INFO : %#", output);
} else {
NSLog(#"stage 3");
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
[self displayAlertWithMessage:output];
if (data) {
// API fetch succeeded
NSLog(#"stage 32");
output = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
// NSLog(#"%#", data);
[self displayAlertWithMessage:output];
} else {
NSLog(#"stage 34");
// fetch failed
output = [error description];
[self displayAlertWithMessage:output];
}
}
NSData* json = [output dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *allCourses = [NSJSONSerialization
JSONObjectWithData:json
options:kNilOptions
error:&error];
NSArray *monday = allCourses[#"items"];
for ( NSDictionary *theCourse in monday )
{
liked = theCourse[#"rating"];
NSLog(#"LIKE INSIDE ARRAY : %#", theCourse[#"rating"]);
}
if( error )
{
NSLog(#"%#", [error localizedDescription]);
}
}];
NSLog(#"stage 4");
return liked;
}
The method runs because I can see it log the string, however it doesn't go through the authorizeRequest, it doesn't even print out the error messages. HOWEVER if my viewController is SampleRootViewController and I call the method from itself it works.
So basically
TestViewcontroller calls a method in SampleRootViewController -> doesn't go through authorizeRequest.
SampleRootViewController calls a method in SampleRootViewController (from itself) -> goes through authorizeRequest and works.
EDIT:
I found out what I was doing "wrong"
I was calling the method like this in background
[self performSelectorInBackground:#selector(getAuthDetails) withObject:nil];
instead of
[self getAuthDetails];

Dynamically reference variables/objects/arrays etc

This seems such an easy task, at least it is in VB.net. I simply need to reference an array based on a string that is passed to a method. When a view controller loads a method is called and a string is passed. A URL will be created based on this string and JSON will be fetched from it. What I want is for the method to populate an appropriate array based on this passed string.
Here we see the method "goGetData" being called in class "getData" with one of three string parameters "workshop/speaker/exhibitor":
- (void)viewDidLoad
{
[getData goGetData:#"workshop"];
[getData goGetData:#"speaker"];
[getData goGetData:#"exhibitor"];
getData *getDataInstance = [[getData alloc] init];
NSArray *newTablesArray = getDataInstance.jsonAllTables;
NSLog(#"Json currently = %#", newTablesArray);
[super viewDidLoad];
[[self myTableView]setDelegate:self];
[[self myTableView]setDataSource:self];
arrayTable =[[NSMutableArray alloc]init];
}
For example if "goGetDate" is fired with "speaker" I would need the speaker data to be fetched and then the "_jsonSpeaker" array to be populated. Here is my attempt so far to reference and populate the arrays based on what string was passed in the method call:
#import "getData.h"
#implementation getData
+(void)goGetData:(NSString *)requestedTable
{
getData *getDataInstance = [[getData alloc] init];
[getDataInstance buildArray];
[getDataInstance fetchData:requestedTable];
}
-(void)buildArray{
// I tried putting the arrays in an array but still do no know how to reference them
_jsonAllTables = [[NSMutableArray alloc] initWithObjects:_jsonExhibitor, _jsonSpeaker, _jsonWorkshop, nil];
}
-(void)fetchData:(NSString *)requestedTable{
NSString *varCurrentTable;
varCurrentTable = [NSString stringWithFormat:#"_json%#", requestedTable];
NSString *requestedURL;
requestedURL = [NSString stringWithFormat:#"http://testapi.website.com/api/%#", requestedTable];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:requestedURL]];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (response){
NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response;
if (newResp.statusCode == 200) {
// STUFF FOR TESTING NSLog(#"Response to request: %# is: %i GOOD", requestedURL, newResp.statusCode);
if ([data length] >0 && error == nil)
{
// STUFF FOR TESTING NSUInteger indexOfArray = [_jsonAllTables indexOfObject:varCurrentTable];
// STUFF FOR TESTING NSString *objectAtIndexOfArray = [_jsonAllTables objectAtIndex:indexOfArray];
// This is the part I think I am stuck on:
// "CURRENT TABLE TO BE POPULATED" = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded");
}
else if (error != nil)
{
NSLog(#"Error: %#", error);
}
} else if (newResp.statusCode == 404){
NSLog(#"Response to request: %# is: %i BAD - URL incorrect", requestedURL, newResp.statusCode);
} else {
// add more returned status error handling here
}
}else{
NSLog(#"No response received");
}
}];
}
#end
Thanks,
Added for clarification on what I am trying to achieve: To save a LOT of writing out the same thing over and over is the following possibly in Obj-c (please excuse the mish-mash of languages)
NSArray *ListOfTables = [NSArray arrayWithObjects:#"Speaker", #"Exhibitor", #"Workshop", nil];
For i as int = 0 to ListOfTables.count{
[self fetchData:(ListOfTables.objectAtIndex = i) withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.(ListOfTables.objectAtIndex = i) = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
i++;
Next
};
Notice i don't call a separate method for each table, instead I call the same method but with different table name parameter each time. I can't seem to find a working example with such placeholders in Xcode.
You probably want a method which is asynchronous and returns the result via a completion handler:
typedef void(^completion_t)(NSArray* objects, NSError*error);
-(void)fetchData:(NSString *)tableName
withCompletion:(completion_t)completionHandler;
Usage:
- (void) foo {
[self fetchData:tableName1 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table1 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
[self fetchData:tableName2 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table2 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
[self fetchData:tableName3 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table3 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
}
Implementation:
typedef void(^completion_t)(NSArray* objects, NSError* error);
-(void)fetchData:(NSString *)tableName
withCompletion:(completion_t)completionHandler
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:tableName]];
// Setup HTTP headers, e.g. "Accept: application/json", etc.
...
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSError* err = error;
NSArray* objects; // final result array as a representation of JSON Array
if (response) {
NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response;
if (newResp.statusCode == 200) {
if ([data length] >0 && error == nil)
{
NSError* localError;
objects = ... // Use NSJSONSerialization to obtain a representation
if (objects) {
if (completionHandler) {
completionHandler(object, nil);
}
return;
}
else {
err = localError;
}
}
else {
err = ...
}
}
}
if (objects == nil) {
assert(err);
if (completionHandler) {
completionHandler(nil, err);
}
}
}];
}
Asynchronous Loop
Another example, for loading a bunch of data:
First, implemented a method which is an "asynchronous loop":
typedef void(^completion_t)(id result, NSError* error);
- (void) fetchObjectsWithTableNames:(NSMutableArray*)tableNames
completion:(completion_t)completionHandler;
This method is, itself asynchronous, thus the completion handler.
Usage:
- (void) foo
{
NSArray* tableNames = #[#"A", #"B", #"C"]; // possibly 1000
[self fetchObjectsWithTableNames:[tableNames mutableCopy]:^(id result, NSError*error){
if (error) {
NSLog(#"Error: %#", error);
}
else {
// finished fetching a bunch of datas with result:
NSLog(#"Result: %#", result);
}
}];
}
Implementation
- (void) fetchObjectsWithTableNames:(NSMutableArray*)tableNames
completion:(completion_t)completionHandler;
{
if ([tableNames count] > 0) {
NSString* name = [tableNames firstObject];
[tableNames removeObjectAtIndex:0];
[self fetchData:name withCompletion:^(NSArray* objects, NSError*error){
if (objects) {
[self.tables addObject:objects];
[self fetchObjectsWithTableNames:tableNames completion:completionHandler];
}
else {
// handle error
}
}];
}
else {
// finished
if (completionHandler) {
completionHandler(#"finished", nil);
}
}
}

Resources