I am working on performing some tasks in the background every 60 seconds. The background task is the server requests documents to be downloaded form the website. The main thread/UI seems to be locking when the request is done and I am saving the data to sqlite.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread detachNewThreadSelector:#selector(startTheBackgroundSync) toTarget:self withObject:nil];
[pool release];
- (void)startTheBackgroundSync {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// [self performSelectorInBackground:#selector(moveSynctoBack) withObject:nil];
// [self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
serverSync = [[[ServerSync alloc]init]autorelease];
while (1==1) {
serverSync.delegate = self;
[serverSync syncNow:nil];
[NSThread sleepForTimeInterval:120];
}
[pool release];
[serverSync release];
}
While the looping does not lock up the main thread, but when ASIHTtpRequest finished with the data it locks up the ui for a second.
The finished selector of an ASIHTTPRequest will always be performed on the main thread. Therefore you shouldn't do long running tasks there.
Instead of launching a new thread you can schedule a repeated NSTimer:
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:120 target:self selector:#selector(backgroundSync) userInfo:nil repeats:YES];
... with the following action method:
-(void) backgroundSync
{
ServerSync* serverSync = [[[ServerSync alloc]init]autorelease];
serverSync.delegate = self;
[serverSync syncNow:nil];
}
Be sure that you use the startAsynchronous-method in ServerSync to start the request!
Furthermore I'd recommend to implement a singleton and then use it like that:
-(void)init {
[[ServerSync sharedSync] setDelegate:self];
}
-(void) backgroundSync
{
[[ServerSync sharedSync] syncNow];
}
Related
I have an NSOperation running on a background thread, and I want to change a property of it, a float named runSpeed, upon getting a memory warning on the main thread. The next time the background thread hits runSpeed though, it appears to be the same value it was previously.
How can I make sure it changes in the NSOperation thread when i change it on the main thread?
EDIT:
Didn't post code originally because I assumed I was thinking about it wrong. Here's my code:
AppDelegate
-(void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
NSLog(#"memory warning");
if (dataUpdateIsActive) {
NSLog(#"data update is active");
dataUpdate.runSpeed = 10;
[NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(resetRunSpeed)
userInfo:nil
repeats:NO];
}
}
Then in a method:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
dataUpdate = [[JDataUpdate alloc] init];
[dataUpdate setOldPSC:[oldContext persistentStoreCoordinator]];
[dataUpdate setCurrentPSC:[newContext persistentStoreCoordinator]];
[dataUpdate setRunSpeed:0.5];
[dataUpdate setEntriesToCreate:250];
[dataUpdate setSaveFrequency:10];
dataUpdateIsActive = YES;
[operationQueue addOperation:dataUpdate];
Both of those NSLogs get triggered.
NSOperation
- (void)main
{
for (NSInteger index = 0; index < [self entriesToCreate]; ++index) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:[self runSpeed]]];
}
}
I have to loop in particluar method which do some task. I have used the method performselectoronmainthread wait until done method. Its working fine if i call it for once. But It fails when I call it in for loop. ie
this is the code:
for (int i=1;i<=3;i++) {
ip=i;
[self performSelectorOnMainThread:#selector(createThread:) withObject:ip waitUntilDone:YES];
}
-(void)createThread:(NSString*)ipIs
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Ip address is :%#",ipIs);
[SimplePingHelper ping:ipIs target:self sel:#selector(Result:)];
[pool release];
}
- (void)Result:(NSNumber*)success {
//do some stuff
}
The problem is that this code works fine when I run this loop once and It calls Result method. But it execution path changes when I try to use loop in performSelectorOnMainThread to pass different variables into it.Then It doesnt call result method.
I am looping because I want to run these same methods on different variables but task to be performed would be same.I am also using waituntil done:YES value..but still it is not working
Any idea ?
try creating other thread
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(setupTimerThread) object:nil];
[myThread start];
-(void)setupTimerThread
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
timer2 = [NSTimer timerWithTimeInterval:0.04
target:self
selector:#selector(triggerTimer:)
userInfo:nil
repeats:YES];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer2 forMode:NSDefaultRunLoopMode];
[runLoop run];
[pool release];
}
-(void)triggerTimer:
{
//this method will be called in loop set loop interval by setting the timers time duration in above method
}
EDIT :
int ip=0;
-(void)triggerTimer:
{
ip++; // till ip<=3
[self createThread:ip];
}
I'm doing an app that loads the contents of viewControllers using NSThread while is reading an XML file.
I have it done as follows:
-(void)viewDidAppear:(BOOL)animated
{
// Some code...
[NSThread detachNewThreadSelector:#selector(loadXML) toTarget:self withObject:nil];
[super viewDidAppear:YES];
}
-(void)loadXML{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Read XML, create objects...
[pool release];
}
My problem is that I don't know how to stop the NSThread if the user changes to another viewController while the NSThread is loading, doing that the app crashes.
I've tried to cancel or exit the NSThread as follows but without success:
-(void)viewsDidDisappear:(BOOL)animated{
[NSThread cancel];
// or [NSThread exit];
[super viewDidDisappear:YES];
}
Can anyone help? Thanks.
When you detach new thread, you can no more cancel or exit it from viewDidDisappear etc. These UI specific methods execute only on main thread so the exit/cancel applies to the main thread which is obviously wrong.
Instead of using the detach new thread method, declare NSThread variable in .h and initialize it using initWithTarget: selector: object: method and cancel it whenever/wherever you want to..
you can also use [NSThread exit]; method of NSThread.
It's better to let a thread end gracefully, i.e. reach its natural conclusion, if you can. It sounds like in your case you can afford to. Also be sure that you're updating the user interface from the main thread, not a secondary thread, as UIKit is not thread safe.
You wrote:
... the app stops responding while the thread finishes...
Once you flag a thread for cancelling or exit, you have to manually stop whatever the thread was called to do. An example:
....
- (void) doCalculation{
/* Do your calculation here */
}
- (void) calculationThreadEntry{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSUInteger counter = 0;
while ([[NSThread currentThread] isCancelled] == NO){
[self doCalculation];
counter++;
if (counter >= 1000){ break;
} }
[pool release]; }
application:(UIApplication *)application
- (BOOL)
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
/* Start the thread */
[NSThread detachNewThreadSelector:#selector(calculationThreadEntry)
toTarget:self withObject:nil];
// Override point for customization after application launch. [self.window makeKeyAndVisible];
return YES;
}
In this example, the loop is conditioned on the thread being in a non-cancelled state.
Hi I'm usuing [NSThread detachNewThreadSelector:toTarget:withObject:] and I'm getting a lot of memory leaks because I have no autorelease pool set up for the detached thread. I'm just wondering where I actualy do this? Is it before I call
[NSThread detachNewThreadSelector:toTarget:withObject:]
or in the method that is being ran in the other thread?
Any help would be appreciated, some sample code would be great.
Thanks.
in the method you call with the thread... i.e. given this...
[NSThread detachNewThreadSelector:#selector(doStuff) toTarget:self withObject:nil];
Your method would be...
- (void)doStuff {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Do stuff
[pool release];
}
You have to set up an autorelease pool in the method you call and that will be executed in the new detached thread.
For example:
// Create a new thread, to execute the method myMethod
[NSThread detachNewThreadSelector:#selector(myMethod) toTarget:self withObject:nil];
and
- (void) myMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Here, add your own code
// ...
[pool drain];
}
Note that we use drain and not release on the autoreleasepool. On iOS, it has no difference. On Mac OS X, if your app is garbage collected, it will triggers garbage collection. This allows you to write code that you can re-use more easily.
Create the new thread:
[NSThread detachNewThreadSelector:#selector(myMethod) toTarget:self withObject:nil];
Create the method that is called by the new thread.
- (void)myMethod
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Your Code
[pool release];
}
What if you need to do something to the main thread from inside your new thread (for example, show a loading symbol)? Use performSelectorOnMainThread.
[self performSelectorOnMainThread:#selector(myMethod) withObject:nil waitUntilDone:false];
Refer :- iPhone SDK Examples
Do it in the method you call. Essentially, you should set up the method that gets called as a self-contained work unit (in fact, it will then be compatible with being called through either [NSOperation][1] or Grand Central Dispatch, too: both better ways of organising concurrent work).
But what if I can't change the implementation of the method I'm calling on a new thread?
In that case, you would go from doing this:
[NSThread detachNewThreadSelector: #selector(blah:) toTarget: obj withObject: arg]
to doing this:
[NSThread detachNewThreadSelector: #selector(invokeBlah:) toTarget: self withObject: dict]
- (void)invokeBlah: (id)dict {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [dict objectForKey: #"target"];
id arg = [dict objectForKey: #"argument"];
[obj blah: arg];
[pool release];
}
rather than using the dictionary, you could also create an NSInvocation that encapsulates the remote object call. I just chose a dictionary because it's the quickest way to show the situation in a SO answer. Either would work.
The documentation states that the method run in the thread must create and destroy its own autorelease pool. So if your code has
[NSThread detachNewThreadSelector:#selector(doThings) toTarget:self withObject:nil];
The method should be
- (void)doThings {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Do your things here
[pool release];
}
I'm having trouble with a simple threading example. I know my methods aren't complete, but as of now, I want to press the start button and have that fire off a thread that increments a counter every second. I know it's connected in IB correctly since the NSLog tells me it gets to my timerThread method. But then it immediately jumps back to the initial myThread, without ever reaching the updateDisplay method and releases the pool, which is why I am guessing my program doesn't actually increment a counter. I thought I would then put it in a sleep interval or something, but in the end I think I am missing the correct way to achieve this. Any thoughts would be great. Thanks!
#implementation MainController
-(id)initWithLabel:(UILabel *)label {
if (self = [super init]) {
countLabel = label;
[countLabel retain];
}
return self;
}
-(int)count {
return count;
}
-(void)setCount:(int) value {
count = value;
}
-(void)updateDisplay:(NSTimer *)timer {
NSLog(#"%s", __FUNCTION__);
countLabel.text = [NSString stringWithFormat:#"%i", count];
count++;
}
-(void)timerThread {
NSLog(#"%s", __FUNCTION__);
[NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
// threadLabel = [NSString stringWithFormat:#"%#", threadID];
}
-(void)myThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
[self performSelectorOnMainThread:#selector(timerThread)
withObject:nil
waitUntilDone:NO];
// NSLog(#"threadID in myThread: %#", threadID);
[pool release];
}
-(void)startThread {
// threadIndex = 0;
// numThreads = 0;
// NSNumber *threadID = [NSNumber numberWithInt:threadIndex++];
[self performSelectorInBackground:#selector(myThread) withObject:nil];
// NSLog(#"%i", threadIndex);
numThreads++;
}
-(void)myThreadStop {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread exit];
[self performSelectorOnMainThread:#selector(updateDisplay)
withObject:nil
waitUntilDone:NO];
[pool release];
}
-(void)stopThread {
[self performSelectorInBackground:#selector(myThreadStop) withObject:nil];
}
-(void) dealloc {
[countLabel release];
[super dealloc];
}
#end
The short answer is that you did not schedule the timer.
iOS (and others) use run loops. Each thread may have a run loop, and the main UI thread has a run loop set up for you. Simplistically, a run loop keeps a queue of things to do and either does them in an ordered fashion or blocks until there is something to do.
Anything that you do with the active UI, like setting the text of a UILabel, must be done on the main thread. In your case, you have set up (but not scheduled) a timer on the main thread to update the timer. Scheduling a timer just means adding it to the run loop.
If you had a lengthy task to perform that would update the UI at the end, you could use performSelectorInBackground to start the lengthy task and performSelectorOnMainThread when the task finishes to update UI.
If you have a short periodic task, such as updating a clock or counter UI, you can just create an NSTimer on the same thread you want the timer to fire on. When creating the timer, use a scheduledTimerWithTimeInterval variant so it will start firing automatically.
When you create a repeating timer, you must keep a reference to it so you can invalidate the timer before the target is released. At the latest, in dealloc you should invalidate the timer.
Instead of calling startThread, turn timerThread into startTimer.
-(void) startTimer {
timerMember = [[NSTimer
scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES] retain];
}
-(void) dealloc {
[timerMember invalidate];
[timerMember release];
...
}