I have a NSTimer defined like:
[NSTimer scheduledTimerWithTimeInterval:self.speed target:self selector:#selector(displayTextOverTime:) userInfo:nil repeats:YES];
The method displayTextOverTime displays some text, then once the last piece of text is displayed it invalidates it's timer and calls another method for processing (which itself may need to start a NSTimer to display text).
Is there a standard way to do this sort of thing?
My original approach was to simply have displayTextOverTime call the other method directly, but that's getting me some weird behavior where I have nested NSTimers (which never die, because even though they are invalidated and won't loop again, they aren't 'done' because the timer inside of them is still running...or...something?), which is NOT what I want. Is there some way to say "call this method, but do it outside of this NSTimer", like back on the main thread?
Specifically, I am seeing more timers being killed than started (I have a method for killing any passed in timer so I can log when it's being killed).
For an exact answer to your question, you could use Grand Central Dispatch to run a function outside of your timer. Were you want to run the function put this:
dispatch_async(dispatch_get_main_queue(), ^{
(your function or block of code goes here)
});
That will run the block outside of your timer. You can chose to do this on the main thread, global thread, or custom thread with a queue that you create.
Related
Consider the following case:
Main Thread -----UIView info \ --------------------------------------- / Button Updated ------
\ (Some Event) / (Some Event)
\ /
BG Thread ---------------------Validate UIView info-----Update Button------------------------
On the main thread, a UIView is present
UIView makes a dispatch call is made to a background thread
In the BG Thread, the UIView's info is validated.
BG Thread makes a dispatch call to a UIButton on the main thread to update it.
My question is - notice how there is a gap between UIView info and the UIButton which means that the app could technically be updated during this time. How can I possible stop this gap? Essentially, from the BG thread, block the Main Thread till the call comes back?
You cannot and must never block the main thread. If you do, the interface will freeze and, if the blockage lasts too long, the WatchDog process will kill your app dead before the user's very eyes.
You can give the user a sense that something is going on, and discourage the user from doing anything, by, say, turning off user interaction and putting up a spinner. But in general, yes, multithreading is hard; you must be prepared for the possibility that you will come back onto the main thread when the app has changed state.
Rather than block a main thread, disable user input controls in your view until the validation is complete, then re-enable them.
It would also make sense to add an activity indicator with hidesWhenStopped set to true; it will show the user that there's background work in progress if you start it when the background work starts, and stop it when validation is complete.
If there's ever a chance the background process could hang or take longer, e.g. if it's making a network request, you might show/enable a cancel button and a way to terminate it.
Showing activity indicator and possibly providing a cancel button both require that the main thread keep running, so definitely don't block it!
Your button should not be updated in the background. You should always modify UIKit components on the main thread.
You should also never block the main thread, what you're looking for is the show the user an indication that a background process is active. 'UIActivityIndicatorView' might be a good thing to show the user, you could also disable user interaction on the view to prevent the user from touching anything if it's critical for them to wait until the operation is complete but not recommended.
Yes, you should never block the main thread and update UI only on main thread.
That said - show a spinner / activity indicator while busy on the background.
Think carefully about the UI and e.g. present something so the user can not change something while you are busy with e.g. dialog or popover or something like that.
In practise this often becomes more a question of UX than blocking.
We all show activity indicator while some lengthy operation is happening in background. Though the activity indicator shows a constantly rotating wheel it won't burden the main thread, because other UIComponents in the same screen still react to the touches.
What I think I know:
I know all touch events are handled by main thread, and main Queue is being used to queue the events. Considering main queue is Serialized queue and only one task at a time can run at any given point in time, alley touch events should get queued up in main queue, while my main thread is busy in refreshing the screen/calling drawrect of UIActivityIndicator.
Study:
I have looked into the code of third party activity indicators. Most of them use CABasicAnimation and call repeat always on animation. While few work directly use NSTimer to repeatedly call drawrect with a small delay. Their code works because there is a small delay in calling drawrect and the method drawrect in itself is light weight.
None of it won't take the loads off the main thread but rather they carefully place load on main thread enough to keep their animation going yet keeping main thread free to handle touch events
What I want to know:
1 - Is this strategy to implement activity indicator is correct? or statement like this
self.timer =[NSTimer timerWithTimeInterval:0.1 target:self
selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
in one of the third party activity indicator that I saw has any special effect?
2 - If I run CABasicAnimation/transaction and repeat the animation forever will it have any special effects on the load of main thread compared to repeatedly calling setNeedsDispaly/drawrect manually?
I'm not sure whether it will help to implement your own activity indicator, but the system one UIActivitiyIndicatorView is just a UIImageView with an array of 12 images that replace each other over time.
Apple made a pretty neat trick by making their spinner discrete. It allowed them to have a simple implementation that doesn't create any computational load on CPU.
UPD
Returning to the things you want to know:
1 - It's not, because implementing manual frame drawing in drawRect is fully done by CPU. And 2 - I can't say for sure, but if one believes what Apple says in documentation and videos about Core Animation it is heavily optimised and runs on Metal or at least OpenGL underneath, so leverages power of GPU.
I have an application that experiences a brief delay when switching views, on the order of 500-1500ms. The change in views is subtle, so I need to provide feedback to the user that something DID just happen.
I would like to use a "Loading" overlay. Unfortunately, the work that is occupying the CPU is related to building the UI, and therefore cannot be moved to a background thread.
Since the work is occupying the main thread, if I add a loading overlay before the other operation starts, it never gets shown because the thread is working on the next workload and won't update the UI until it gets around to it.
Some operating systems have a DoEvents or FlushMessagePump method that can be used in the rare circumstances like these. Is there such a thing in iOS? SetNeedsDisplay() is not what I want, as it will only queue the update in the message pump.
Alternative suggestions are welcome too.
Instead of delaying your loading, you can force the run loop to run:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate date]];
Show your loading overlay, and then use - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay with a delay of 0 to do your actual loading. This will delay your loading until the screen has a chance to refresh, because it will queue the selector on the main thread's run loop.
In my main starting thread I need to pause the code and start a new thread and wait until I get user input. Then Id like to discard the new thread made and go back to where the main thread left off. But whats happening is that the new thread is called but the main thread keeps going with the code. How do I deal with this without interfering with the user being able to use the interface buttons? I think that maybe another nscondition needs to be made in my if(moveCount == 2) statement? Or that my main thread needs to wait for a signal from my other thread notifying user input is received.
ADDITIONAL NOTE: I would also like the original thread paused in such a way that I can still use my 2 UIButtons in my interface.
MORE ADDITIONAL NOTES AS TO THE QUESTIONS IM RECEIVING: This is a game I'm making. Somewhere mid code means in my main thread in one of my methods. This method decides which direction I'm moving in then i get a tag difference, then make an attack. But sometimes 2 attacks can be made so its at this point the user has 2 buttons to click on the screen to decide which attack to make.
Also its quiet clear to me that I should not pause the main thread now. If so what is the alternative? Thanks
NSCondition and NSThread
#property (strong, nonatomic) NSCondition *condition;
#property (strong, nonatomic) NSThread *aThread;
In my viewDidLoad create the following.
// create the NSCondition instance
self.condition = [[NSCondition alloc]init];
// create the thread
self.aThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadLoop) object:nil];
somewhere mid code..
if(moveCount == 2){
[self.aThread start];
}
// I need to get captureDecision from user from 2 buttons before continue.. How do I pause this thread when aThread is started. Then when capture decision is received discard aThread and start here.
NSLog(#"Capture Decision = %d", captureDecision);
if(captureDecision == 1){
tagDifference = newButton.tag - currentButton.tag;
}else{
tagDifference = currentButton.tag - newButton.tag;
}
}
aThread Method
-(void)threadLoop{
NSLog(#"Thread Loop Triggered");
while([[NSThread currentThread] isCancelled] == NO)
{
[self.condition lock];
while(captureDecision == 0)
{
[self.condition wait];
}
[self.condition unlock];
}
[NSThread exit]; //exit this thread when user input is received
}
What you are requesting is not possible, the main thread IS the one that handles user interactions, whenever you "halt" the main thread the interface becomes unresponsive.
Based on apple's documentation:
The main run loop of your app is responsible for processing all
user-related events. The UIApplication object sets up the main run
loop at launch time and uses it to process events and handle updates
to view-based interfaces. As the name suggests, the main run loop
executes on the app’s main thread. This behavior ensures that
user-related events are processed serially in the order in which they
were received.
Which means that if you halt the main thread you won't be able to.
https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW14
(Unless there is some strange way to make a background thread handle the user interactions which i might not be aware of, but still its pretty strange)
As others have told you here, your approach is far from optimal. Questions you have to ask yourself are, in what part of the code do you want to wait for a certain event to occur, and for what purpose.
If you give us a more detailed explanation of what you want to achieve then we can offer you a much better solution.
EDIT:
What kind of game is it? turn based? something that gets drawn constantly like with a timer. Basically you have to use functions, for example, if it was a card game, you would lay all the cards and wait for user input, when the user hits the button you would run "calculate outcome function" and make the proper changes in the board and wait for the next input.
If you want to pause the thread i would assume it is because something is running constantly, there is a special timer that syncs with the screen called CADisplayLink
https://developer.apple.com/LIBRARY/IOS/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
You can even "pause" this timer to wait for an input from the user.
BTW, if you DONT want to let the user interact with the interface until an event occurs all you have to do is, self.view.userInteractionEnabled = NO; this disables all interactions on the screen until you set it back to "YES".
EDIT 2:
The way you change the flow varies on what you are using to make your game, OpenGL? Quartz2D? simple views?
It kinda feels you are a bit lost, you might wanna start from here:
http://www.raywenderlich.com/tutorials
(Scroll down to the making games part)
It looks like you are going about this wrong. By design the main thread should not be blocked. You should use some sort of callback/delegate from your background thread to your main thread to set your tag decision. I'd provide a bit more info/examples, but I'm not really sure what you trying to achieve or where your "somewhere mid code" section takes place. I thought the main thread, but then you call a selector to be performed on the main thread.
I need to create two timers. One is on the UI thread, the other one is on the background thread. These timers are independent from each other.
Scenario that I will be using; I basically need this to test the responsiveness of UI thread. TImer in uI thread updates lastUpdatedDatetime property every 200ms in UI thread. There is also a background thread that polls this lastupdatedDatetime property every 200ms.
Does anyone know how can I achieve this?
The point of NSTimer is to be able to schedule things to be run on a thread (usually the main thread) while it is handling other events on a run loop. If all you want to do on the background thread is to poll something every 200 ms, then it is far easier to not use an NSTimer and to instead just sleep the background thread. So setup an NSTimer as usual on your main thread and on the background thread do something like:
while (stillRunning) {
usleep(200*1000);
// Do something with lastUpdatedDatetime
}
This technique is wasteful of a thread but is probably more deterministic for testing purposes than using Grand Central Dispatch.
Keep in mind if lastUpdatedDatetime is an NSDate that it should be set as an atomic property so that when you access it on the background thread you are sure to receive a valid object.
look into dispatch_async and dispatch_after
dispatch_async will let you fire off a call to another thread, this is what I use to do main thread changes.
dispatch_after will let you delay a dispatch_async for a number of seconds.
These two in conjunction will let you go back and forth between threads.
here is the documents on Grand Central Dispatch