iPhone UILabel not updating - iphone

I know there are lots of similar questions floating around, but none of the answers seem to fix my problem. I have an app that uses an NSURLConnection to download a file, and then does some calculations on the downloaded file. I set up a UILabel to display the current loading status (eg: "Loading file", "Parsing file"). I update the UILabel in the didReceiveResponse and connectionDidFinishLoading function of the NSURLConnection delegate, as well as some other places in my code. I update it by calling the following function:
[self performSelectorOnMainThread:#selector(updateProgress) withObject:nil waitUntilDone:NO]
where -(void)updateProgress is a function I defined to call [theLabel setNeedsDisplay]. I NSLog'd it, like
NSLog(#"theLabel: %#\n",theLabel.text);
and the information is updated correctly, but the label doesn't actually update in the view. Also, updateProgress is only called AFTER everything is loaded. It updates the label THEN, which is hardly useful. Any suggestions?

The NSURLConnection is blocking the main thread (no updates will be performed on the view until it finishes).
You can perform updateProgress in the background:
[self performSelectorInBackground:#selector(updateProgress) withObject:nil]
The first line of updateProgress should be:
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc]init];
The last lines should be:
[pool release];
pool = nil;
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
Of course, you can also perform the NSURLConnection in the background. Then you can update the label on the main thread.

Related

UIImageView only displaying intermittently

I'm using the following code to display a page number, when the user switches pages in my iPad app. The number is supposed to show up in a nice transparent gray pane (similar to the "Build Succeeded" message in XCode). However, the image view only shows up about 5% of the time. (I haven't added the code to put a number in the pane yet). The NSLog() message appears every time. I've also set a breakpoint and stepped through, and the UIImageView code is getting called. Sadly, I can't step into the framework to see what it's doing.
The variables used are all locals or constants, so I doubt it has anything to do with a race condition. Also, I can wait minutes between clicks and not get an image, or press the button rapidly and get multiple stacked images (they're transparent, so it's easy to notice a stack).
I tried running a setNeedsDisplay on self.view after the addSubview: (even though I'm pretty sure addSubview: does it), but that didn't do anything.
Calling displayPageNumber: directly, without threading, doesn't make it appear consistently either.
Any ideas?
#define PageDisplayTime 0.5
#define PageDisplayImageName #"PageIndicator.png"
#define PageDisplayImage [UIImage imageNamed: PageDisplayImageName]
...
[NSThread detachNewThreadSelector: #selector(displayPageNumber:)
toTarget: self withObject: index];
...
- (void) displayPageNumber: (NSNumber*) _pageIndex
{
NSLog(#"Page Number: '%d'.", [_pageIndex integerValue] + 1);
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
UIImageView* glassPaneView = [[UIImageView alloc] initWithImage: PageDisplayImage];
glassPaneView.center = CGPointMake(300.0, 300.0);
[self.view addSubview: glassPaneView];
[NSThread sleepForTimeInterval: PageDisplayTime];
[glassPaneView removeFromSuperview];
[glassPaneView release];
[pool release];
}
AFAIK all UI coding must be done on the main thread. doing it on the background thread will at best have unpredictable results, at worse will crash.
Calling the method directly (thus in the main thread) doesn't work because the sleep just stops the current thread, so it is not able to do anything including drawing until you remove the view, thus preventing it from showing.
You have to refactor the method into multiple methods called on the main thread. The first just adds the subview and sets a timer, the timer handling method then removes the subview.
I figured out the issue. In case others run into this, here it is:
The addSubview: / removeFromSuperview pair with the sleepForTimeInterval: between them are basically useless. I knew sleeping blocked the thread, but apparently this thread needs to be unblocked to update the view. I assumed (incorrectly it appears) that the view update happened on the main thread.
Here's my replacement solution:
unhide the subview in the main thread and call detachNewThreadSelector: with a message that sleeps for X seconds and then hides the subview.
And I'm going to add an NSLocked counter that gets incremented on unhide, and decremented in the hider message, but only hides when the counter is down to 0. This will allow repeated activations to extend the duration.

Execute a method that takes a second to complete without temporarily halting code

Basically, i have a method that takes a few seconds to complete as it copies some files using NSFileManager. This is invoked on the touchesMoved event when the user picks up a draggable UIView icon. However, there's a slight delay before the icon's position is updated. I'm guessing it's waiting for that method to copy it's files before continuing. The method HAS to be triggered on touchesMoved, so please don't suggest moving it.
How can i execute a method that takes about a second to complete, without holding up the code?
(..and don't worry the copy method doesn't get repeatedly called from the touchesMoved event)
You could perform the task in the background using performSelectorInBackground:...:
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html
This prevent that selector from blocking the main thread.
Example:
[self performSelectorInBackground:#selector(myMethod) withObject:nil];
Do it in a background thread. Leave the main thread to deal with UI stuff only.
Technically you could divide the copying of files into very small chunks, and tell the current NSRunLoop to dispatch between each file copy.
But practically just say no to any IO access on the main thread, all IO access should be done in the background. Even the slightest block on the main thread will make the UI stutter and be unresponsive, Android user might accept that, iOS user do not.
Your options are numerous, and easy to implement. You could do a simple performSelector–:
-(void)backgroundWorker {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do your stuff
[pool release];
}
-(void)startDoingIOStuff {
[self performSelectorInBackground:#selector(backgroundWorker)
withObject:nil];
}
You could do it practically inline using a block and GCD:
-(void)startDoingIOStuff {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL),
^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do your stuff
[pool release];
});
}
Or you could use an NSOperation on a NSOperationQueue. I have written a longer blog post on this topic, including source code that is available here: http://blog.jayway.com/2010/08/19/future-cocoa-operation/
Before immediately resorting to a secondary thread, it would certainly be worth a try to use a plain old performSelector on self. For example:
[self peformSelector:#selector(copyFiles) withObject:nil afterDelay:0.0];
Note that this is different from doing:
[self copyFiles];
The peformSelector version basically says "do copyFiles ASAP, OK?", but doesn't block everything while waiting for it to be done. In other words, it's possible that the perform selector version would allow the main event loop to update the UI (thereby preventing the apparent visual lag) before the file copying is actually done.

Yet another question about showing UIActivityIndicator

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?

iPhone: Problems releasing UIViewController in a multithreaded environment

I have a UIViewController and in that controller, i am fetching an image from a URL source. The image is fetched in a separate thread after which the user-interface is updated on the main thread. This controller is displayed as a page in a UIScrollView parent which is implemented to release controllers that are not in view anymore.
When the thread finishes fetching content before the UIViewController is released, everything works fine - but when the user scrolls to another page before the thread finishes, the controller is released and the only handle to the controller is owned by the thread making releaseCount of the controller equals to 1. Now, as soon as the thread drains NSAutoreleasePool, the controller gets releases because the releaseCount becomes 0. At this point, my application crashes and i get the following error message:
bool _WebTryThreadLock(bool), 0x4d99c60: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
The backtrace reveals that the application crashed on the call to [super dealloc] and it makes total sense because the dealloc function must have been triggered by the thread when the pool was drained. My question is, how i can overcome this error and release the controller without leaking memory?
One solution that i tried was to call [self retain] before the pool is drained so that retainCount doesn't fall to zero and then using the following code to release controller in the main thread:
[self performSelectorOnMainThread:#selector(autorelease)
withObject:nil waitUntilDone:NO];
Unfortunately, this did not work out. Below is the function that is executed on a thread:
- (void)thread_fetchContent {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *imgURL = [NSURL URLWithString:#"http://www.domain.com/image.png"];
// UIImage *imgHotspot is declared as private - The image is retained
// here and released as soon as it is assigned to UIImageView
imgHotspot = [[[UIImage alloc] initWithData:
[NSData dataWithContentsOfURL: imgURL]] retain];
if ([self retainCount] == 1) {
[self retain]; // increment retain count ~ workaround
[pool drain]; // drain pool
// this doesn't work - i get the same error
[self performSelectorOnMainThread:#selector(autorelease)
withObject:nil waitUntilDone:NO];
}
else {
// show fetched image on the main thread - this works fine!
[self performSelectorOnMainThread:#selector(showImage)
withObject:nil waitUntilDone:NO];
[pool drain];
}
}
Please help! Thank you in advance.
Yeah it can be really daunting to try and keep threaded stuff in sync.
The use case you describe sounds perfect for an NSOperation.
By using this approach you can have an NSOperationQueue as an ivar in the controller and release this in your controllers dealloc method.
The benefits are many, when the controllers view is visible in the scrollView it (viewWillAppear or loadView) starts retrieving the image using an NSOperation added to an NSOperationQueue, if the user then scrolls away before the operation is done and the NSOperationQueue is released, it will take care of sending a cancel message to all operations and in general close everything down in an orderly fashion.
If this is a central component in your app, which I guess it is since you put thought into releasing things that are "of screen", I would recommend having your controller display a "dummy image" in the loadVIew method and then start a fetch operation in the viewDidLoad. You could subclass NSOperation so that you just send it the URL and let it do its thing.
I did something similar some weeks ago where I had to continuously start threaded operations, but with a large chance the the user would do something that caused these to get canceled. That functionality is "build" into the NSOperation.
NSOperation question

IPhone SDK - Leaking Memory with performSelectorInBackground

Maybe someone can help me with this strange thing:
If a user clicks on a button, a new UITableView is pushed to the navigation controller. This new view is doing some database querying which takes some time. Therefore I wanted to do the loading in background.
What works WITHOUT leaking memory (but freezes the screen until everything is done):
WorkController *tmp=[[WorkController alloc] initWithStyle:UITableViewStyleGrouped];
self.workController=tmp;
[tmp release];
[self.workController loadList]; // Does the DB Query
[self.workController pushViewController:self.workController animated:YES];
Now I tried to do this:
// Show Wait indicator
....
WorkController *tmp=[[WorkController alloc] initWithStyle:UITableViewStyleGrouped];
self.workController=tmp;
[tmp release];
[self performSelectorInBackground:#selector(getController) withObject:nil];
}
-(void) getController {
[self.workController loadList]; // Does the DB Query
[self.navigationController pushViewController:self.workController animated:YES];
}
This also works but is leaking memory and I don't know why !
Can you help ?
By the way - is it possible for an App to get into AppStore with a small memory leak ? Or will this be checked first of all ?
Thanks in advance !
No, small memory leaks will not (most likely) you application to be rejected from appstore.
In your example as you run your method in separate thread you should create and dispose NSAutoreleasePool object for that thread to handle autoreleased objects. Following changes to getController method should do the trick:
-(void) getController {
NSAutoreleasedPool *pool = [[NSAutoreleasedPool alloc] init];
[self.workController loadList]; // Does the DB Query
[self.navigationController pushViewController:self.workController animated:YES];
[pool release];
}
For more details see Autorelease Pools section in memory management guide. Relevant quote from there:
If you spawn a secondary thread, you
must create your own autorelease pool
as soon as the thread begins
executing; otherwise, you will leak
objects. (See “Autorelease Pools and
Threads” for details.)
Btw, you're calling pushViewController: from a background thread. This is bad.
You should only do things to the UI - like pushing view controllers and changing UI items - from the main thread. If you don't, things break.
See the Cocoa Fundamentals Guide section titled "Are the Cocoa Frameworks Thread Safe?": it says "All UIKit objects should be used on the main thread only."