I aims to release a movieplayer (theMovie) and then start another action (so-called playButtonClicked) after it is completely released. I used performSelector to delay the "playButtonClicked" for 1 second and it works well. The code is:
[theMovie release];
[self performSelector:#selector(playButtonClicked) withObject:nil afterDelay:1];
However, I don't want to always delay 1 second. I want to start the "playButtonClicked" as soon as "theMovie" is completely released. I tried the following code, but it didn't work because [timer userInfo] never is nil.
Does anybody know how to check a movieplayer release finished.
[theMovie release];
//[self performSelector:#selector(playButtonClicked) withObject:nil afterDelay:1];
NSTimer *atimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(waitForReleaseFinish:)
userInfo: (MPMoviePlayerController*) theMovie repeats:YES];
The code of waitForRleaseFinish: (NSTimer *)timer is:
if ([timer userInfo]==nil) //here I actually want to test if (theMovie==nil),but I don't know how to do and I'm not sure if it is a correct way to determine the release finished.
{
[timer invalidate];
[self playButtonClicked];
}
Look forward to helps. Thank you.
There is no need.
If you just release the player and then call playButtonClicked, like this:
[theMovie release];
[self playButtonClicked];
It won't execute the second line until the first is completed, or until theMovie is released. This is all on the same thread so it will execute in order. You don't need a timer for this. Although in situations where what you're waiting for to finish executes on a new thread, you would use a callback, rather than guessing how long it takes (which is much less than 1 second!).
Also, just so you don't misunderstand, "completely releasing" is just subtracting the retainCount by one. It will automatically deallocate when it reaches zero.
Just as a side note, why is it important that theMovie is released (deallocated?) before playButtonClicked is executed?
Also, your waitForReleaseFinish: code would work, but it's unnecessary because theMovie would be released before the timer is created.
Related
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];
}
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
NSTimer doesn't stop
I am using a NSTimer to update the value of a slider while a audio is playing.
if (sliderUpdateTimer) {
[sliderUpdateTimer invalidate];
sliderUpdateTimer=nil;
}
sliderUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(updateSliderForAudio) userInfo:nil repeats:YES];
once the audio is finished playing i am invalidating the Timer in audioPlayer delegate method.
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
NSLog(#"audioPlayerDidFinishPlaying");
[player release];
audioPlayer=nil;
[sliderUpdateTimer invalidate];
[sliderUpdateTimer release];
sliderUpdateTimer=nil;
}
The delegate method is getting called but the timer is not stopping.... I have only once thread on which i am runing the timer. But still the timer does not stop.... Any help in this regard is welcome...
First of all, you would be over releasing it and should receive EXC_BADACCESS (you're not retaining the timer when you set it, so you shouldn't release it either). Should only call:
[sliderUpdateTimer invalidate];
sliderUpdateTimer=nil;
Since you don't receive a crash, it seems sliderUpdateTimer is nil when calling audioPlayerDidFinishPlaying:. User NSLog or put a breakpoint to check if this is true. If this is the case, you're probably setting it to nil at some point, search for slideUpdateTimer and check where this might occur.
Hey try this thing...
- (void)stopTimer{
[sliderUpdateTimer invalidate];
//don't release it. as it is autoreleased.
//[sliderUpdateTimer release];
sliderUpdateTimer=nil;
}
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
NSLog(#"audioPlayerDidFinishPlaying");
[player release];
audioPlayer=nil;
[self performSelectorOnMainThread:#selector(stopTimer) withObject:nil waitUntilDone:NO];
}
I know you have only single thread but there may be two different run loops
Try after removing the below lines from 1st section of your code
if (sliderUpdateTimer){
[sliderUpdateTimer invalidate];
sliderUpdateTimer=nil;
}
Don't need to call release message on that sliderUpdateTimer object
Because you have created that object with pending autorelease message
Here no need to use these lines.
I hope this will work.
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
I've read up a lot about NSTimers, but I must be doing something very wrong with them, because it's practically all the leaks that show up in the Leaks Instrument. The "Responsible Frame" column says -[NSCFTimer or +[NSTimer(NSTimer).
So here's how I have an NSTimer set up in my main menu. I shortened it up to just show how the timer is setup.
.h -
#interface MainMenu : UIView {
NSTimer *timer_porthole;
}
#end
#interface MainMenu ()
-(void) onTimer_porthole:(NSTimer*)timer;
#end
.m -
(in initWithFrame)
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
timer_porthole = [[NSTimer scheduledTimerWithTimeInterval:.05
target:self
selector:#selector(onTimer_porthole:)
userInfo:nil
repeats:YES] retain];
}
return self;
}
When leaving the view, it kills the timers:
-(void) kill_timers{
[timer_porthole invalidate];
timer_porthole=nil;
}
And of course, dealloc:
- (void)dealloc {
[timer_porthole invalidate];
[timer_porthole release];
timer_porthole = nil;
[super dealloc];
}
Don't call retain on your NSTimer!
I know it sounds counter-intuitive but when you create the instance it's automatically registered with the current (probaby main) threads run loop (NSRunLoop). Here's what Apple have to say on the subject...
Timers work in conjunction with run
loops. To use a timer effectively, you
should be aware of how run loops
operate—see NSRunLoop and Threading
Programming Guide. Note in particular
that run loops retain their timers, so
you can release a timer after you have
added it to a run loop.
Once scheduled on a run loop, the
timer fires at the specified interval
until it is invalidated. A
non-repeating timer invalidates itself
immediately after it fires. However,
for a repeating timer, you must
invalidate the timer object yourself
by calling its invalidate method.
Calling this method requests the
removal of the timer from the current
run loop; as a result, you should
always call the invalidate method from
the same thread on which the timer was
installed. Invalidating the timer
immediately disables it so that it no
longer affects the run loop. The run
loop then removes and releases the
timer, either just before the
invalidate method returns or at some
later point. Once invalidated, timer
objects cannot be reused.
Quotes are sourced from Apple's NSTimer class reference.
So your instantiation becomes...
timer_porthole = [NSTimer scheduledTimerWithTimeInterval:.05
target:self
selector:#selector(onTimer_porthole:)
userInfo:nil
repeats:YES];
And now that you're no longer holding the reference to the instance you wont want the release call in your dealloc method.
I've seen you already accepted an answer but there are two things here that I wanted to rectify:
It's not needed to retain a scheduled timer but it doesn't do any harm (as long as you release it when it's no longer needed). The "problematic" part of a timer/target relationship is that...
a timer retains its target. And you've decided to set that target to self.
That means — retained or not — the timer will keep your object alive, as long as the timer is valid.
With that in mind, let's revisit your code from bottom to top:
- (void)dealloc {
[timer_porthole invalidate]; // 1
[timer_porthole release];
timer_porthole = nil; // 2
[super dealloc];
}
1 is pointless:
If timer_porthole was still a valid timer (i.e. scheduled on a runloop) it would retain your object, so this method wouldn't be called in the first place...
2 no point here, either:
This is dealloc! When [super dealloc] returns, the memory that your instance occupied on the heap will be freed. Sure you can nil out your part of the heap before it gets freed. But why bother?
Then there is
-(void) kill_timers{
[timer_porthole invalidate];
timer_porthole=nil; // 3
}
3 given your initializer (and as others have pointed out) you are leaking your timer here; there should be a [timer_porthole release] before this line.
PS:
If you think it all over, you'll see that retaining the timer (at least temporarily) creates a retain-cycle. In this particular case that happens to be a non-issue which is resolved as soon as the timer is invalidated...
You missed [timer_porthole release]; call in your kill_timers method. If you call kill_timers before dealloc method is called, you set timer_porthole to nil, but you did not release it.
I'm trying to create a UILabel which will inform the user of what is going on while he waits. However the UILabel always delay its text update until after the system goes idle again.
The process:
[infoLine performSelectorOnMainThread:#selector(setText:) withObject:#"Calculating..." waitUntilDone:YES];
[distanceManager calc]; // Parses a XML and does some calculations
[infoLine performSelectorOnMainThread:#selector(setText:) withObject:#"Idle" waitUntilDone:YES];
Should not waitUntilDone make this happen "immediately"?
If you are doing this on the main UI thread, don't use waitUntilDone. Do a setText, setNeedsDisplay on the full view, set a NSTimer to launch what you want to do next starting 1 millisecond later, then return from your function/method. You may have to split your calculation up into chucks that can be called separately by the timer, maybe a state machine with a switch statement (select chunk, execute chunk, increment chunk index, exit) that gets called by the timer until it's done. The UI will jump in between your calculation chunks and update things. So make sure your chunks are fairly short (I use 15 to 200 milliseconds).
Yes waitUntilDone makes the setText: happen immediately, but setting the label's text does not mean the screen is updated immediately.
You may need to call -setNeedsDisplay or even let the main run loop tick once before the screen can be updated.
Here's a useful function I added to a subclass of UIViewController. It performs the selector in the next run loop. It works, but do you think I should make NSTimer *timer an instance variable since it's likely this method will be called more than once.
- (void)scheduleInNextRunloopSelector:(SEL)selector {
NSDate *fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0.001]; // 1 ms
NSTimer *timer = [[NSTimer alloc]
initWithFireDate:fireDate interval:0.0 target:self
selector:selector userInfo:nil repeats:NO];
[fireDate release];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
}
Use performSelector:(SEL) withObject:(id) afterDelay:(NSTimeInterval):
self.infoLine.text = #"Calculating...";
[self performSelector:#selector(likeABoss) withObject:nil afterDelay:0.001];
//...
-(void) likeABoss {
// hard work here
self.infoLine.text = #"Idle";
}