Note: It's probably worth scrolling down to read my edit.
I'm trying to setup an NSTimer in a separate thread so that it continues to fire when users interact with the UI of my application. This seems to work, but Leaks reports a number of issues - and I believe I've narrowed it down to my timer code.
Currently what's happening is that updateTimer tries to access an NSArrayController (timersController) which is bound to an NSTableView in my applications interface. From there, I grab the first selected row and alter its timeSpent column. Note: the contents of timersController is a collection of managed objects generated via Core Data.
From reading around, I believe what I should be trying to do is execute the updateTimer function on the main thread, rather than in my timers secondary thread.
I'm posting here in the hopes that someone with more experience can tell me if that's the only thing I'm doing wrong. Having read Apple's documentation on Threading, I've found it an overwhelmingly large subject area.
NSThread *timerThread = [[[NSThread alloc] initWithTarget:self selector:#selector(startTimerThread) object:nil] autorelease];
[timerThread start];
-(void)startTimerThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
activeTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTimer:) userInfo:nil repeats:YES] retain];
[runLoop run];
[pool release];
}
-(void)updateTimer:(NSTimer *)timer
{
NSArray *selectedTimers = [timersController selectedObjects];
id selectedTimer = [selectedTimers objectAtIndex:0];
NSNumber *currentTimeSpent = [selectedTimer timeSpent];
[selectedTimer setValue:[NSNumber numberWithInt:[currentTimeSpent intValue]+1] forKey:#"timeSpent"];
}
-(void)stopTimer
{
[activeTimer invalidate];
[activeTimer release];
}
UPDATE
I'm still totally lost with regards to this leak. I know I'm obviously doing something wrong, but I've stripped my application down to its bare bones and still can't seem to find it. For simplicities sake, I've uploaded my applications controller code to: a small pastebin. Note that I've now removed the timer thread code and instead opted to run the timer in a separate runloop (as suggested here).
If I set the Leaks Call Tree to hide both Missing Symbols and System Libraries, I'm shown the following output:
EDIT: Links to screenshots broken and therefor removed.
If the only reason you are spawning a new thread is to allow your timer to run while the user is interacting with the UI you can just add it in different runloop modes:
NSTimer *uiTimer = [NSTimer timerWithTimeInterval:(1.0 / 5.0) target:self selector:#selector(uiTimerFired:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:uiTimer forMode:NSRunLoopCommonModes];
As an addendum to this answer it is now possible to schedule timers using Grand Central Dispatch and blocks:
// Update the UI 5 times per second on the main queue
// Keep a strong reference to _timer in ARC
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, (1.0 / 5.0) * NSEC_PER_SEC, 0.25 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
// Perform a periodic action
});
// Start the timer
dispatch_resume(_timer);
Later when the timer is no longer needed:
dispatch_source_cancel(_timer);
Related
I've made a game in Xcode 6, using several NSTimers for different things, like a scorer timer, countdown timer, and to move my objects around. The problem is that sometimes (it seems like) the NSTimers stop for like half a second which makes it look like it lags. Example: When the character is moving, it stops for a tiny second and then continues to move. It happens so fast, but it is noticable, and it annoys me so much. I want it to be completely smooth. Any help would be appreciated!
A couple of thoughts:
If you're having a small delay in the timer processing, the most likely issue is that you have something blocking the main queue. Take a careful look at your code and see if you can find anything that could block the main queue.
You can actually use Instruments to find places in your app where the thread might be blocked. If I recall correctly, WWDC 2112 video Building Concurrent User Interfaces on iOS shows the trick with Instruments to find where your app is blocked. It's a bit dated, but the techniques for finding where the main thread blocks still apply.
It's unlikely, but you might want to consider checking the run loop modes that your timer is running on. For example, the default:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
This can pause during certain animations. You might consider using a broader array of run loop modes, e.g.:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
This results in a timer that is less susceptible to certain delays during particular types of animations. It just depends upon what else your app is doing when you see the delay in the user interface.
When using a timer to update animations, better than a NSTimer is a CADisplayLink. For example, define a few properties:
#property (nonatomic, strong) CADisplayLink *displayLink;
#property (nonatomic) CFTimeInterval startTime;
Then you can write code to start and stop the display link:
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
self.startTime = CACurrentMediaTime();
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
CFTimeInterval elapsed = CACurrentMediaTime() - self.startTime;
// update your UI, not on the basis of "this is called x times per second",
// but rather, on the basis that `elapsed` seconds have passed
}
The key in good animation code is that you don't just assume that your routine will be called at a specified frequency, but rather that you update the UI based upon the number of elapsed seconds. This way, a slow device that drops a few frames and a fast device will yield the same animation, the latter would just be a little smoother than the former.
The merits of display links, though, are discussed briefly in WWDC 2014 video - Building Interruptible and Responsive Interactions. There are other longer discussions of the topic that are eluding me at this point, but this might be a good place to get introduced to the topic (even though the vast majority of that video is on other topics).
You may want to try a high resolution timer, like Timer dispatch sources. It looks a bit scary at first, but actually quite easy to use. Sample code (with comments)
dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue , dispatch_block_t block) {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer) {
// Use dispatch_time instead of dispatch_walltime if the interval is small
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void MyCreateTimer()
{
dispatch_source_t aTimer = CreateDispatchTimer(30 * NSEC_PER_SEC, 1 * NSEC_PER_SEC, dispatch_get_main_queue(), ^{
NSLog(#"Timer fired!");
});
// Keep a reference if you want to, say, stop it somewhere in the future
}
EDIT:
In XCode, if you type dispatch in the editor, it will suggest a snippet called dispatch_source timer - GCD: Dispatch Source (Timer), which will generate the template code for the timer.
i have some problem. So i have code which update song name and picture from php. Song name work and also updated but picture not work, in php file all work but in my project - no. How make update picture from url after 10 sec for example. Thanks.
-(void)viewWillDraw {
NSURL *artistImageURL = [NSURL URLWithString:#"http://site.ru/ParseDataField/kiss.php?image"];
NSImage *artistImage = [[NSImage alloc] initWithContentsOfURL:artistImageURL];
[dj setImage:artistImage];
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
NSError* error = nil;
NSString* text = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://site.ru/ParseDataField/kiss.php?artist"]
encoding:NSASCIIStringEncoding
error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[labelName setStringValue:text];
});
});
}
You should really consider placing this code someplace other than -viewWillDraw. This routine can be called multiple times for the same NSView under some circumstances and, more importantly, you need to call [super viewWillDraw] to make sure that things will actually draw correctly (if anything is drawn in the view itself).
For periodic updates (such as every 10 seconds), you should consider using NSTimer to trigger the retrieval of the next object.
As for the general question of why your image isn't being drawn correctly, you should probably consider putting the image retrieval and drawing code into the same structure as your label retrieval and drawing code. This will get the [dj setImage: artistImage] method call outside of the viewWillDraw chain which is likely causing some difficulty here.
I am trying to create an application for the Mac that would create live video streaming. I know about VLC and other solutions, but still.
To that end i am trying to record video from iSight using QTKit, and save it continuously as a series of tiny video files. However, the recording turns out not quite continuous, with gaps between the files.
Basically, I am just setting up a timer, that starts recording to a new file at certain time intervals, thus stopping the old recording. I also tried setting the max recorded length, and using a delegate method ...didFinishRecording... and ...willFinishRecording..., but with the same result (i can't really estimate the difference between the gaps in these cases).
Please, help me, if you know how these things should be done.
Here is my current code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
QTCaptureSession *session = [[QTCaptureSession alloc] init];
QTCaptureDevice *iSight = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo];
[iSight open:nil];
QTCaptureDeviceInput *myInput = [QTCaptureDeviceInput deviceInputWithDevice:iSight];
output = [[QTCaptureMovieFileOutput alloc] init] ; //ivar, QTCaptureFileOutput
[output setDelegate:self];
a = 0; //ivar, int
fileName = #"/Users/dtv/filerecording_"; //ivar, NSString
[session addOutput:output error:nil];
[session addInput:myInput error:nil];
[capview setCaptureSession:session]; //IBOutlet
[session startRunning];
[output setCompressionOptions:[QTCompressionOptions compressionOptionsWithIdentifier:#"QTCompressionOptionsSD480SizeH264Video"] forConnection:[[output connections] objectAtIndex:0]];
[output recordToOutputFileURL:[NSURL fileURLWithPath:[NSString stringWithFormat:#"%#%i.mov", fileName, a]] bufferDestination:QTCaptureFileOutputBufferDestinationOldFile];
NSTimer *tmr = [NSTimer timerWithTimeInterval:5 target:self selector:#selector(getMovieLength:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:tmr forMode:NSDefaultRunLoopMode];
}
‐ (void) getMovieLength:(NSTimer *) t {
a++;
[output recordToOutputFileURL:[NSURL fileURLWithPath:[NSString stringWithFormat:#"%#%i.mov", fileName, a]] bufferDestination:QTCaptureFileOutputBufferDestinationOldFile];
}
There is a native mechanism to brake the captured movie into pieces. Use
[QTCaptureFileOutput setMaximumRecordedDuration:]
to specify the duration of the piece or
[QTCaptureFileOutput setMaximumRecordedFileSize:]
to specify the file size limit.
When the limit is reached the delegate method will be called:
[QTCaptureFileOutput_Delegate captureOutput: shouldChangeOutputFileAtURL: forConnections: dueToError:]
In the this method you can set the new file name:
[QTCaptureFileOutput recordToOutputFileURL:]
This will allow you to cut the pieces of the recorded movie pretty precisely.
Note, that [QTCaptureFileOutput_Delegate captureOutput: didFinishRecordingToOutputFileAtURL: forConnections: dueToError:] will be called a bit later after the recoding into the file has been actually finished. If you use this method to set the new file you will have gaps in the final video. It does not mean you do not need to use this method though. This method will indicated when the piece of the movie is ready to be used.
If you need even more precise cutting you can use
[QTCaptureFileOutput captureOutput: didOutputSampleBuffer: fromConnection:]
to specify the exact movie frame when to start recording into a new piece. However, you will need more specific knowledge to work with the method.
I am using UIProgresView for displaying downloading and in label the percentage of content downloads.The function is executing every time. When I displaying the progress value it is showing properly in the console. But it is not displaying on the screen, It is display at only 0% and 100%. I am using ASIHTTP framework too.
Please help in that.
CODE from comment:
for (float i=0; i< [topicNew count]; i++)
{
NSDictionary *new= [topicNew objectAtIndex:i];
NSString *imageName = [[[NSString alloc] initWithFormat:#"%#.%#.%#.png", appDelegate.subject,topicNamed,[new objectForKey:kWordTitleKey]] autorelease];
NSString *imagePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:imageName];
NSData *imageData = [self ParsingImagePath:[new objectForKey:kWordImagePathKey]];
[progressView setProgress:i/[topicNew count]];
[lblpercent setText:[[NSString stringWithFormat:#"%.0f",i/[topicNew count]] stringByAppendingString:#"%"]];
... More code here...
It looks like you are threadlocking your main thread. Meaning, you are updating the progressView but the mainthread never gets out of your for loop to display the updated change until the for loop is finished, which by then looks like your progress is set to 100%.
You need to either use a timer like (NSTimer) or some kind of background thread that processes your image files. Then you need to call your instance of UIProgressView from the background thread when you want to update your progress (end of the for loop for example)
float p = i/[topicNew count];
[progressView performSelectorOnMainThread:#selector(setProgress:)
withObject:[NSNumber numberWithFloat:p]
waitUntilDone:NO];
and pass in your updated progress to the progressView. Make sure you are not calling the progressView from the background thread directly, as UIKit is not thread safe. Only call it from your main thread, or using the performSelectorOnMainThread:withObject:waitUntilDOne: method.
Hopefully that gets you in the right directly
How reliable is Xcode's "Build and Analyze" ?
I keep looking for the "potential leak" that it finds - but don't understand it.
Here the code:
-(void)viewDidLoad{
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *aBI = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(add:)];
self.navigationItem.rightBarButtonItem = aBI;
aBI.release;
UIBarButtonItem *eB = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize
target:self action:#selector(EinstellungenTapped:)];
UIBarButtonItem * space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *emailB = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCompose
target:self action:#selector(emailTapped:)];
NSArray* toolbarItems = [NSArray arrayWithObjects: eB,space,emailB,
nil];
[self setToolbarItems: toolbarItems];
eB.release; // <------- Potential leak ??????
emailB.release; // <------- Potential leak ??????
space.release; // <------- Potential leak ??????
}
Why should these three be potential leaks?
Even without the assignemt to the NSARRAY it's telling me about leaks.
What is wrong here?
many thanks....
I imagine the problem is because you're horribly mis-using dot syntax and confusing the static analyzer. You should never ever type anObject.release, because that has the completely wrong meaning. Dot syntax is designed for accessing properties, and has that specific semantic meaning, e.g. accessing a property. Anything which modifies the object itself should be a method, not a property, and should be called as such.
In other words, change those to
[eB release];
[emailB release];
[space release];
and things should work fine. Now the static analyzer should recognize that you really are releasing the objects.
As one final note, the fact that eB.release technically works can be considered an artifact of how the compiler works. While I don't believe that will ever break, I also don't know if it's guaranteed not to, and it's very bad form to rely upon.