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.
Related
I want some operation executed every second. I do not want ticks to be accurate between every two seconds. But for a long term, I want them exact for average.
For example, the ticks may happen in time sequence like this:
0.999, 2.005, 2.995, 3.950, 5.012, ... 999.01, 1000.011, ...
Functions may not executed exactly per second, but in a long term, there would be no (or little enough) error in average.
A simple way is using NSTimer. But there is a problem: If CPU ratio is high, the NSTimer fires with a tinny delay. For example, it may fires as a sequence like this:
1.001, 2.002, 3.004, 4.005, 5.006, ... 999.999, 1001.001, ...
As time elapses, the time error may go more and more greater.
Therefore, is there any way to capture any time elapse event in Xcode? Something like "NSTimeNotificationSecondPassed" or ...?
I have develop a method to achieve my goal:
Create an thread Like This:
- (void)_threadHandleTick:(id)arg
{
while(...)
{
#autoreleasepool
{
[self sleepToNextSecond];
... /* Do what ever you like or raise a noification */
}
}
}
Then the - sleepToNextSecond method:
- (void)sleepToNextSecond
{
NSDateFormatter *microSecondFormat = [[NSDateFormatter alloc] init];
[microSecondFormat setDateFormat:#"SSSSSS"];
NSString *microSecondString = [microSecondFormat stringFromDate:[NSDate date]];
useconds_t microSeconds = 1000000 - ((useconds_t)[microSecondString integerValue]);
usleep(microSeconds);
}
Done.
I'm very new to Xcode/programming and trying to modify existing code
I'm having a small problem where I have an amount of objects (enemies) on the screen at one particular time and cannot redefine their value. I set my enemies to begin with 3 on the screen.
My objective is to change the amount of enemies based on the current score.
I've attached snippets of the code below.
int numberOfEnemies;
if (self.score>=0) {
numberOfEnemies = 3
}
else if (self.score>=100) {
numberOfEnemies = 4
}
// Setup array
enemyArray = [[NSMutableArray alloc] init];
for(int i = 0; i < numberOfEnemies; i++) {
[enemyArray addObject:[SpriteHelpers setupAnimatedSprite:self.view numFrames:3
withFilePrefix:#"enemyicon" withDuration:((CGFloat)(arc4random()%2)/3 + 0.5)
ofType:#"png" withValue:0]];
}
enemyView = [enemyArray objectAtIndex:0];
What do I need to do to parse the new value of numberOfEnemies into the array when my score updates?
I'm going to move our conversation into an answer since I don't want it to get too long winded, and I can easily edit and expand on this.
So far, we've established that the reason that you're having issues is that you execute the above code in the viewDidLoad function, which will run at least once when the application is first started. The problem with this is as you've found out, that you arent getting a chance to see a new score, and then update the number of enemies.
I know that game update loops for iOS are usually done in the following structure, but I would recommend finding a tutorial online to get what may be a more efficient/correct way to do it.
From your current structure, I would take the code you have above and create a new function out of it:
-(void) updateDifficulty:(NSTimer *)gameTimer
{
//This can be the code you have above for now
}
Afterwards, inside of your viewDidLoad, I would put the following code:
-(void) viewDidLoad:
{
gameTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateDifficulty:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
}
What that does is it declares a timer that will keep track of the game time, and with how it was declared, every 1 second it will call the updateDifficulty method. This is the general structure that you want, but again I would highly suggest you check out a game tutorial from Ray Wenderlich for example.
Hope that helps!
I have a computational process that takes quite a bit of time to perform so a UIActivityIndicatorView seems appropriate. I have a button to initiate the computation.
I've tried putting the command [calcActivity startAnimating]; at the beginning of the computation in an IBAction and [calcActivity stopAnimating]; at the end of the computation but nothing shows.
Next, I created a new IBAction to contain the starting and stopping with a call to the computation IBAction and a dummy for loop just to give the startAnimating a little chance to get started between the two. This doesn't work either.
The skeletal code looks like this:
- (IBAction)computeNow:(id)sender {
[calcActivity startAnimating];
for (int i=0; i<1000; ++i) { }
[self calcStats];
[calcActivity stopAnimating];
}
- (IBAction)calcStats {
// do lots of calculations here
return;
}
Ok, as I commented, you should never performe complex calculations in your main thread. It not only leads to situations like yours, your app might also be rejected from the store.
Now, the reason for the UIActivityIndicatorView not being updated is, that the UI doesn't actually update itself e.g. when you call [calcActivity startAnimating]; Instead, it gets updated after your code ran through. In your case, that means that startAnimating and stopAnimating are getting called at once, so nothing really happens.
So, the 'easy' solution: Start a new thread, using either this techniques or, probably better, GCD.
Thanks for the nudge, Phlibbo. I'm new to this game and appreciate all the help. I didn't comprehend all the info on the links you provided, but it did prod me to search further for examples. I found one that works well. The IBAction 'computeNow' is triggered by the calculation button. The code now looks like this:
- (IBAction)computeNow {
[calcActivity startAnimating];
[self performSelector:#selector(calcStats) withObject:nil afterDelay:0];
return;
}
- (void) calcStats {
// Lots of tedious calculations
[calcActivity stopAnimating];
}
Compiling in XCode 3.1.1 for OSX 10.5.8 target, 32-bit and i386 build.
I have a modal run loop, running in NSWindow wloop and NSView vloop. The modal loop is started first. It starts, runs and stops as expected. Here's the start:
[NSApp runModalForWindow: wloop];
Then, when the user presses the left mouse button, I do this:
if (ticking == 0) // ticking is set to zero in its definition, so starts that way
{
ticking = 1; // don't want to do this more than once per loop
tickCounter = 0;
cuckCoo = [NSTimer scheduledTimerWithTimeInterval: 1.0f / 10.0f // 10x per second
target: self // method is here in masterView
selector: #selector(onTick:) // method
userInfo: nil // not used
repeats: YES]; // should repeat
}
Checking the return of the call, I do get a timer object, and can confirm that the timer call is made when I expect it to be.
Now, according to the docs, the resulting NSTimer, stored globally as "cuckCoo", should be added to the current run loop automagically. The current run loop is definitely the modal one - at this time other windows are locked out and only the window with the intended mouse action is taking messages.
The method that this calls, "onTick", is very simple (because I can't get it to fire), located in the vloop NSView code, which is where all of this is going on:
- (void) onTick:(NSTimer*)theTimer
{
tickCounter += 1;
NSLog(#"Timer started");
}
Then when it's time to stop the modal loop (which works fine, btw), I do this:
[cuckCoo invalidate];
[NSApp stop: nil];
ticking=0;
cuckCoo = NULL;
NSLog(#"tickCounter=%ld",tickCounter);
ticking and tickCounter are both global longs.
I don't get the NSLog message from within onTick, and tickCounter remains at zero as reported by the NSLog at the close of the runloop.
All this compiles and runs fine. I just never get any ticks. I'm at a complete loss. Any ideas, anyone?
The problem is related to this statement "The current run loop is definitely the modal one". In Cocoa, each thread has at most one runloop, and each runloop can be run in a variety of "modes". Typical modes are default, event tracking, and modal. Default is the mode the loop normally runs in, while event tracking is typically used to track a drag session of the mouse, and modal is used for things like modal panels.
When you invoke -[NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:] it does schedule the timer immediately, but it only schedules it for the default runloop mode, not the modal runloop mode. The idea behind this is that the app generally shouldn't continue to run behind a modal panel.
To create a timer that fires during a modal runloop, you can use -[NSTimer initWithFireDate:interval:target:selector:userInfo:repeats:] and then -[NSRunLoop addTimer:forMode:].
The answer specific to...
[NSApp runModalForWindow: wloop];
...is, after the modal run loop has been entered:
NSRunLoop *crl;
cuckCoo = [NSTimer timerWithTimeInterval: 1.0 / 10
target: self
selector: #selector(onTick:)
userInfo: nil
repeats:YES];
crl = [NSRunLoop currentRunLoop];
[crl addTimer: cuckCoo forMode: NSModalPanelRunLoopMode];
(crl obtained separately for clarity) Where the onTick method has the form:
- (void) onTick:(NSTimer*)theTimer
{
// do something tick-tocky
}
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);