I am debugging my IOS5 application on the device for the first time and it is behaving very strangely. My application makes asynchronous http calls. When these calls complete I am posting notifications to handle the returned values. This all works great on the simulator. However on the device, it appears that the connectionDidFinishLoading:connection NSURLConnection delegate method is never called.
What is even stranger is the application then runs the incorrectly notification handler.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[[UIApplication sharedApplication]setNetworkActivityIndicatorVisible:NO];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
SBJsonParser *jsonParser = [[SBJsonParser alloc]init];
NSError *error = nil;
id jsonObject = [jsonParser objectWithString:responseString error:&error];
switch (currentRequestType) {
case USER_EXISTS:{
NSNumber *userExists =[jsonObject objectForKey:#"exists"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"userExistsNote" object:userExists];
}
break;
case REGISTER_USER:{
NSNumber *success =[jsonObject objectForKey:#"success"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"registerUserNote" object:success];
}
case AUTHENTICATE_USER:{
NSNumber *success =[jsonObject objectForKey:#"success"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"authenticateUserNote" object:success];
}
default:
break;
}
}
The above switch statement delegates which notification should be posted. This peice of code never seems to run when debugging on the device however the method listening for the "registerUserNote" notification runs.. though the "authenticateUserNote" should have posted.
Again.. this all works great in the simulator.
Any ideas?
Thanks!
Related
I just built my first app and now I started testing it on my phone. It looks great when I first launch the app after building it, the launch images appears and then my json data is loaded via NSURL and displays properly on the app. But when I close down the app, update the data via php and mysql and re open it the launch image does not appear and my app is not updated. Is it possible to have the app launch like it did when I first launched it, always have launch image and also get the new data?
Here is my code if it helps.
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadJSON];
}
- (void)loadJSON
{
Reachability *networkReachability = [Reachability reachabilityForInternetConnection];
NetworkStatus networkStatus = [networkReachability currentReachabilityStatus];
if (networkStatus == NotReachable) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"Error" message: #"Connection Failed" delegate: self cancelButtonTitle:#"Refresh" otherButtonTitles:nil]; [alert show]; [alert release];
});
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:#"http://url.com/GetData.php"];
NSData *data = [NSData dataWithContentsOfURL:url options:0 error:nil];
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSArray *firstItemArray = array[0];
NSString *yesNoString = firstItemArray[0];
NSString *dateString = firstItemArray[1];
NSString *timeString = firstItemArray[2];
NSString *homeString = firstItemArray[3];
NSString *awayString = firstItemArray[4];
NSString *lastUpdatedString = firstItemArray[5];
dispatch_async(dispatch_get_main_queue(), ^{
self.YesOrNo.text = yesNoString;
self.date.text = [#"For " stringByAppendingString:dateString];
self.time.text = timeString;
self.home.text = homeString;
self.away.text = awayString;
self.lastUpdated.text = lastUpdatedString;
self.lastUpdatedText.text = #"Last Updated";
self.vs.text = #"vs";
});
});
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
[self loadJSON];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc {
[_YesOrNo release];
[_date release];
[_time release];
[_vs release];
[_home release];
[_away release];
[_lastUpdatedText release];
[_lastUpdated release];
[super dealloc];
}
#end
If someone can point me in the right direction, that would be great and appreciated, thanks.
When you hit the round home button when running an app, it just puts it into the background, where it continues to run in a type of catatonic state. When you tap its icon again, it just wakes up, and doesn't re-launch. If you'd like to have your app completely quit when the user hits the home button, use the info.plist option "UIApplicationExitsOnSuspend" also known as "Application does not run in background." Set this to YES in your info.plist, and you'll get a fresh start every time. You can access this by clicking on your project in Xcode in the Project Navigator mode, and select the "info" tab at the top middle.
As it is your first app, you should try reading about the various devices and os versions.
Read about various application states, also the methods that are present in the AppDelegateClass, that get called when the app enters into various states,try reading about them.
So what has happened in your case is the device that you are using is Multitasking one. So when you press the home button or the sleep button the game goes to background, and is not killed. So next time when you tap on the application icon on your device, it brings it back to the foreground and does not relaunch it hence your Viewdidload method won't be called and your changes won't get reflected.
So, now to terminate your app, you can go through this link
http://www.gottabemobile.com/2013/02/10/how-to-close-apps-on-iphone/
Hope this helps.
I'm doing this test app where I want to receive notification when the iPod changes the now playing item (song), the test is working nice while app is in foreground but as soon as the app goes to the background it stop getting notifications which is OK, when I tap on the app again (comes to foreground) I get all notifications according to all the times the now playing changed while the app was in background but everytime I'm getting the same song information, so how can I get the correct song information for each notification?
This is the test I did, in the AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
MPMusicPlayerController *player = [MPMusicPlayerController iPodMusicPlayer];
[notificationCenter addObserver:self
selector:#selector(nowPlayingItemChanged:)
name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification
object:player];
[player beginGeneratingPlaybackNotifications];
return YES;
}
-(void) nowPlayingItemChanged:(NSNotification *)notification {
MPMusicPlayerController *player = (MPMusicPlayerController *)notification.object;
MPMediaItem *song = [player nowPlayingItem];
if (song) {
NSString *title = [song valueForProperty:MPMediaItemPropertyTitle];
NSString *album = [song valueForProperty:MPMediaItemPropertyAlbumTitle];
NSString *artist = [song valueForProperty:MPMediaItemPropertyArtist];
NSString *playCount = [song valueForProperty:MPMediaItemPropertyPlayCount];
NSLog(#"title: %#", title);
NSLog(#"album: %#", album);
NSLog(#"artist: %#", artist);
NSLog(#"playCount: %#", playCount);
}
}
See this post your options in the background are pretty restricted:
StackOverFlow Post
And the Apple Docs regarding that state it is not really possible:
Apple Documentation on Background states
Be sure to remove the observer when going into the background:
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:musicPlayer];[player endGeneratingPlaybackNotifications];
Add it again when entering the foreground.
I would like to know if there's an automatic way of knowing when the connection was recovered.
My app connects to a webservice, lets say the network is not available in that moment so the app won't get the info from the server, but I would like the app to automactily try to reconect to the server if it "feels" that the connection was recovered.
Is there such a callback?
In whatever class you handle your NSURLConnection you need to add some connection check. So below I have posted an example
Create a Reachability instance
Add an observer to the Reachability did change notification
When the connection will change the - (void)networkReachabilityDidChange:(NSNotification *)notification will be fired.
You obviously check the networkStatus before firing off a connection in the first place.
-(id)init
{
self = [super init];
if(self)
{
Reachability* newInternetReachability = [Reachability reachabilityForInternetConnection];
[newInternetReachability startNotifier];
self.networkReachability = newInternetReachability;
networkStatus = [self.networkReachability currentReachabilityStatus];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(networkReachabilityDidChange:) name:kReachabilityChangedNotification object:nil];
}
return self;
}
- (void) startHTTPRequest
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:YOUR_URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:YOUR_REQUEST_TIMEOUT];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest: delegate:self];
}
- (void)networkReachabilityDidChange:(NSNotification *)notification
{
Reachability *currReach = [notification object];
NSParameterAssert([currReach isKindOfClass: [Reachability class]]);
int currStatus = [currReach currentReachabilityStatus];
// Check that current reachability is not the same as the old one
if(currReach != self.networkReachability)
{
switch (currStatus) {
case ReachableViaWiFi:
// fire off connection
[self startHTTPRequest];
break;
case ReachableViaWWAN:
// Fire off connection (3G)
[self startHTTPRequest];
break;
case NotReachable:
// Don't do anything internet not reachable
break;
default:
break;
}
[self updateReachability];
}
This is only a simple example but you probably need to persist the request until the connection has become available so you can fire it off later. This could be done via NSOperationQueue or something similar.
There isn't such a thing from the standard library perspective. You will have to implement that yourself. You could use apple's Reachability code to listen for network changes. So once you receive a notification from the Reachability code saying that the internet is now connected, you could fire off an URL connection. If you need an example I could mock something up quickly for you.
I post the notifications like this in an operation:
DownloadStatus * status = [[DownloadStatus alloc] init];
[status setMessage: #"Download started"];
[status setStarted];
[status setCompleteSize: [filesize intValue]];
[userInfo setValue:status forKey:#"state"];
[[NSNotificationCenter defaultCenter]
postNotificationName:[targetURL absoluteString]
object:nil userInfo:userInfo];
[status release];
DownloadStatus is an object that contains some information abou the download that is being currently downloaded. userInfo is a property of the object that has been initialized in the init part and is kept for the complete duration of the operation. It is created so:
NSDictionary * userInfo = [NSDictionary dictionaryWithObject:targetURL
forKey:#"state"];
"targetURL" is a NSString, I use this just to make sure everything is working fine. When I receive the event - I registered like this:
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(downloadStatusUpdate:)
name:videoUrl
object:nil];
Here "videoUrl" is a string that contains the url being downloaded, so that I will receive notification about an url I'm waiting to see downloaded.
The selector is implemented like this:
- (void) downloadStatusUpdate:(NSNotification*) note {
NSDictionary * ui = note.userInfo; // Tried also [note userInfo]
if ( ui == nil ) {
DLog(#"Received an update message without userInfo!");
return;
}
DownloadStatus * state = [[ui allValues] objectAtIndex:0];
if ( state == nil ) {
DLog(#"Received notification without state!");
return;
}
DLog(#"Status message: %#", state.message);
[state release], state = nil;
[ui release], ui = nil; }
But this selector always receives a null userInfo. What am I doing wrong?
MrWHO
One way or another, you seem to be initialising your userInfo object incorrectly. The line as given:
NSDictionary * userInfo = [NSDictionary dictionaryWithObject:targetURL
forKey:#"state"];
Would create an autoreleased NSDictionary and store it to a local variable. The value would not be propagated up to your member variable.
Supposing that's a snippet, followed by e.g.
self.userInfo = userInfo;
to assign the local to the member, retaining it at the same time, then your code should generate an exception at this line:
[userInfo setValue:status forKey:#"state"];
Since it attempts to mutate an immutable object. It's therefore much more likely that the value of userInfo isn't stored and you're messaging nil at that point.
So, I would think that — assuming you have userInfo declared as a 'retain' type property, you want to replace:
NSDictionary * userInfo = [NSDictionary dictionaryWithObject:targetURL
forKey:#"state"];
With:
self.userInfo = [NSMutableDictionary dictionaryWithObject:targetURL
forKey:#"state"];
I'm writing test cases for a wrapper class written around ASIHTTPRequest. For reasons I can't determine, my test cases complete with failure before the ASIHTTPRequest finishes.
Here's how the program flow works.
Start in my test case.
Init my http engine object, instruct it to create a new list
Create the new ASIHTTPRequest object and set it up.
Add the request to an operation queue.
Wait until that queue is empty
Check to see if my delegate methods were called and fail the test if they weren't.
Now, most of the time everything works fine and the test passes, but some of the time it fails because my delegate methods were called AFTER the operation queue returned control to my wait method.
Test Case
// Set my flags to 'NO'
- (void)setUp {
requestDidFinish = NO;
requestDidFail = NO;
}
- (void)testCreateList {
NSString *testList = #"{\"title\": \"This is a list\"}";
JKEngine *engine = [[JKEngine alloc] initWithDelegate:self];
NSString *requestIdentifier = [engine createList:jsonString];
[self waitUntilEngineDone:engine];
NSString *responseString = responseString_;
[engine release];
GHAssertNotNil(requestIdentifier, nil);
GHAssertTrue(requestDidFinish, nil);
GHAssertTrue([responseString hasPrefix:#"{\"CreateOrEditListResult\""], nil);
}
// Puts the test into a holding pattern until the http request is done
- (void)waitUntilEngineDone:(JKEngine *)engine {
[engine waitUntilFinishedRunning];
}
// The delegate method called on successful completion
- (void)requestFinished:(NSString *)requestIdentifier withResponse:(NSString *)response {
NSLog(#"request did finish");
requestDidFinish = YES;
responseIdentifier_ = [requestIdentifier retain];
responseString_ = [response retain];
}
Engine Code
- (NSString *)createList:(NSString *)list {
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:url]];
[request addRequestHeader:#"Content-Type" value:kContentType];
[request setRequestMethod:kPOST];
request.delegate = self;
[request appendPostData:[list dataUsingEncoding:NSUTF8StringEncoding]];
NSString *requestIdentifier = [NSString stringWithNewUUID];
[operationQueue_ addOperation:request];
[operationDictionary_ setObject:request forKey:requestIdentifier];
return requestIdentifier;
}
// This is the ASIHTTPRequest delegate method that's called on success
// but it sometimes isn't called until AFTER the operationQueue finishes running
- (void)requestFinished:(ASIHTTPRequest *)request {
DLog([request responseString]);
BOOL canNotifiyDelegate = [self.delegate respondsToSelector:#selector(requestFinished:withResponse:)];
if (canNotifiyDelegate) {
NSArray *keyArray = [operationDictionary_ allKeysForObject:request];
NSString *requestIdentifier = [keyArray objectAtIndex:0];
[operationDictionary_ removeObjectForKey:requestIdentifier];
if ([keyArray count] != 1) {
ALog(#"It looks like a request was added to the operation dictionary multiple times. There's a bug somewhere.", nil);
}
[self.delegate requestFinished:requestIdentifier withResponse:[request responseString]];
}
}
- (void)waitUntilFinishedRunning {
[operationQueue_ waitUntilAllOperationsAreFinished];
}
This is the way ASIHTTPRequest works. Delegate methods are called on the main thread, and calls to delegates do not block the request thread, so it's perfectly possible your delegates will be called after the queue finishes.
ASIHTTPRequest calls delegate methods on the main thread, by default GH-Unit runs its tests on a background thread. I'm still a little hazy on exactly what was going on, but forcing my network tests to run on the main thread fixed the problem.
I implemented the following method in my network test class.
- (BOOL)shouldRunOnMainThread {
return YES;
}