Update tableview with regular time intervals in iphone - iphone

I have created a sample application. It has fetched data from server for every 1 sec and updated the result in UITableView. I had already done. But it crashes my app rapidly. What I do is call NSTimer every sec.
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(MainLoop)
userInfo:nil repeats:YES];
In that timer function i have called NSThread function.
-(void)MainLoop
{
if(isPreviousThreadFinished)
{
NSLog(#"Thread Opened");
isPreviousThreadFinished = NO;
[NSThread detachNewThreadSelector:#selector(MainLoopThread) toTarget:self withObject:nil];
}
}
-(void)MainLoopThread
{
MainLoopPool=[[NSAutoreleasePool alloc]init];
//Get data from the server
[self performSelectorOnMainThread:#selector(UpdateTable) withObject:nil waitUntilDone:YES];
[MainLoopPool release];
}
-(void)UpdateTable
{
[self.tableView reloadData];
isPreviousThreadFinished = YES;
NSLog(#"Thread closed");
}
It works fine. And reloads data correctly from the server. I stop NSTimer in viewWillDisappear method. When I go to previous page sometimes it crashes application. In console i have seen the error is given below,
bool _WebTryThreadLock(bool), 0xb2aa410: 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...
What is wrong with my code? Crashes appear randomly.

When I work on my application I discovered very good thing - NSOperation. It's thread safe. You can simply add you operations to the queue without fear to crash. There are a lot of tutorials about NSOperation
http://www.icodeblog.com/2010/03/04/iphone-coding-turbo-charging-your-apps-with-nsoperation/
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
I strongly recommend you use NSOperation-s instead of threads in your project.

Related

Best way to implement by multithreading

I'm kind of new to multithreading, and need some advice.
I'm using ARC in my code.
Problem : I've set up NSTimer in my app to fire every 1 second some method which creates and starts thread like this
//Create a new thread
mSomeThread = [[NSThread alloc] initWithTarget:self selector:#selector(someMethod) object:nil];
//start the thread
[mSomeThread start];
Where mSomeThread is an ivar
Let say the execution of mSomeThread takes more than 1 second, and the mSomeThread is allocated second time, i.e. according to ARC "rules" its released before be allocated one more time.
Does it mean that the first thread doesn't complete and and is forced to quite ?
An NSThread retains itself for the duration of its execution. There's no risk that resetting mSomeThread will cause a running thread to be terminated prematurely.
Yes. If you really need to keep reference to the current thread of execution for your someMethod then you need to wait for it to complete before you can actually start a new thread.
A quick way of doing this would be to add
while ([mSomeThread isExecuting]) {
sleep(1);
}
immediately after [mSomeThread start];.
By the way I'd rather re-implement NSThread and setup a repetitive NSTimer inside its main implementation.
Something like:
- main {
#autoreleasepool {
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(someMethod) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] run];
}
}

Memory management with NSThread

I have an app that needs to signal continuously a word in morse code. I did this by creating an NSThread and running some code inside the selector with a "while loop". Here is the code:
#implementation MorseCode
-(void)startContinuousMorseBroadcast:(NSString *)words{
if (!(threadIsOn)) {
threadIsOn = YES; s
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadSelector:) object:words];
[myThread start];
}
if (morseIsOn) {
morseIsOn = NO;
}
else{
morseIsOn = YES;
}
}
-(void)threadSelector:(NSString *)words{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
while (![myThread isCancelled]) {
// ///it Does some code here
} //end While
NSLog(#"Cleaning the pool");
[pool drain];
}
#end
When exiting the application (the user presses the button), in the applicationDidEnterBackground the following selector is executed:
-(void)cleanUpMorseObject{ //this is defined in the MorseCode class, same as threadSelector
if (threadIsOn) {
NSLog(#"cleanUpMorseObject, threadIsOn");
threadIsOn = NO;
morseIsOn = NO;
[myThread cancel];
[myThread release];
}
}
The application responds correctly to the event, I’ve checked with nslog.
And then [MorseCode release] is called.
The code looks like this:
-(void)applicationDidEnterBackground{ //this happens in the ViewController
[theMorse cleanUpMorseObject]; //theMorse is an instance of MorseCode
[theMorse release];
}
The problem: Although I call [myThread release] and then [theMorse release] the retainCount of the theMorse is still above 0 (It doesn’t call the dealloc).
The Leaks Instrument doesn’t say I have a leak, but if I open and close the application for like 10 times eventually the Iphone resets. Also in the debugger eventually I see the “Received memory warning. Level=2”.
Also I never see the NSLog before the pool drain…
The app doesn't run in the background.
Any ideas? Thank you
You really should schedule the sending of the message on the RunLoop, the probably easiest way being to schedule a timer (repeat infinitely, and short repeat period like FLT_EPSILON or similar) instead of using threads for that.
Working with threads is complicated and as everyone should avoid it (as Apple stated in its Concurrency Programming Guide, and as most documentation said, "Threads are evil" ;)).
That's because multithreading is a vast and complicated subject, that needs synchronizations, resources protection, being aware of dead locks, critical sections & so on, good and adapted memory mgmt, and much much more. In general if you need to do stuff in the background:
Use mechanisms already in place (like asynchronous implementation of some operations and being signalled by delegate methods or notifications) if available
Use methods like performInBackground:
Use NSOperationQueues
Use GCD
And only in last resort and if there are no other options (or for really specific cases), use NSThread.
This will avoid you a lot of issues as all the other, higher APIs will take care of a lot of things for you.
Moreover, using threads for this task like you do is likely to use much more CPU (will probably reach 100% usage quickly) as there won't be any time left for the task scheduler (that also why even GCD that takes care of all stuff like that is way better than NSThreads, and scheduling the sending in the RunLoop is even better for the CPU if you don't need strong RT constraints)
First, retainCount can never return 0. It is a useless method. Don't call it.
Secondly, leaks only detects objects that are no longer referenced. If a thread is still running, it isn't leaked.
Finally, a thread doesn't stop when you call cancel. It just sets a flag that you have to check via isCancelled to see if it is time to stop work in the thread. Are you doing that?
OK -- easy stuff answered. Next? Try build and analyze. Then use the Allocations instrument and turn on reference count tracking. Then see what is calling retain an extra time.
I decided to give up the NSThread class and used another aproach:
-(void)playSOSMorse{
if ([myTimer isValid]) {
[myTimer invalidate];
[myTimer release];
myTimer = nil;
}
myTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:#selector(tymerSelector)
userInfo:nil
repeats:NO] retain];
//the timer calls a selector that performs a selector in background
}
-(void)tymerSelector{
[self performSelectorInBackground:#selector(threadSelector2) withObject:nil];
}
-(void)threadSelector2 {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//some code here
[pool drain];
//calls another selector on the main thread to see if it needs to fire the timer again and restart the cycle
[self performSelectorOnMainThread:#selector(selectorOnMainThread) withObject:nil waitUntilDone:NO];
}
-(void)selectorOnMainThread{
[myTimer invalidate];
[myTimer release];
myTimer = nil;
if (morseIsOn) { //this is a boolean that if it is true (YES) calls the timer again
[self playSOSMorse];
}
}
I hope this helps somebody :)
Thank you

how to stop performing selector in background?

I have some class A. In this class i have a method,
which calls [self performSelectorInBackground:...]. And it starts downloading
some info from internet.
After i tap Home button, then enter the app again, this background method keeps working.
So, if i call this method again, i have bad_access, because background method is already working and i call it twice.
Can i stop performing selector in background of the class A? For example in my applicationDidEnterBackground?
Or can i check, if selector is performing or something?
I found couple things like
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget:a];
[NSObject cancelPreviousPerformRequestsWithTarget:a selector:#selector(startDownload) object:nil];
But they didn't work for me.
So
my objAppDelegate:
#inteface ObjAppDelegate
{
A *a;
}
#implementation ObjAppDelegate
{
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//or it can be didBecomeActive..
//here. check if background task of class A is running, or just stop it ??
}
}
#implementation A
{
//some timer, or event, etc.
-(void)startDownload
{
[self performSelectorInBackground:#selector(runBackgroundTask) withObject:nil];
}
-(void)runBackgroundTask
{
//some network stuff..
}
}
i did it like this:
threadForDownload = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[threadForDownload start];
[self performSelector:#selector(startDownload) onThread:threadForDownload withObject:nil waitUntilDone:NO];
(void)threadMain:(id)data {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (YES) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[pool release];
}
In my startDownload method i look at activity indicator to check, whether
startDownload is already running..
-(void)startDownload
{
if (![[UIApplication sharedApplication] isNetworkActivityIndicatorVisible]) // flag..
{
//....
}
}
// I make visible networkActivityIndicator every time i start downloading
You can easily create a BOOL instance variable to determine whether background task is active.
BOOL isBackgroundTaskRunning;
Then in runBackgroundTask
if (isBackgroundTaskRunning) {
// already running
return;
}
isBackgroundTaskRunning = TRUE;
...
isBackgroundTaskRunning = FALSE;
Here's what to do:
the background task saves its thread to a property somewhere using NSThread currentThread
the background task periodically checks the thread's isCancelled property.
the main thread sends cancel to the thread object saved by the background thread in step 1.
On exit, the background thread sets the property to nil.
All of the operations on the property used to store the thread in have to be protected by #synchronized or equivalent to prevent the main thread from sending cancel to a deallocated thread object.
The background thread can't do IO operations that block for more than a short period of time. In particular, synchronous downloading of URLs using NSURLConnection is out. If you are using NSURLConnection, you'll want to move to the asynchronous methods and a run loop (arguably, in that case, you can do away with the background thread altogether). If you are using POSIX level IO, use poll() with a timeout.
I don't think that it would be save to force the interruption of a method. What you can do is to change the state of your object and check that state inside your method implementation to early return in case of a cancel (but don't forget to release allocated objects).
This is how NSOperationQueue works. From the documentation:
Cancelling an operation does not immediately force it to stop what it is doing. Although respecting the value returned by the isCancelled is expected of all operations, your code must explicitly check the value returned by this method and abort as needed.
Run the method in a background thread, and keep a record of the NSThread. Then later, you can just end the thread.

NSOperations or NSThread for bursts of smaller tasks that continuously cancel each other?

I would like to see if I can make a "search as you type" implementation, against a web service, that is optimized enough for it to run on an iPhone.
The idea is that the user starts typing a word; "Foo", after each new letter I wait XXX ms. to see if they type another letter, if they don't, I call the web service using the word as a parameter.
The web service call and the subsequent parsing of the result I would like to move to a different thread.
I have written a simple SearchWebService class, it has only one public method:
- (void) searchFor:(NSString*) str;
This method tests if a search is already in progress (the user has had a XXX ms. delay in their typing) and subsequently stops that search and starts a new one. When a result is ready a delegate method is called:
- (NSArray*) resultsReady;
I can't figure out how to get this functionality 'threaded'.
If I keep spawning new threads each time a user has a XXX ms. delay in the typing I end up in a bad spot with many threads, especially because I don't need any other search, but the last one.
Instead of spawning threads continuously, I have tried keeping one thread running in the background all the time by:
- (void) keepRunning {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SearchWebService *searchObj = [[SearchWebService alloc] init];
[[NSRunLoop currentRunLoop] run]; //keeps it alive
[searchObj release];
[pool release];
}
But I can't figure out how to access the "searchFor" method in the "searchObj" object, so the above code works and keeps running. I just can't message the searchObj or retrieve the resultReady objects?
Hope someone could point me in the right direction, threading is giving me grief:)
Thank you.
Ok, I spend the last 8 hours reading up on every example out there.
I came to realize that I would have to do some "Proof of Concept" code to see if there even would be a speed problem with building a new thread for "each" keystroke.
It turns out that using NSOperation and NSOperationQueue is more than adequate, both in terms of speed and especially in terms of simplicity and abstraction.
Is called after each keystroke:
- (void) searchFieldChanged:(UITextField*) textField {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSString *searchString = textField.text;
if ([searchString length] > 0) {
[self performSelector:#selector(doSearch:) withObject:textField.text afterDelay:0.8f];
}
}
This is mainly to stop the code form initiating a search for keystrokes that are less than 800 ms. apart.
(I would have that a lot lower if it where not for the small touch keyboard).
If it is allowed to time out, it is time to search.
- (void) doSearch:(NSString*) searchString {
[queue cancelAllOperations];
ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
[queue addOperation:searchOperation];
[searchOperation release];
}
Cancel all operations that is currently in the queue. This is called every time a new search is
started, it makes sure that the search operation already in progress gets closed down in an orderly fashion, it also makes sure that only 1 thread is ever in a "not-cancelled" state.
The implementation for the ISSearchOperation is really simple:
#implementation ISSearchOperation
- (void) dealloc {
[searchTerm release];
[JSONresult release];
[parsedResult release];
[super dealloc];
}
- (id) initWithSearchTerm:(NSString*) searchString {
if (self = [super init]) {
[self setSearchTerm:searchString];
}
return self;
}
- (void) main {
if ([self isCancelled]) return;
[self setJSONresult:/*do webservice call synchronously*/];
if ([self isCancelled]) return;
[self setParsedResult:/*parse JSON result*/];
if ([self isCancelled]) return;
[self performSelectorOnMainThread:#selector(searchDataReady:) withObject:self.parsedResult waitUntilDone:YES];
}
#end
There are two major steps, the downloading of the data from the web service and the parsing.
After each I check to see if the search has been canceled by [NSOperationQueue cancelAllOperations] if it has, then we return and the object is nicely cleaned up in the dealloc method.
I will probably have to build in some sort of time out for both the web service and the parsing, to prevent the queue from choking on a KIA object.
But for now this is actually lightning fast, in my test I am searching an 16.000 entries dictionary and having Xcode NSLog it to the screen (slows things down nicely), each 800 ms. I issue a new search string via a timer and thereby canceling the old before it has finished its NSLog results to screen loop.
NSOperationQueue handles this with no glitches and never more that a few ms. of two threads being executed. The UI is completely unaffected by the above tasks running in the background.

connectionDidFinishLoading - how to force update UIView?

I am able to download a ZIP file from the internet. Post processing is done in connectionDidFinishLoading and works OK except no UIView elements are updated. For example, I set statusUpdate.text = #"Uncompressing file" but that change does not appear until after connectionDidFinishLoading has completed. Similarly, the UIProgressView and UIActivityIndicatorView objects are not updated until this method ends.
Is there any way to force an update of the UIView from within this method? I tried setting [self.view setNeedsDisplay] but that didn't work. It appears to be running in the main thread. All other commands here work just fine - the only problem is updating the UI.
Thanks!
Update: here is the code that is NOT updating the UIVIEW:
-(void)viewWillAppear:(BOOL)animated {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(processUpdate:) userInfo:nil repeats:YES];
downloadComplete = NO;
statusText.text = #"";
}
-(void)processUpdate:(NSTimer *)theTimer {
if (! downloadComplete) {
return;
}
[timer invalidate];
statusText.text = #"Processing update file.";
progress.progress = 0.0;
totalFiles = [newFiles count];
for (id fileName in newFiles) {
count++;
progress.progress = (float)count / (float)totalFiles;
// ... process code goes here ...
}
}
At then end of processUpdate, I set downloadComplete = YES. This builds & runs without errors and works as intended except nothing updates in the UIVIEW until after processUpdate completes, then everything updates at once.
Thanks for your help so far!
As Niels said, you must return control to the run loop if you want to see views update. But don't start detaching new threads unless you really need to. I recommend this approach:
- (void)connectionDidFinishLoading:(NSConnection *)connection {
statusUpdate.text = #"Uncompressing file";
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
}
- (void)doUncompress {
// Do work in 100 ms chunks
BOOL isFinished = NO;
NSDate *breakTime = [NSDate dateWithTimeIntervalSinceNow:100];
while (!isFinished && [breakTime timeIntervalSinceNow] > 0) {
// do some work
}
if (! isFinished) {
statusUpdate.text = // here you could update with % complete
// better yet, update a progress bar
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
} else {
statusUpdate.text = #"Done!";
// clean up
}
}
The basic idea is that you do work in small chunks. You return from your method to allow the run loop to execute periodically. The calls to performSelector: will ensure that control eventually comes back to your object.
Note that a risk of doing this is that a user could press a button or interact with the UI in some way that you might not expect. It may be helpful to call UIApplication's beginIgnoringInteractionEvents to ignore input while you're working... unless you want to be really nice and offer a cancel button that sets a flag that you check in your doUncompress method...
You could also try running the run loop yourself, calling [[NSRunLoop currentRunLoop] runUntilDate:...] every so often, but I've never tried that in my own code.
While you are in connectionDidFinishLoading nothing else happens in the application run loop. Control needs to be passed back to the run loop so it can orchestrate the UI updating.
Just flag the data transfer as complete and the views for updating. Defer any heavy processing of the downloaded data to it's own thread.
The application will call your views back letting them refresh their contents later in the run loop. Implement drawRect on your own custom views as appropriate.
If you're receiving connectionDidFinishLoading in the main thread, you're out of luck. Unless you return from this method, nothing will be refreshed in the UI.
On the other hand, if you run the connection in a separate thread, then you can safely update the UI using the following code:
UIProgressView *prog = ... <your progress view reference> ...
[prog performSelectorOnMainThread:#selector(setProgress:)
withObject:[NSNumber numberWithFloat:0.5f]
waitUntilDone:NO];
Be careful not to update the UI from a non-main thread - always use the performSelectorOnMainThread method!
Do exactly what you're doing with the timer, just dispatch your processing code to a new thread with ConnectionDidFinish:. Timers can update the UI since they're run from the main thread.
The problem turned out to that the UI isn't updated in a for() loop. See the answer in this thread for a simple solution!