I discovered performSelectorInBackground: a couple days ago, and I immediately knew a spot in my app where this would be perfect.
- (void)activate {
waitForStartCode.text = #"Loading...";
userNotifications.text = #"";
timeRemaining.text = #"";
[loadingNTP startAnimating];
[self performSelectorInBackground:#selector(initializeEverything) withObject:nil];
}
This is called when my view is visible. Before, while the NTP time servers were being connected to using CocoaAsyncSocket, my app froze until this process was completed. I really want a loading view with an animated UIActivityIndicatorView.
Everything in the initializeEverything -(void) works fine, exept the NTP initialization using CocoaAsyncSocket.
[NetworkClock sharedNetworkClock];
I get a :
Even with my unskilled eye, I could tell that CocoaAsyncSocket was not meant to be run in the background.
Is there any way around this?
CocoaAsyncSocket supports asynchronous networking. So in principle you do not need a background thread yourself to prevent your UI from freezing: CocoaAsyncSocket will handle communication in background for you.
I cannot say if you are using the framework in a "blocking" way or you are doing anything else that is making your UI block. But, as I said, in principle you do not need to manage a background thread yourself.
Related
what is the proper way to notify the watchdog that i'm not in an endless loop and that i'm still loading so don't kill my app?
I'm receiving in my crashlogs
Exception Type: 00000020
Exception Codes: 0x8badf00d
and only when running the app from iphone independently from xcode
the code taking time is :
- (void)viewDidLoad {
[super viewDidLoad];
Reachability* reachability = [Reachability sharedReachability];
[reachability setHostName:#"www.apps2you.com"]; // set your host name here
NetworkStatus remoteHostStatus = [reachability remoteHostStatus];
if (remoteHostStatus == ReachableViaWiFiNetwork||remoteHostStatus == ReachableViaCarrierDataNetwork )
{
//getting the xml file and then getting the ad images online to display as splah ads.
}
else {
//or launch the main interface if there's no connectivity.
[self DisplayTabbar];
}
}
thanks.
If you have some initializing that takes a long time then it's best to run it in a new thread (via performSelectorInBackground:withObject:). Your UI would then start in some "locked" state. Create a method that "unlocks" the UI. As the very last action of your background method, run that unlock method via performSelectorOnMainThread:withObject:waitUntilDone:.
It's important not to block the main thread so that the run loop can respond to iOS events. That's why you should avoid sleep or other blocking stuff. It's alright to block in another thread, though. Also, adapting event-based programming methods help a lot.
Update:
Nowadays, it's better to use dispatch_async instead of performSelectorInBackground:withObject:. You still should create a new NSAutoreleasePool inside the dispatch_async block to avoid memory leaks.
I think if watchdog is killing your app, then it takes way too long to load and your users wont wait for it. Look at some memory management techniques and you may have to structure your project better.
There may be a way to tell watchdog not to kill your app, but I guarantee your load times will frustrate your users and you won't be getting the out-reach you want with your app.
I had the UIActivityIndicatorView working fine in simulator and other 3.0 devices in my app. But I found out that it was not spinning (or showing) in the new iphone 4. Basically I need to show the activity indicator when a button is clicked and hide it when the button click event is complete. I was using the approach below.
[NSThread detachNewThreadSelector: #selector(spinBegin) toTarget:self withObject:nil];
from this link. As mentioned, it correctly spins the activity indicator on all except 4.*.. not sure why. To get around this, I also followed another approach something like (from developer.apple.com)
`
(IBAction)syncOnThreadAction:(id)sender
{
[self willStartJob];
[self performSelectorInBackground:
#selector(inThreadStartDoJob:)
withObject:theJobToDo
];
}
(void)inThreadStartDoJob:(id)theJobToDo
{
NSAutoreleasePool * pool;
NSString * status;
pool = [[NSAutoreleasePool alloc] init];
assert(pool != nil);
status = [... do long running job specified by theJobToDo ...]
[self performSelectorOnMainThread:
#selector(didStopJobWithStatus:)
withObject:status
waitUntilDone:NO
];
[pool drain];
}
`
The problem with this was that, it is showing the acitivityVIewIndicator spinning correctly (at least on the simulator) but after it stops, the built in activity indicator in the top bar (where it shows the battery% etc) is still spinning.
I'm new to objective C. I have finished my app completely but for this silly thing. I realize there is no way to display UIActivityView without starting another thread. and finally, just to rant, I don't understand why they have to make it so complicated. I mean they knew it was going to have this problem, why not provide a sample code everyone can use rather than deriving their own solutions.
Finally, can anyone please provide me with a direction or some sample code. I would really appreciate it. I have been searching for a few hours now and have not found anything really that works!
Why are you starting/stopping the indicator on a separate thread? Any methods you send to your UIActivityIndicatorView must be sent on the main (UI) thread.
Any events sent by a button pressed will automatically be run on the main thread. If you're using background threads to complete the process, you could do something like:
- (IBAction)buttonPressed:(id)sender {
// This runs on the main thread
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(inThreadStartDoJob:) withObject:theJobToDo];
}
- (void)inThreadStartDoJob:(id)theJobToDo {
// Set up autorelease pool
...
// Run your long-running action
...
// Stop the spinner. Since we're in a background thread,
// we need to push this to the UI Thread
[activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
Edit: As for the activity indicator in the top bar (where the battery is), doesn't this automatically start/stop based on network activity?
Currently I am retrieving a bunch of images from the internet and scaling them and then displaying them on my view.
The problem is I am doing it in the viewDidLoad method - so when the user taps they have to actually wait for this processing to happen and then the view is shown which causes a slight delay.
Is there anyway I could show the view and then somehow spark off the loading of the images AFTER the user has the view in front of them - similar to how a web page loads?
- (void)configureImages
{
if ([currentHotel.hotelImages count] > 0)
{
imageView1.image = [self getScaledImageFromURL: [currentHotel.hotelImages objectAtIndex:0]];
imageView2.image = [self getScaledImageFromURL: [currentHotel.hotelImages objectAtIndex:1]];
imageView3.image = [self getScaledImageFromURL: [currentHotel.hotelImages objectAtIndex:2]];
}
}
Consider NSOperation/NSOperationQueue, discussed in the Concurrency Programming Guide. There are several links to examples here.
Apps should use the asynchronous networking APIs for all networking to avoid blocking the user experience. It's best to avoid adding threads (such as happens when you use NSOperationQueue) for tasks like networking where the OS already provides async alternatives.
Apple supplies the async NSURLConnection, which works well but is a bit tedious to use. You can add a simple wrapper like gtm-http-fetcher which reduces async fetches to a single line with a callback when the load has finished. That will let you start all the loads in your viewDidLoad: method without stalling the user interface.
How can I delay the app loading to show the splash screen for longer?
You should let the app start as usual then make the first view that appears have the identical image on it as the splash screen. Start a timer and then replace that view with your real application root view after a few seconds.
Deliberately delaying the actual application launch is a big no-no.
UPDATE: No seriously, DON'T do this!
Or us the C function
sleep(9);
Putting this in applicationDidFinishLaunching: will cause you program to pause for 9 seconds, any other integer may be entered as well.
EDIT: I've learned a lot in the past year. Don't do this. The reason being that the springboard will automatically stop the app launching if it takes too long. That timing is poorly documented so even one second can result in the app failing.
This question is similar: splash screen like tap tap revenge 3
Basically, in your applicationDidFinishLaunching:, add an image view on top of other views containing your Default.png.
See the above discussion of why you probably should not delay your app load in this way. But if you happen to have a scenario where sleeping for short duration would be preferable to the overhead of switching out a view, use NSThread's sleepForTimeIntervale instead of sleep(). It's more framework friendly and you have more granular control over the sleep time:
[NSThread sleepForTimeInterval:0.75]
I had a situation where the client had to demo the launch image. So, this was my solution..
- (void)applicationDidBecomeActive:(UIApplication *)application
{
UIImageView *defaultImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default#2x.png"]];
[self.window addSubview:defaultImageView];
sleep(2);
[defaultImageView removeFromSuperview];
[defaultImageView release];
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
You can use it sleep method to get this result "
sleepForTimeInterval
", If you want to get it launch time, do like :
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
[NSThread sleepForTimeInterval:8.0];
}
It will delay the launch by 8 seconds
Warning : But it is not recommended by apple, as it will the watchdod about long time for your app loading, It can kill your app.
But incase if you need it to get some specific screenshot or for some in-house use, you can use to solve for purpose but never in app submission.
I'm working on an app right now that was working fine until I started implementing some threading for background loading of images. Now theres no crashes but the keyboard does not display. That is to say its invisible (I can still type, I just cant see the actual keyboard).
The only thing I've done was implement threading so I'm wondering if I'm somehow messing with the thread the keyboard runs on or something?
The threads I'm calling are like:
[NSThread detachNewThreadSelector:#selector(loadWebView:)
toTarget:self withObject:[NSNumber numberWithInt:pageNum]];
and
[scrollView performSelectorOnMainThread:#selector(addSubview:)
withObject:curWebView waitUntilDone:NO];
Thanks in advance.
UIWebViews (and all UI elements) cannot be used on background threads safely. If you wish to load images in the background, use NSURLConnection's asynchronous loading methods.