I have a function that selects a row on a UITableView and then fires didSelectRowAtIndexPath to emulate the row having been clicked. This all happens very fast making it hard to see what has happened, I want to artificially pause between these actions so that it is more pleasing to the eye.
I implemented this using NSTimer using the following code however it only has a resolution as low as 1 second. I would ideally like to pause around 300 ms. How can I achieve as simply as possible?
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
I'm not sure I agree with calling didSelectRowAtIndexPath directly. I would suggest moving whatever you're doing in there to a separate method (say, selectionResponse:) and calling that directly.
Once you've done this, you can use performSelector:withObject:afterDelay: to call your selectionResponse: method, setting the afterDelay: argument to whatever value you want.
The link for the documentation on performSelector:withObject:afterDelay is here
Note that it's also a good idea to use + cancelPreviousPerformRequestsWithTarget:selector:object: (from the same document) in your dealloc, to cancel a pending perform request if your user chooses to back out of your view controller within the delay period, before the selector is invoked. This will prevent a crash.
Related
I have a simple UIView that draws itself using drawRect:. In order for the view to animate, the drawRect method needs to be called every say 0.05 seconds, so I use a repeating timer:
timer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self
selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
I don't know too much about run loops, and threads, and all that system stuff, so I want to know if this is the correct way to run an animation? This timer repeats itself endlessly. Is this something I should be worried about? Will this block the main thread? What can I do to minimize overall impact on performance?
The approach is not bad, but there are other ways to do it.
The timer's target method must take the timer as an argument, so instead of setting it for setNeedsDisplay directly, you should set up a method like this:
- (void)animationTimerDidFire:(NSTimer *)timer
{
[myView setNeedsDisplay];
}
If your view will always be visible, then you can just set-and-forget the timer. On the other hand, if it may go away because you switch to a different view, you will need to invalidate and recreate the timer as needed.
The main thread of your app uses a run loop and calls out to various methods in response to events, like user taps, system notifications (e.g., memory warning), and I/O arriving. If anything the run loop calls takes a long time to return, it will hold up everything in the queue. When you set up a timer, it is added to a list and that the run loop checks it each time through; if one of the timers is ready, it calls your method.
The end result is that timers are not exact: they might not fire as often as you like, might be called late, etc. Again, if your app is pretty simple, the main run loop won't be very busy and so a timer will probably be good enough. Just make sure your animation is based on actual time elapsed between calls, rather than assuming each call happens exactly 0.05 seconds apart.
Alternatives
If your animation simply involves flipping through some static images, UIImageView has some support for this.
If creating each frame of animation takes a noticeable amount of time (and you don't want to block the main thread), you could use a background queue to draw into an image (see CGBitmapContextCreate and CGBitmapContextCreateImage), then signal the main thread when a new image is ready to display. Anything that touches a view MUST happen on the main thread, but you can do the drawing on the background.
You also might want to read up on CALayer in the QuartzCore framework. This is what the UIView objects actually manipulate to draw on the screen. You may find that, instead of drawing, you can get the effects you want by manipulating some CALayer objects and letting Core Animation do the heavy lifting (e.g., you change the color from red to blue, Core Animation takes care of fading from one to the other).
Well, if you are using an overridden or custom method, you should use recursion and a completion block for calling the animation. I find it works much better than a timer, since timers aren't always exact and can cause some animation issues if you have the animations timed for cycling.
EDIT: I don't know much about using drawRect: and calling [self setNeedsDisplay] to update it, so I can't help you in that regard. Sorry.
I have a method, someMethod, that is called when a) the user taps the view and b) when a user drags the view. In someMethod, there is a UIView animateWithDuration block that makes the toolbar on top of the view disappear, and resets its frame accordingly. If the user taps the view, than drags it, someMethod will be fired while the animation is still completing, and this isn't the behavior I want (simply canceling the animation doesn't work because the completion block still fires (even if I check the 'finished' BOOL). All things being considered, I just don't want this method to be fired while the animation is still in progress.
Obviously an easy solution to this is to set a manual lock with a BOOL and only allow the method to be called once the lock is free.
I'm wondering, is there a more elegant way to accomplish this? Possible to use GCD or some other library to accomplish this so it's more fool proof?
Update: I did try to use synchronized, the problem though is the method fires off the animation, finishes, but the animation is still running on another thread. Any other ideas?
A timer running out does not imply or require a secondary thread. You're in control of what thread a timer is scheduled on. If you just schedule the timer on the main thread, then both things happen on the main thread.
The suggestions of using #synchronized achieve the effect that a given block of code is not running for the same object (whatever is the parameter of #synchronized) at the same time, but that's not the same thing as saying it's not run on two different threads.
If you want to detect if a method is called on a thread other than the main thread and then shunt it over to the main thread, you can use +[NSThread isMainThread] and dispatch_async(dispatch_get_main_queue(), ^{ /* re-call current method */ });.
In modern iOS and OS X, the most elegant mechanism for controlling the execution is to use dispatch queues and blocks. For a global lock, you can use a single serial queue and make request to it either synchronously or asynchronously, depending on whether you want the remainder of the execution on that thread to stop while you execute the critical code.
Declare your queue globally somewhere:
dispatch_queue_t myQueue;
So, when you launch, you'll create your queue:
myQueue = dispatch_queue_create( "CRITICAL_SECTION", DISPATCH_QUEUE_SERIAL); // FIFO
And when you want to execute the critical section of code, you use:
dispatch_sync( shpLockQueue, ^{
// critical section here
});
Depending on your needs, you might want to call your method within one of these blocks, or you might want to have the block within the object that you are protecting.
You could use the main dispatch queue for this, if you needed to make sure that the routine is run on the main thread, but if that's unnecessary, it's going to be more efficient to use your own queue. If you elect to use the main queue, you don't need to set up your own queue, or store it, you can just execute your code within:
dispatch_sync( dispatch_get_main_queue(), ^{
// critical section here
});
I would suggest the #synchronized() block, Heres a great blog post on the explanation of it:
http://googlemac.blogspot.com/2006/10/synchronized-swimming.html
#synchronized(self) {
[self someMethod];
}
Well even using just a global variable, doesn't guarantee mutual exclusion, since the variable is copied to the register before being updated, if that indeed is what you meant by "manual lock BOOL ..." and unfortunately their aren't any really elegant solutions ....
Check out https://developer.apple.com/library/mac/ipad/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html
Good luck.
Is there any significant difference in performance when you call
[someObject performSelector:#selector(testMethod:) withObject:anotherObject];
vs
[someObject testMethod:anotherObject];
?
The first causes an extra call to objc_msgSend() that isn't necessary in the second case.
The performance difference is unlikely to remotely matter unless you are calling said method as quickly as you possibly can many 10s of thousands of times and you aren't doing any significant work in testMethod:.
I.e. don't worry about it unless you measure an actual performance problem.
Interesting fact, performing a selector with a delay of 0 causes that method to be called at the top of the next run loop of the app. You can use that to delay certain events that occur frequently (used a lot in optimizations of UI, like images that get reloaded in a UIScrollView)
No there isn't any performance hit that I am aware of, and if there is any it is not significant.
I’ve come across an important difference when passing data to another view controller in prepareForSegue.
using:
[viewController performSelector:#selector(aMethod:) withObject:anObject];
aMethod is called AFTER viewDidLoad and viewWillAppear of the destination viewController.
using:
[viewController aMethod: anObject] ;
aMethod is called BEFORE viewDidLoad and viewWillAppear of the destination viewController.
So if you’re sending data important for the setup of the destination viewController, use the second way.
There is a lot difference in above both methods. I was trying to get animation of Two buttons coming from right side and stops at center but the second button was coming with 0.3 second delay. Now the main point comes here. I was using one animation method for both of 2 buttons. Now i wanted that when I click Finish button, then both buttons should go to left and again New buttons come. This was fine till reading.
Now when i was writing method for Finish button click. I was performing going out of buttons Animation first and then coming in buttons, but when I used the Above second method i.e. [someObject testMethod:anotherObject]; then what happens is I was not able to see the Going out Animation and directly coming in animation of buttons was shown.
Here actually comes the use of first method i.e. [someObject performSelector:#selector(testMethod:) withObject:anotherObject withDelay:delay];
The reason I found was when I click the Finish button the animation runs in different thread and the other code runs in different thread so the going out action was performed in another thread and coming in was performed in another thread. So first thread was not shown.
After using first method with Delay time of total animation. I achieved my goal. So both methods have their own significance.
For my experience,there are two differences:
The first one can add afterDelay:(CGFloat)seconds, and this is the only case I use the first one.
[someObject performSelector:#selector(testMethod:) withObject:anotherObject afterDelay:1.0];
The second one, you need to define it in someObject.h. Otherwise, you will get a compile warning.
The answer is that they are exactly the same.
There are two really good articles one from Mike Ash, where he explains the objc_msgSend():
http://www.mikeash.com/pyblog/friday-qa-2012-11-16-lets-build-objc_msgsend.html
And an another one from Tom Dalling who is explaining that perform selector is calling objc_msgSend().
http://tomdalling.com/blog/cocoa/why-performselector-is-more-dangerous-than-i-thought/
I have a UITableViewController with its default UITableView. I begin slowly dragging the table with my finger to scroll, i.e. not flicking it with my finger. Every time the table moves on-screen the scrollViewDidScroll method of the controller is called; when some conditions I've specified are met, one of these calls to scrollViewDidScroll uses performSelector:withObject:afterDelay to schedule some action at a later time.
However, I'm finding that the action will not execute until I release my finger. For example, if I set the afterDelay parameter to 2 seconds, but hold my finger for 5 seconds, when I release my finger and the action executes it's 3 seconds too late. Is there any way to allow the action (which is to update the UI and so must run in the main thread) to execute while the finger is still against the screen?
Thanks!
This is because when a UIScrollView (UITableView's superclass) is scrolling, it changes its runloop in order to prioritize the scrollView over whatever the application was doing. This is happening to make sure scrolling is as smooth as it can be.
try using this version of delayed method:
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
for the modes, I'd suggest starting with an array that contains the following:
[[NSRunloop currentRunLoop] currentMode],
NSDefaultRunLoopMode
I've implemented a tap-and-hold handler using an NSTimer that I first set in the TouchesBegan overload.
However, what I actually want is for an action to be continuously performed in quick-fire succession while the touch is being held. So, on timer expiry I call a handler to do the work, which then sets another timer and the cycle continues until the TouchesEnded comes in and cancels it, or another terminating condition is met.
This works fine, until my handler code triggers an animation to go off at the same time.
Now we have animation events and timer events going off, and in all that we need to handle TouchesEnded as well.
What I am finding is that, if the animation is triggered, and I set my timer to less than 0.025 seconds, my TouchesEnded event doesn't come through until the timer cycle stops (the other terminating condition). Setting a slower timer, or not triggering the animation, make it work (TouchedEnded comes in straight away), but are not what I want.
Obviously this is all on the device (release build - no NSLogs) - in the sim it all works fine
Is there any way of setting the relative priorty of these events - or is it likely I'm missing something else obvious here?
[Update]
I've worked around this in this instance by doing the continuous part without visual feedback until it's done (which from this users perspective is instant). I think this is ok for now. I'd still like to hear any more thoughts on this (Jeffrey's idea was good), but I'm not waiting on tenterhooks now.
Try writing your own Timer-type class by spawning off onto a thread. Example:
BOOL continue = YES; //outside of your #implementation
-(void)doLoop
{
while(continue){
[NSThread sleepForTimeInterval:.025];
[self performSelectorOnMainThread:#selector(whateverTheFunctionIs) waitUntilDone:YES];
}
}
and this would be started by [NSThread detatchNewThreadSelector:#selector(doLoop) toTarget:self withObject:nil]. This is not exactly threadsafe, but you can choose to wrap the boolean into a NSNumber and then do #synchronize on it if you so choose. Alternatively, after I wrote that little snippet I realized it would be better to do a check against the current NSTime instead of sleepForTimeInterval: but you get the point. :)