NSURLConnection sendAsynchronousRequest wait time in iOS - ios7

I am using NSURLConnection sendAsynchronousRequest to fetch some data from the web server. The issue is sometimes I am able to fetch the data and some times NULL is returned from the web server. When I looked in to the server logs, it has sent the data. I suspect it as a timing issue. Below is my code.
if(netStatus==ReachableViaWiFi || netStatus==ReachableViaWWAN)
{
NSLog(#"YES WIFI");
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[NSURL URLWithString:formattedURL];
[request setURL:[NSURL URLWithString:formattedURL]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *jsonData, NSError *error)
{
progressbar.progress = 0.0;
NSLog(#"Next line of code");
NSLog(#"dataAsString %#", [NSString stringWithUTF8String:[jsonData bytes]]);
if ([NSString stringWithUTF8String:[jsonData bytes]]==NULL)
{
NSLog(#"R U HERE...THE RESULT IS NULL");
UIStoryboard *storybord = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *viewController = [storybord instantiateViewControllerWithIdentifier:#"noserver"];
viewController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:viewController animated:YES];
}
else
{
searchTable.hidden=FALSE;
progressbar.hidden=TRUE;
NSLog(#"R U HERE...THE RESULT IS NOT NULL");
NSError *error1;
dictresult = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error1];
self.searchTable.dataSource=self;
self.searchTable.delegate=self;
[searchTable reloadData];
}
NSLog(#"Asynch request in search results finished!");
if (error) {
} else {
}
}];
}
Is there a default timeout which is set when expecting data from the web server. Can I increase the default timeout or can I instruct to wait for some more time till I receive the data from the server. Please let me know how to accomplish this task. Thanks for your time

Related

How to show JSON data in UIView labels

About every single tutorial and example on the internet I see shows how to fetch JSON from some url and show it in Tableview. This is not my problem I know how to do that with AFNetworking framework or with native APIs.
My problem is that after I have downloaded the JSON, I want to show some of it in my UIView labels. I have actually succeeded doing this when I was trying to find a way around NSURLSession inability to cache in iOS 8. But I didn't realize that it was synchronous.
Factory.m
+ (Factory *)responseJson
{
static Factory *shared = nil;
shared = [[Factory alloc] init];
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *error = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
NSLog(#"error");
} else {
//-- JSON Parsing
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil];
//NSLog(#"Result = %#",result);
shared.responseJson = result;
}
return shared;
}
My question is that is it possible to use for example AFNetwoking to do the same thing? Am I missing some method that I need to call like in case of a TableView
[self.tableView reloadData];
I would like to use that framework because I need to check Reachability and it seems to implement it already.
Edit as asked to show more code
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self factoryLoad];
[self setupView];
}
- (void)factoryLoad
{
Factory *shared = [Factory responseJson];
self.titles = [shared.responseJson valueForKeyPath:#"data.title"];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
- (void)setupView
{
self.issueTitleLabel.text = [self.titles objectAtIndex:0];
}
There are a couple oddities in the code you posted.
Factory, which appears to be a singleton class, should be instantiated inside a dispatch_once to ensure thread safety.
In ViewController.m, you are calling factoryLoad on the main thread, which is subsequently calling sendSynchronousRequest on the main thread. Apple's NSURLConnection Documentation warns against calling this function on the main thread as it blocks the thread, making your application unresponsive to user input.
You should not be passing in nil as the error parameter in NSJSONSerialization JSONObjectWithData:.
In your case I would recommend separating the fetching of data from the construction of your singleton object.
Factory.m
+(Factory *)sharedFactory {
static Factory *sharedFactory = nil;
dispatch_once_t onceToken;
dispatch_once(&onceToken, {
sharedFactory = [[Factory alloc] init];
});
}
-(void)fetchDataInBackgroundWithCompletionHandler:(void(^)(NSURLResponse *response,
NSData *data,
NSError *error)
completion {
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:downloadQueue
completionHandler:completion];
}
Now you should be able to create a reference to the data with a guarantee that the download request has finished and thus the data will exist.
ViewController.m
-(void)factoryLoad {
[[Factory sharedFactory] fetchDataInBackgroundWithCompletionHandler:^(void)(NSURLResponse *response, NSData *data, NSError *error){
if(!error) {
NSError *error2;
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error2];
if(error2){ /* handle error */ }
self.titles = [serializedData valueForKeyPath:#"data.title"];
[Factory sharedFactory].responseJSON = serializedData;
}
else {
// handle error
}
}];
}
This will guarantee that the download has completed before you try to access any of the downloaded information. However, I've left a few things out here, including any sort of activity indicator displaying to the user that the app is doing something important in the background. The rest is, uh, left as an exercise to the reader.
Ok I took a deeper investigation into Morgan Chen's answer and how to block.
The example code took some modification but I think It works as it should and is better code.
In Factory.m
+ (Factory *) sharedInstance
{
static Factory *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
-(void)fetchDataInBackgroundWithCompletionHandler: (void(^)(BOOL success, NSDictionary *data, NSError *error)) block
{
NSString * baseURL = #"http://jsonurl.com/file.json";
AFHTTPRequestOperationManager * manager = [[AFHTTPRequestOperationManager alloc] init];
__weak AFHTTPRequestOperationManager *weakManager = manager;
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(#"internet!");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[operationQueue setSuspended:NO];
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(#"no internet");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[operationQueue setSuspended:YES];
break;
default:
break;
}
}];
[manager.reachabilityManager startMonitoring];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:baseURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (responseObject && [responseObject isKindOfClass:[NSDictionary class]]) {
block(YES, responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { // invalid request.
NSLog(#"%#", error.localizedDescription);
block(NO, nil, error);
}];
}
In ViewController.m I call this method on viewDidLoad
-(void)factoryLoad
{
[[Factory sharedInstance] fetchDataInBackgroundWithCompletionHandler:^(BOOL success, NSDictionary *data, NSError *error) {
if (success) {
NSLog(#"we have stuff");
self.responseData = data;
self.titles = [self.responseData valueForKeyPath:#"data.title"];
[self setupView];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
}];
}

How to get variable which is return from method

I have two methods and I need to use a variable from first as input parameter in the second. How can I do it? My code is :
First method
-(NSString*)getResponseData :(NSString*) apiHttp {
NSString *code = #"&code=";
NSString *finalLink = [[NSString alloc] initWithFormat:#"%#%#",apiHttp,phoneNumber];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:finalLink]];
NSLog(#"%#", finalLink);
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
NSError *error;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
myString = [jsonDict objectForKey:#"result"];
// NSLog(#"%#", myString);
}];
return myString;
}
Second method:
-(void)showCodeView:(NSString*) ifString{
if([ifString isEqualToString:#"200"]){
aPasswordField.hidden = NO;
[aPasswordField setBorderStyle:UITextBorderStyleLine];
aPasswordField.layer.cornerRadius=1.0f;
aPasswordField.layer.masksToBounds=YES;
aPasswordField.layer.borderColor=[[UIColor whiteColor]CGColor];
aPasswordField.layer.borderWidth= 0.8f;
UIColor *color = [UIColor lightTextColor];
aPasswordField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:#"Код" attributes:#{NSForegroundColorAttributeName: color}];
self.aPasswordField.delegate = self;
}
}
And this is how I call them:
[self getResponseData:apiHttp];
[self showCodeView:myString];
So I can't understand why my myString is null after [self getResponseData:apiHttp]; was called even if my method retutns it.
You are calling two methods after another but are missing that the first one is asynchronous.
When you call sendAsynchronousRequest:queue:completionHandler: it will perform the request asynchronously (not waiting) and call the completion block once it has a response. Since the code doesn't wait for this to happen, getResponseData: immediately returns the current value of myString which is nil if it's not set yet.
You can see how this is working by adding a some log statements before and after each method call:
NSLog(#"Before getResponseData:");
[self getResponseData:apiHttp];
NSLog(#"After getResponseData:");
NSLog(#"Before showCodeView:");
[self showCodeView:myString];
NSLog(#"After showCodeView:");
and the same for the asynchronous request
NSLog(#"Before sendAsynchronousRequest:");
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *connectionError) {
NSLog(#"After sendAsynchronousRequest:");
// the rest of the completion block ...
There are many ways to deal with this. One would be to add a block argument for the getResponseData: method that is called from the completion handler of the request.
If you are unused to working with blocks, a simpler but more tightly coupled alternative is to call [self showCodeView:myString]; from inside of the completion handler.
You want to perform showCodeView only when your asynchronous getResponseData finishes, so implement your own rendition of the completion block pattern:
- (void)getResponseData :(NSString*) apiHttp completionHandler:(void (^)(NSDictionary *, NSError *))completion {
NSString *code = #"&code=";
NSString *finalLink = [[NSString alloc] initWithFormat:#"%#%#",apiHttp,phoneNumber];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:finalLink]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (completion) {
if (connectionError) {
completion(nil, connectionError);
} else {
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
completion(json, parseError);
}
}
}];
}
Note, I've eliminated that __block variable and changed the return type to void (since this doesn't return anything ... the value is passed back via the completion block).
You can then do:
[self getResponseData:apiHttp completionHandler:^(NSDictionary *json, NSError *error) {
if (error) {
// handle this however appropriate for your app
} else {
NSString *myString = json[#"result"];
[self showCodeView:myString];
}
}];

NSURLSessionDataTask doesn't update my table

I have a class for get xml and parse it.
+ (instancetype)sharedRssNewsLoader
{
static BGMRssNewsLoader *sharedRssNewsLoader = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedRssNewsLoader = [[self alloc] initPrivate];
});
return sharedRssNewsLoader;
}
- (instancetype)initPrivate
{
self = [super init];
//did the superclass's designated initializer succeed?
if (self)
{
self.rssNewsItems = [[NSMutableArray alloc] init];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:nil];
//RSS call
[self fetchFeed];
}
return self;
}
- (void)fetchFeed
{
NSString *requestString = #"http://xxx...";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
self.rssXmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
DDLogVerbose(#"rssXmlString: %#", self.rssXmlString);
dispatch_sync(dispatch_get_main_queue(), ^{
[self parseXML:self.rssXmlString];
});}
];
[dataTask resume];
}
//parse XML
- (void)parseXML:(NSString *)source
{
NSError *error = nil;
DDXMLDocument *theDocument = [[DDXMLDocument alloc] initWithXMLString:source
options:0
error:&error];
NSArray *xmlItems = [theDocument nodesForXPath:#"//item"
error:&error];
for(DDXMLElement *itemElement in xmlItems)
{
NSString *itemTitleValue = [[[itemElement elementsForName:#"title"] lastObject] stringValue];
NSString *itemDescriptionValue = [[[itemElement elementsForName:#"title"] lastObject] stringValue];
BGMRssNewsEntry *rssEntry = [[BGMRssNewsEntry alloc] initRssNewsEntryWithTitle:itemTitleValue
rssText:itemDescriptionValue
rssUrl:nil
rssDate:nil
isRssRead:NO];
[self.rssNewsItems addObject:rssEntry];
}
}
//if a programmer calls [[BGMItemsStore alloc] init], let him know the error of his ways
- (instancetype) init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"You are wrong! Use +[BBGMRssNewsLoader sharedRssNewsLoader]"
userInfo:nil];
return nil;
}
I would like to show result in the table. So in the other class I do:
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self)
{
//custom initialization
//initialization sharedRssNewsLoader with rssItems array and RSS call
[BGMRssNewsLoader sharedRssNewsLoader];
}
return self;
}
But my table is absolutely clear. I know that I need to switch to main_queue when I am working with NSURLSessionDataTask, I did it. I think that I mess with thread :(
That BGMRssNewsLoader is asynchronously loading the data and in parseXML you are adding entries to the self.rssNewsItems object. But I don't see where it tells the table to reload itself when this asynchronous request done. If your table view's data source tries to immediately retrieve the rssNewsItems array, it will likely be empty until the asynchronous request is complete.
Generally, when you're done loading the data, you'd call [self.tableView reloadData] (either by supplying the tableview as a parameter during the instantiation of the BGMRssNewsLoader, or by having it post a notification that the table view controller is observing, or by supplying a completion block parameter in which you'd perform reloadData).
I'd also suggest temporarily adding a breakpoint or log statement where you add objects to the rssNewsItems array, just to make sure that that the XML feed was successfully retrieved and parsed.
For example, I might retire fetch and define a method like so:
- (void)fetchFeedWithCompletionBlock:(void (^)(NSArray *items, NSError *error))completion
{
NSString *requestString = #"http://xxx...";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
self.rssXmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
DDLogVerbose(#"rssXmlString: %#", self.rssXmlString);
dispatch_sync(dispatch_get_main_queue(), ^{
[self parseXML:self.rssXmlString];
if (completion) {
completion(self.rssNewsItems, nil);
}
});
}];
[dataTask resume];
}
I would, by the way, not have the initPrivate method initiate the fetch. You want the caller to be able to supply whatever completion block it wants.
I'd then have the tableview's controller do something like:
[[BGMRssNewsLoader sharedRssNewsLoader] fetchFeedWithCompletionBlock:^(NSArray *items, NSError *error) {
// use the items array and/or error
[self.tableView reloadData];
}];
Clearly, the fetchFeedWithCompletionBlock should actually detect errors and call the completion block with the appropriate NSError (so the table view can decide how it wants to present the error ... the singleton shouldn't be doing anything with the UI itself), but hopefully this illustrates the idea of a network object that provides a completion block to allow the caller to specify what it wants to do when the asynchronous request is complete.

Activity indicator with SBJson framework

I'm currently working on an app that uses a basic login page to check if the user has access to the app. I've made the login part with help of this tutorial which uses this frame work to use username and password from a simple web script.
I'm hoping that someone else maybe have worked with it or can help me with my issue. I wan't to show an activity indicator, I'm using MBProgressHUD as an activity indicator.
So I've experimented with it but can't get it to show the activity indicator when the app is connecting to the URL. I've done some bad connection simulating with the login process but the activity indicator won't show up when the app is connecting to the URL. It only shows on errors and the only thing that shows any kind of loading on success is that the login buttons pressed state is "active" (blue highlighted) until the loading is done.
So here's my code that runs when the user has typed in username and password and clicks on the login button:
// Login button
- (IBAction)loginBtnClicked:(id)sender
{
// Show the activity indicator
[HUD showUIBlockingIndicatorWithText:#"Loggar in..."];
#try {
if([[userNameTxtField text] isEqualToString:#""] || [[passwordTxtField text] isEqualToString:#""] ) {
// No username or password entered
[self alertStatus:#"Du måste ange användarnamn och lösenord" :#"Något gick fel!"];
// Hide activity indicator
[HUD hideUIBlockingIndicator];
} else {
NSString *post =[[NSString alloc] initWithFormat:#"username=%#&password=%#",[userNameTxtField text],[passwordTxtField text]];
NSLog(#"PostData: %#",post);
NSURL *url=[NSURL URLWithString:#"http://www.nebulon.se/json/sendus/jsonlogin.php"];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
//[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]];
NSError *error = [[NSError alloc] init];
NSHTTPURLResponse *response = nil;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Response code: %d", [response statusCode]);
if ([response statusCode] >=200 && [response statusCode] <300){
NSString *responseData = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
NSLog(#"Response ==> %#", responseData);
SBJsonParser *jsonParser = [SBJsonParser new];
NSDictionary *jsonData = (NSDictionary *) [jsonParser objectWithString:responseData error:nil];
NSLog(#"%#",jsonData);
NSInteger success = [(NSNumber *) [jsonData objectForKey:#"success"] integerValue];
NSLog(#"%d",success);
if(success == 1){
// Login success, grant user access to app
NSLog(#"Login SUCCESS");
[self loginSuccess];
// Hide activity indicator
[HUD hideUIBlockingIndicator];
// Store username
NSString *userName = [userNameTxtField text];
NSUserDefaults *UserDefaults = [NSUserDefaults standardUserDefaults];
[UserDefaults setObject:userName forKey:#"userName"];
[UserDefaults synchronize];
[self dismissViewControllerAnimated:NO completion:nil];
} else {
// Login error
NSString *error_msg = (NSString *) [jsonData objectForKey:#"error_message"];
[self alertStatus:error_msg :#"Inloggningen misslyckades"];
[self loginFailed];
// Hide activity indicator
[HUD hideUIBlockingIndicator];
}
} else {
// Login error
if (error) NSLog(#"Error: %#", error);
[self alertStatus:#"Ingen nätverksuppkoppling hittades." :#"Ett fel har inträffat!"];
[self loginFailed];
// Hide activity indicator
[HUD hideUIBlockingIndicator];
}
}
}
#catch (NSException * e) {
// Login error
NSLog(#"Exception: %#", e);
[self alertStatus:#"Inloggningen misslyckades." :#"Ett fel har inträffat!"];
[self loginFailed];
// Hide activity indicator
[HUD hideUIBlockingIndicator];
}
}
I believe the issue is due to the use of sendSynchronousRequest:returningResponse:error:
This method will be blocking the main/UI thread so the HUD never actually gets a chance to show until the method has returned, at which point the code continues and the HUD is hidden.
I think you should be looking at using an asynchronous request. and implementing the NSURLConnection delegate methods.
EDIT: Added code sample.
Assuming you're targeting iOS 5 and higher you can use the following code snippet which takes advantage of blocks with sendAsynchronousRequest:queue:completionHandler: and GCD.
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:backgroundQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// NOTE: This block is called on the background queue.
// Use GCD to get back onto the main thread
dispatch_async(dispatch_get_main_queue(), ^{
// This block will process the response and data on the main thread
});
}];
It really is very little work to port your existing code to use this mechanism. If you don't know how blocks work you should read the documentation as they are a very powerful language feature and are being used in an increasing amount of Apple and third-party frameworks.
I would also recommend staying AWAY from third-party networking libraries for now until you understand the nuances that can cause issues such as this.

Use a NSURLconnection for multiple posts to API

I having a situation here where I have some templates (objects), which I want to push to my backend. Now When the users presses the synchronise button, there is a connection to the backend form which I get all the templates. The templates on the IPAD app are compared to those that were transferred from the backend. If a template on the IPAD app has the ID(0), then there is a post to the backend. the backend then returns the saved template with his ID (to add to the local stored template).
Now I think my problem here is that I'm using the same connection (templateupdateconnection => bad name...) for all of those posts to the backend. The actual problem is that in the connectiondidfinishloading method, I only get a response on the last template that was posted to the backend.
Anyone who knows how I can solve this?
Thanks in advance!!
THE CODE
-(void)syncRegistrations:(NSArray *)arrayOfRegistrations{
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:[NSEntityDescription entityForName:#"Registration" inManagedObjectContext:self.managedObjectContext]];
NSError *error;
NSArray *Data = [self.managedObjectContext executeFetchRequest:request error:&error];
BOOL old=FALSE;
for(int registrationCounter = 0; registrationCounter < arrayOfRegistrations.count; registrationCounter ++){
NSDictionary *dictRegistration = [arrayOfRegistrations objectAtIndex:registrationCounter];
for(Registration *registration in Data){
if([dictRegistration objectForKey:#"id"] == registration.id){
old = TRUE;
}
else if ([registration.id intValue]==0){
NSString *jsonRequest = [NSString stringWithFormat:#"{\"form\":%#}",registration.form.id];
NSLog(#" de jsonrequest: %#",jsonRequest);
NSURL *url = [NSURL URLWithString:#"http://mybackend/registrations"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSData *requestData = [NSData dataWithBytes:[jsonRequest UTF8String] length:[jsonRequest length]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%d", [requestData length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody: requestData];
self.registrationtoupdate = registration;
self.registrationUpdateConnection = NULL;
self.registrationUpdateConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[self.registrationUpdateConnection start];
registration.id=[NSNumber numberWithInteger:-1];
[self.managedObjectContext save:&error];
old=TRUE;
}
}
if(old==FALSE){
//nieuwe template toevoegen
Registration *registration = [NSEntityDescription insertNewObjectForEntityForName:#"Registration" inManagedObjectContext:self.managedObjectContext];
registration.id = [dictRegistration objectForKey:#"id"];
registration.form = [self getFormByID:[dictRegistration objectForKey:#"form"]];
[self.managedObjectContext save:&error];
}
old=FALSE;
}
//[self getRC];
[self performSelector:#selector(getRC) withObject:nil afterDelay:3];
}
CONNECTIONDIDFINISHLOADING METHOD
else if([connection isEqual:self.registrationUpdateConnection]){
NSDictionary *dictRegistration = [NSJSONSerialization JSONObjectWithData:self.registrationdata options:kNilOptions error:&error];
NSLog(#"de data van de registratie is: %#",dictRegistration);
NSLog(#"de registration to update is: %#",self.registrationtoupdate);
self.registrationtoupdate.id = [dictRegistration objectForKey:#"id"];
[self.managedObjectContext save:&error];
}
You should encapsulate the NSURLConnection and all relevant state data into a class. Let that class have a start method which asynchronously starts the connection and a completion block which has a parameter result representing the eventual result of the request. That way, you don't mismatch response data with other connections.
Then, in the completion block, reference the corresponding context (registration to update), get the JSON representation and update the context on the correct thread or queue appropriately for the given managed object context (see below "Thread Confinement"):
So, basically:
else if ([registration.id intValue]==0) {
// setup the request
NSMutableURLRequest *request = ...
MyHTTPRequestOperation* op =
[[MyHTTPRequestOperation alloc] initWithRequest:request
completion:^(void)(id result)
{
if (![result isKindOfClass:[NSError class]]) {
assert(result != nil && [result isKindOfClass:[NSData class]]);
NSDictionary* dictRegistration =
[NSJSONSerializationJSONObjectWithData:result
options:kNilOptions
error:&error];
// Todo: check if dictRegistration has the required type
id newRegistrationID = [dictRegistration objectForKey:#"id"];
// Update the model on the thread/queue you defined for the
// NSManagedObjectContext:
[self updateRegistration:registration withID: newRegistrationID];
}
else {
// an error occurred
}
}];
[op start];
registration.id=[NSNumber numberWithInteger:-1];
}
You should be familiar with NSModelObjectContext and "Thread Confinement".
see also: -(id)initWithConcurrencyType:(NSManagedObjectContextConcurrencyType)ct
Core Data Release Notes for OS X v10.7 and iOS 5.0
Core Data Best Practices at WWDC/2012 (requires dev account)
#CouchDeveloper, thanks for putting me on the right track!!
The final solution, wasn't to create a subclass of the NSURLConnection, but just using the completion block in combination with a simple NSURLConnection. check the following code:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil){
NSDictionary *dictrc = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSLog(#"de data van de rc is: %#",dictrc);
rc.id = [dictrc objectForKey:#"id"];
[self.managedObjectContext save:&error];
}
}];