NSURLConnection returns old data although NSURLRequestReloadIgnoringLocalAndRemoteCacheData - objective-c

I’ve got a serious problem. In my app I m downloading a XML-File using NSURLConnection. So far so good. But from time to time, the NSURLConnection returns an old version of the requested file although I set The cachePolicy to NSURLRequestReloadIgnoringLocalAndRemoteCacheData.
The really disgusting part of the bug is, that the connection always return the old version of the file until I active and then deactivate the airplane mode or I rebooted the device.
An other interesting fact is: When I activate Wifi and call the app to reload the file the correct and actual version of the file is returned. But after turning off Wifi the old version is retuned again.
I m really becoming desperate with this problem and hope anybody can help me?
Here is the code from my class, downloading the file:
#import "HTTPReciever.h"
#import "Constants.h"
#interface HTTPReciever ()
#property (strong, nonatomic) NSMutableData *receivedData;
#property (strong, nonatomic) NSString *directoryPath;
#property (strong, nonatomic) NSString *filename;
#property (assign, nonatomic) BOOL storeData;
#end
#implementation HTTPReciever
#synthesize receivedData = _receivedData;
#synthesize delegate = _delegate;
#synthesize directoryPath = _directoryPath;
#synthesize filename = _filename;
- (BOOL)recieveDataFromURL:(NSURL *)url
storeAtDirectoryPath:(NSString *)directoryPath
withName:(NSString *)name
{
[self setDirectoryPath:directoryPath];
[self setFilename:name];
[self setStoreData:YES];
return [self recieveDataFromURL:url];
}
- (BOOL)recieveDataFromURL:(NSURL *)url
{
NSURLRequest *theRequest = [[NSURLRequest alloc] initWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:cDefaultHTTPTimeOutInterval];
self.receivedData = nil;
self.receivedData = [[NSMutableData alloc] initWithLength:0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:theRequest
delegate:self
startImmediately:NO];
if ([NSURLConnection canHandleRequest:theRequest] && connection) {
[connection start];
return YES;
} else {
return NO;
}
}
#pragma mark - NSURLConnectionDelegate protocol
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[self.receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self.receivedData setLength:0];
if ([response isKindOfClass:[NSHTTPURLResponse self]]) {
NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
NSString *modified = [headers objectForKey:#"Last-Modified"];
if (modified) {
// not yet used
}
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (self.storeData) {
[[NSFileManager defaultManager] createDirectoryAtPath:self.directoryPath
withIntermediateDirectories:YES
attributes:nil
error:nil];
[self.receivedData writeToFile:[NSString stringWithFormat:#"%#%#", self.directoryPath, self.filename]
atomically:YES];
}
[self.delegate receiver:self
didFinishLoadingData:self.receivedData
lastVersion:YES];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
[self.delegate receiverFailedLoadingData:self];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
#end

Related

UITableViewController with UISearchDisplayController not reloading data

I have a ViewController class with an appropriate .XIB file. Here is the ViewController code:
ViewController.h:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface ViewController : UIViewController
{
NSArray *news;
NSMutableData *data;
}
#property (strong, nonatomic) IBOutlet UITableView *mainTableView;
#property (strong, nonatomic) IBOutlet UISearchBar *searchBar;
#property (strong, nonatomic) Results *result;
#end
ViewController.m:
#import "ViewController.h"
#import "Results.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize result, mainTableView, searchBar;
-(id) init
{
self = [super initWithNibName:#"ViewController_iPhone" bundle:nil];
if (self)
{
result = [[Results alloc] init];
mainTableView = [[UITableView alloc] init];
[self.mainTableView setDelegate:self];
}
return self;
}
- (void)viewDidLoad
{
self.title = #"Search";
[[UINavigationBar appearance] setTintColor:[UIColor blackColor]];
[super viewDidLoad];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSString *query = searchBar.text;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://samplesite.com/external/metasearchjson.php?query=%#", query]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSArray *responseDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:NULL];
if ([responseDict isKindOfClass:[NSArray class]]) {
news = responseDict;
//[mainTableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
[[self mainTableView] reloadData];
NSLog(#"%#", mainTableView);
} else {
NSLog(#"JSON Error.");
}
}
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:[indexPath row] inSection:0]];
NSString *url = _getString([[news objectAtIndex:[indexPath row]] objectForKey:#"link"]);
[result getURL:url];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self.navigationController pushViewController:result animated:YES];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"The download could not complete - please make sure you're connected to either 3G or Wi-Fi." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [news count];
}
NSString *_getString(id obj)
{
return [obj isKindOfClass:[NSString class]] ? obj : nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MainCell"];
}
cell.detailTextLabel.text = _getString([[news objectAtIndex:indexPath.row] objectForKey:#"metaScore"]);
cell.textLabel.text = _getString([[news objectAtIndex:indexPath.row] objectForKey:#"title"]);
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
return cell;
}
My connections are all working, and the code successfully puts the JSON data into the UITableView.
My problem is, the table view isn't reloading!
I have tried to just load it without a UISearchDisplayController, and it works fine. I'm thinking it's some sort of override. Where my TableView reloads data, that just doesn't work. What's also weird is that if you type in something to the search display, the table view is displayed. What am I doing wrong that the Search Bar doesn't reload the data?
In your search display delegate, you need to implement
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
and return YES.
Try make next steps:
In ViewController.h:
add protocols:
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
add property:
#property (strong, nonatomic) NSArray* news;
then replace in connectionDidFinishLoading: method news = responseDict; as
self.news = responseDict;
then perform reloadData in main thread:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
...
dispatch_async(dispatch_get_main_queue(), ^{
[[self mainTableView] reloadData];
});
in ViewController.m insert line [self.mainTableView setDataSource: self]; :
-(id) init
{
self = [super initWithNibName:#"ViewController_iPhone" bundle:nil];
if (self)
{
...
[self.mainTableView setDelegate:self];
[self.mainTableView setDataSource: self];
}
return self;
}
hope it helps you.

Objective-C issue with connectionDidFinishLoading and Delegate

Am trying to use an API class to fetch data from a URL and then have that data returned to a method inside the ViewController. I have the ViewController call the API method.
Have tried several places on SO already and come up with the following, but it is not working and as an Objective-C noobie have tried reading documentation and such but still not understanding what could be going wrong.
I have a viewcontroller calling an method 'fetch' inside a class 'api'.
I have the connection delegates inside api class and it works ok (correct data is printed inside connectionDidFinishLoading method).
I need a delegate to return the data to a method inside the viewcontroller class.
So far I have
ViewController.h
#import "Api.h"
#interface ViewController : UIViewController <apiDelegate>{
}
- (void)apiSucceeded:(NSString *)jsonString;
- (void)apiFailed:(NSString *)failedMessage;
#end
ViewController.m
#import "ViewController.h"
#import "Api.h"
- (void)apiSucceeded:(NSString *)jsonString {
NSLog(#"WORKED");
}
- (void)apiFailed:(NSString *)failedMessage {
NSLog(#"FAILED");
}
- (void)viewDidLoad {
[super viewDidLoad];
Api* myapi = [[Api alloc]init];
[myapi fetch];
}
#end
Api.h
#protocol apiDelegate
#required
- (void)apiSucceeded:(NSString *)jsonString;
- (void)apiFailed:(NSString *)failedMessage;
#end
#interface Api : NSObject {
id _delegate;
}
#property (nonatomic, assign) id _delegate;
-(void)fetch;
#end
Api.m
#import "Api.h"
#import "ViewController.h"
#synthesize _delegate;
- (void)fetch{
//connection setup....
[NSURLConnection connectionWithRequest:[request autorelease] delegate:self];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.receivedData setLength: 0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// NSLog(#"append data");
[self.receivedData appendData:data];
// NSLog(#"Bytes: %d", [receivedData length]);
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//TODO error handling for connection
if ([_delegate respondsToSelector:#selector(apiFailed:)]) {
[_delegate apiFailed:[error localizedDescription]];
}
NSLog(#"Cannot Connect");
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"DONE. Received Bytes: %d", [self.receivedData length]);
NSString *jsonString = [[NSString alloc] initWithBytes: [receivedData mutableBytes] length:[receivedData length] encoding:NSUTF8StringEncoding];
if ([_delegate respondsToSelector:#selector(apiSucceeded:)]) {
[_delegate apiSucceeded:jsonString];
}
}
#end
There is no error but it just does not run 'apiSucceeded' method.
Please dont misunderstand this as a question regarding 'connectionDidFinishLoading'. That bit works well, its the handing of the data BACK to a ViewController that is causing issues.
Can anybody see what I have done wrong please?
I seems that you forgot to set
myapi.delegate = self;
after
Api* myapi = [[Api alloc]init];

Getting the NSURLConnection delegate callback into the right thread

I can't seem to get a Delegate callback to work when I thread my code. I am trying to call a remote server using NSURLConnection to get an access_token. The access_token is received in connection:didReceiveResponse: delegate. This is not a problem until I thread it. I thought I was passing the correct objects for my delegates, but it does not reach connection:didReceiveResponse:
Can anyone see why connection:didReceiveResponse: in LoginViewController does not get called when I thread the call? Thanks.
LoginViewController.m
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSString *access_token = [self getTokenFromResponse: response];
[self.delegate didGetAccessToken:access_token];
}
- (void)fetchAccessTokenNoUI
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: #"www.mysite.com"]];
[NSURLConnection connectionWithRequest:request delegate:self];
}
AccessToken.h
#interface AccessToken : NSObject
#property (atomic, strong) LoginViewController *loginViewController; // doing this to try to keep it persistent
- (void) fetchAccessTokenWithDelegate: (id <LoginDelegate>)delegate;
#end
AccessToken.m
- (void) fetchAccessTokenWithDelegate: (id < LoginDelegate >)delegate
{
dispatch_queue_t downloadQueue = dispatch_queue_create("Fetch access_token queue", NULL);
dispatch_async(downloadQueue, ^ {
// this works fine if I don't do it in a queue
self.loginViewController = [[LoginViewController alloc] init];
self.loginViewController.delegate = delegate;
[self.loginViewController fetchAccessTokenNoUI];
});
dispatch_release(downloadQueue);
}
CallingClass.m
- (void)didGetAccessToken:(NSString *)access_token
{
if (!access_token)
{
LoginViewController *userProfileViewController = [[LoginViewController alloc] init];
userProfileViewController.delegate = self;
[self.navigationController pushViewController:userProfileViewController animated:YES];
}
}
- (IBAction)favourite:(id)sender
{
AccessToken *accessToken = [[AccessToken alloc] init];
[accessToken fetchAccessTokenWithDelegate:self];
}
So after some more research, I found the solution.
I don't actually need AccessToken.h/m. I thought that the NSURLConnection call would block my UI thread. But NSURLConnection connectionWithRequest:delegate: is automatically run in a separate thread.
So all I have to do is delete AccessToken.h and AccessToken.m. Then, in CallingClass.m, I change favourte: to
- (IBAction)favourite:(id)sender
{
LoginViewController *loginViewController = [[LoginViewController alloc] init];
[loginViewController fetchAccessTokenWithDelegate:self];
}
That's it! Don't need to dispatch_queue_t at all.
Have you put stubs to implement the required protocol methods,
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
Because I had a similar problem which went away after implementing them.

NSUrlConnection in NSThread - No delegate is executed!

I am using an NSURLConnection in an NSThread but none of the NSURLConnection's delegate methods are executed! I have a main method in my NSTread subclass and a while loop that keeps the thread active. Any help?
Sorry for all of this code but I think its the best way to describe my problem. So this is an object that does the async connection calling createConnectionWithPath:userObjectReference
#interface WSDAsyncURLConnection : NSObject
{
NSMutableData *receivedData;
NSDate *connectionTime;
NSURLConnection *connection;
id _theUserObject;
}
#property (nonatomic, retain) NSMutableData *receivedData;
#property (nonatomic, retain) NSDate *connectionTime;
#property (nonatomic, assign) NSURLConnection *connection;
- (void)createConnectionWithPath:(NSString *)thePath userObjectReference:(id)userObject;
#end
#import "WSDAsyncURLConnection.h"
#implementation WSDAsyncURLConnection
#synthesize connectionTime, receivedData, connection;
- (void) terminate
{
if (self.connection) {
[self.connection release];
self.connection = nil;
}
}
- (void) createConnectionWithPath:(NSString *)thePath userObjectReference:(id)userObject;
{
_theUserObject = userObject;
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:thePath]
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:60];
self.connection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:YES];
if (self.connection)
{
/* record the start time of the connection */
self.connectionTime = [NSDate date];
/* create an object to hold the received data */
self.receivedData = [NSMutableData data];
}
}
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.receivedData setLength:0];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
/* appends the new data to the received data */
[self.receivedData appendData:data];
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[self terminate];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
// displays the elapsed time in milliseconds
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:self.connectionTime];
// displayes the length of data received
NSUInteger length = [self.receivedData length];
NSString* aStr = [[NSString alloc] initWithData:receivedData encoding:NSASCIIStringEncoding];
[self terminate];
[[NSNotificationCenter defaultCenter] postNotificationName:WSDAsynchURLConnectionDidFinished
object:_theUserObject
userInfo:[NSDictionary dictionaryWithObject:aStr forKey:#"urlResponseString"]];
NSLog(#"ti=%f, l=%d, response=%#", elapsedTime, length, aStr);
}
#end
This code is mostly from an apple's example project and it works fine outside an NSThread.
But when I use it in the following thread subclass no delegate method is executed !!
#implementation IncomingThread
- (void) main {
NSAutoreleasePool *poool = [[NSAutoreleasePool alloc] init];
// I start the URLConnection here ... But no delegate is executed !
[urlConn createConnectionWithPath:#"http://localhost:8888" userObjectReference:nil];
while (![self isCancelled]) {
[NSThread sleepForTimeInterval:3.];
}
[poool release];
}
- (id) init
{
self = [super init];
if (self != nil) {
urlConn = [[WSDAsyncURLConnection alloc] init];
}
return self;
}
- (void) dealloc {
NSLog(#"deallocating (%#)...", [self className]);
[urlConn release];
[super dealloc];
}
First of all: you don't need to use NSURLConnection in the separate thread. Since it is asynchronous it doesn't block the main thread.
Second: there is not processing of your connection because you always stop the execution of the thread's runloop with this peace of code:
while (![self isCancelled]) {
[NSThread sleepForTimeInterval:3.];
}
From the docs for the sleepForTimeInterval:
No run loop processing occurs while the thread is blocked.
You’re doing this the hard way. NSURLConnection does not play very nice with threads, since it needs a run loop to work. Your thread does not have a run loop and therefore the code does not work. Why not run the connection on the main thread? Or you can wrap the connection in an NSOperation, sample code here. And nowadays you also have the option to use a synchronous connection and dispatch it to a global GCD queue.
Did you remember to assign the delegate?
Something like:
self.connection.delegate = self;
Just because your class WSDAsyncURLConnection implements the delegate methods, doesn't mean they are being called.
Late but it may save others life :)
Solution link is : NSURLConnection delege methods does not work

NSURLConnection in NSOperation

I am writing a code that reads data from a http connection and stores in a byte array.
I have written my own NSOperation class. Reference of the code is
Concurrent Operations Demystified
My HttpWorker class declaration is like
#interface HttpWorker : NSOperation{
NSString *param;
double requestCode;
BOOL isLive;
long initSleep;
BOOL _isFinished;
BOOL _isExecuting;
NSURL *_url;
NSURLConnection *_connection;
NSData *_data;
}
#property (nonatomic, retain) NSString *param;
#property (nonatomic) double requestCode;
#property (nonatomic) BOOL isLive;
#property (nonatomic) long initSleep;
#property (readonly) BOOL isFinished;
#property (readonly) BOOL isExecuting;
#property (readonly, copy) NSURL *url;
#property (nonatomic, retain) NSURLConnection *httpCon;
#property (readonly, retain) NSData *data;
-(id)initWithUrl:(NSURL *)_url;
-(void) setRequestParameters:(NSString *)parameters iRequestCode:(double)iRequestCode initialSleep:(long)initialSleep;
#end
And my HttpWorker.m class is like
#import "HttpWorker.h"
#import "Resources.h"
#implementation HttpWorker
#synthesize param;
#synthesize requestCode;
#synthesize isLive;
#synthesize initSleep;
#synthesize isFinished = _isFinished;
#synthesize isExecuting = _isExecuting;
#synthesize url = _url;
#synthesize data = _data;
-(id) initWithUrl: (NSURL *)Url{
self = [super init];
if(self == nil){
return nil;
}
_url = [Url copy];
_isExecuting = NO;
_isFinished = NO;
return self;
}
-(BOOL) isConcurrent{
return YES;
}
-(void) start{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:_url];
NSLog(#"Connecting... %#",_url);
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately: YES];
if(_connection == nil){
NSLog(#"connection is nil");
}
}
-(void) setRequestParameters:(NSString *)parameters iRequestCode:(double)iRequestCode initialSleep:(long)initialSleep {
self.param = parameters;
self.requestCode = iRequestCode;
self.initSleep = initialSleep;
}
/////////////////////////// delegate methods ///////////////////////////////
-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"receieved response...");
_data = [[NSData alloc] init];
}
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)incomingData {
NSLog(#"receieved data...");
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection {
NSLog(#"connection did finish loading...");
}
#end
Problem is that when i run this code and add httpworker object to the NSOperationQueue, the code runs successfully and _connection is not nil but none of the delegate methods is executed. Can anyone please help?
Thanks and Best Regards...
Your delegate for the connection is "self" (= your NSOperation object). I assume this object is already gone when the connection wants to send messages to the delegate.
Your NSOperation does not have a "main" implementation. Consequently nothing will happen after the thread is started. It will (asynchronously!) fire the NSOperation and quit.
See:
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html#//apple_ref/occ/instm/NSOperation/main
Bottom line: I highly recommend ASIHTTPRequest for this kind of task.
http://allseeing-i.com/ASIHTTPRequest/