I have to perform a timer using a NSThread as I need to download text data from the web, and without that, in 3G connection it freezes the UI while downloading. So I've used a NSThread but it still freezes for a while and I don't know how to solve this....
Here's the code I'm using to perform the timer:
- (void)viewDidLoad{
[NSThread detachNewThreadSelector:#selector(onTimerK2) toTarget:self withObject:nil];
}
- (void)onTimerK2{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
timer = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(onTimerKY2) userInfo:nil repeats:YES];
[pool release];
}
- (void)onTimerKY2{
NSLog(#"working");
}
You're detaching a new thread to call onTimerK2, which then immediately calls a method back on the main thread, which will of course freeze your interface.
Edit
You should be doing any long-running work not on the main thread (either yourself, or by using the asynchronous nature of NSURLConnection as mentioned elsewhere),and then updating your UI by calling selectors on the main thread as this activity progresses.
Having said that, you may have more success with the following changes/reordering of your code:
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:15
target:self
selector:#selector(onTimerK2)
userInfo:nil
repeats:YES];
}
- (void)onTimerK2{
[NSThread detachNewThreadSelector:#selector(onTimerKY2)
toTarget:self
withObject:nil];
}
- (void)onTimerKY2{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"working");
[pool release];
}
It's not very clear how you are trying to solve the UI freeze problem by using timer. But if your UI is freezing due to downloading then you can try asynchronous loading instead of using timer or detaching another thread.
EDIT: Unless you configure a run loop for secondary thread, timer is not going to work from that thread. Check the run loop management in threading programming guide. This can be a far difficult work than to use asynchronous connection.
Related
I use this code for stopping NSTimer
[timer invalidate]
timer = nil;
It works fine for the first run. But, after I resume the timer with this code.
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
NSTimer won't stop anymore with [timer invalidate]
It look like multiple instance of timer is running simultaneously. You can do one thing, before start to run a new timer, check for previous instance of timer, and if timer instance is available, then invalidate it. After this start new instance
if(timer)
{
[timer invalidate];
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
In apple's official document they said:
You must send this message from the thread on which the timer was
installed. If you send this message from another thread, the input
source associated with the timer may not be removed from its run loop,
which could prevent the thread from exiting properly.
If your timer is running on main thread, do this:
[timer performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:YES];
If it is on any other thread, lets call the thread myThread, then do this:
[timer performSelector:#selector(invalidate) onThread:myThread withObject:nil waitUntilDone:NO];
Hope this helps.. :)
Just invalidate the timer inside the selector that fires. That will ensure you have a pointer to the correct timer (which is probably why your invalidate call isn't working:
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates:)
userInfo:nil
repeats:YES];
Note the colon after checkNewUpdates:
Then, in your checkNewUpdates: method, do something like this:
- (void)checkNewUpdates:(NSTimer*)timer
{
// do somehting
// Then, check if the criteria for stopping the timer has been met, and invalidate it here.
if( self.shouldStopTimer ) // made up variable, use your own criteria.
{
[timer invalidate];
}
}
I know this doesnt answer your question per-se;
Can I suggest using polling mechanism instead of a timer? Ive had a world of trouble with NSTimers in the past and polling was a good alternative. Ive made a simple one below.
- (void) doPoll {
// do logic here
if (shoudStop) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, X * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self doPoll];
});
}
This is just a simple example, it does not stop retain cycles If you choose to try this, Yours should.
Hope it helps.
I have a table that is refreshing itself every two seconds. It works great on the simulator and on my wifi. But once I switch to the cellular network (or any slow network), I cannot select the rows reliably.
Sometimes when I click a row it will work after 8 seconds. Sometimes never.
I thought my refresh function was causing the delay but I printed the time at the beginning and end of the function and it only takes 2 milliseconds.
Has anyone had a similar slow network issue? Any tips on what might be the cause of the hang-up?
My refresh function is called in viewDidLoad:
//Set timer to call refresh function every two seconds
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(updateMethod) userInfo:nil repeats:YES];
My updateMethod is:
- (void) updateMethod
{
[columnArray removeAllObjects];
[self getColumnData];
[homeTable reloadData];
}
getColumnData calls a website and puts data in the columnArray
You must not perform network operations on the main queue. You can create an NSOperationQueue to move the network logic to a background queue and only perform the UI update in the main queue when the network operation ends.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = #"Data request queue";
[queue addOperationWithBlock:^{
[self getColumnData];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[homeTable reloadData];
}];
}];
I am using the thread to call my function "initialGetMethod"
[NSThread detachNewThreadSelector:#selector(initialGetMethod) toTarget:self withObject:nil];
and my get method is
-(void) initialGetMethod
{
self.loginPassword = [[ UIAlertView alloc] initWithTitle:#"Please Login to MFP" message:#"Enter Valid UserID and Password" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:#"Cancel", nil];
[self.loginPassword setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];
[self.loginPassword setTag:2];
[self.loginPassword show];
}
but its giving the exception "Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. "
its giving the exception at "[self.loginPassword setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];"
and if i call the function as "[self initialGetMethod];" its not giving the exception but it will take some time..
i tried loading in background but its not working.. (mean i dont want it to be in background)..
please suggest some solution ..
The error which you are getting during running application is
"Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. "
which is occur once you are updating or accessing the UI elements in any other thread except the MainThread (Use Main thread only to access or Update the UI it will only help you)
Here you are showing Alert in Background thread that's why it happens
Please use one of the following to PopUp alert
[self performSelector:#selector(initialGetMethod) withObject:nil];
[self performSelector:#selector(initialGetMethod) withObject:nil afterDelay:0.1];
//call createthread:
[self setupTimerThread:];
//write following code in setupTimerThread
timer = [NSTimer timerWithTimeInterval:02
target:self
selector:#selector(initialGetMethod:)
userInfo:nil
repeats:NO];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
[runLoop run];
I was wondering why when you create a repeating timer in a GCD block it doesen't work?
This works fine:
-(void)viewDidLoad{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimer) userInfo:nil repeats:YES];
}
-(void)runTimer{
NSLog(#"hi");
}
But this doesent work:
dispatch_queue_t myQueue;
-(void)viewDidLoad{
[super viewDidLoad];
myQueue = dispatch_queue_create("someDescription", NULL);
dispatch_async(myQueue, ^{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimer) userInfo:nil repeats:YES];
});
}
-(void)runTimer{
NSLog(#"hi");
}
NSTimers are scheduled on the current thread's run loop. However, GCD dispatch threads don't have run loops, so scheduling timers in a GCD block isn't going to do anything.
There's three reasonable alternatives:
Figure out what run loop you want to schedule the timer on, and explicitly do so. Use +[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:] to create the timer and then -[NSRunLoop addTimer:forMode:] to actually schedule it on the run loop you want to use. This requires having a handle on the run loop in question, but you may just use +[NSRunLoop mainRunLoop] if you want to do it on the main thread.
Switch over to using a timer-based dispatch source. This implements a timer in a GCD-aware mechanism, that will run a block at the interval you want on the queue of your choice.
Explicitly dispatch_async() back to the main queue before creating the timer. This is equivalent to option #1 using the main run loop (since it will also create the timer on the main thread).
Of course, the real question here is, why are you creating a timer from a GCD queue to begin with?
NSTimer is scheduled to thread’s runloop. In code of question, runloop of thread dispatched by GCD is not running. You must start it manually and there must be a way to exit run loop, so you should keep a reference to the NSTimer, and invalidate it in appropriate time.
NSTimer has strong reference to the target, so target can't has strong reference to timer, and runloop has strong reference to the timer.
weak var weakTimer: Timer?
func configurateTimerInBackgroundThread(){
DispatchQueue.global().async {
// Pause program execution in Xcode, you will find thread with this name
Thread.current.name = "BackgroundThreadWithTimer"
// This timer is scheduled to current run loop
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
// Start current runloop manually, otherwise NSTimer won't fire.
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}
}
#objc func runTimer(){
NSLog("Timer is running in mainThread: \(Thread.isMainThread)")
}
If timer is invalidated in future, pause program execution again in Xcode, you will find that thread is gone.
Of course, threads dispatched by GCD have runloop. GCD generate and reuse threads internally, there threads are anonymous to caller. If you don't feel safe to it, you could use Thread. Don't afraid, code is very easy.
Actually, I try same thing last week and get same fail with asker, then I found this page. I try NSThread before I give up. It works. So why NSTimer in GCD can't work? It should be. Read runloop's document to know how NSTimer works.
Use NSThread to work with NSTimer:
func configurateTimerInBackgroundThread(){
let thread = Thread.init(target: self, selector: #selector(addTimerInBackground), object: nil)
thread.name = "BackgroundThreadWithTimer"
thread.start()
}
#objc func addTimerInBackground() {
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}
This is a bad Idea. I was about to delete this answer, but I left it here to avoid others from doing the same mistake I did.
Thank you #Kevin_Ballard for pointing at this.
You'd only add one line to your example and it'd work just as you wrote it:
[[NSRunLoop currentRunLoop] run]
so you'd get:
-(void)viewDidLoad{
[super viewDidLoad];
myQueue = dispatch_queue_create("someDescription", NULL);
dispatch_async(myQueue, ^{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run]
});
}
Since your queue myQueue contains a NSThread and it contains a NSRunLoop and since the code in the dispatch_async is run the the context of that NSThread, currentRunLoop would return a stopped run loop associated with the thread of your queue.
I am wondering if someone can explain why dispatching back to the main queue and creating a repeating NSTimer I am having to add it to RUN LOOP for it too fire? Even when using performselectorOnMainThread I still have to add it to a RUN LOOP to get it to fire.
Below is an example of my question:
#define queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define mainqueue dispatch_get_main_queue()
- (void)someMethodBeginCalled
{
dispatch_async(queue, ^{
int x = 0;
dispatch_async(mainqueue, ^(void){
if([_delegate respondsToSelector:#selector(complete:)])
[_delegate complete:nil];
});
});
}
- (void)compelete:(id)object
{
[self startTimer];
//[self performSelectorOnMainThread:#selector(startTimer) withObject:nil waitUntilDone:NO];
}
- (void)startTimer
{
NSTimer timer = [NSTimer timerWithTimeInterval:3 target:self selector:#selector(callsomethingelse) userInfo:nil repeats:YES];
//NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes];
}
EDIT:
I believe I worded this question very poorly. I would like to know why [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes]; is necessary in startTimer if I call someMethodBeginCalled. If I don't include that line, the timer doesn't fire.
If I call startTimer from viewDidLoad for example, I can remove the NSRunLoop line and the timer will fire every 60 seconds.
And here's how to add an NSTimer to a runloop:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
You could always use this method instead:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(getBusLocation) userInfo:nil repeats:YES];
This will save you a line, as it will add it to the run loop automatically.
Because, as the docs say:
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.
It is a design decision that Apple made when they wrote the code for NSTimer (and I'm sure they had good reason to do so) and there is nothing we can do to get around it. Is it really that burdensome?
Like #sosborn said, NSTimers depend on NSRunLoops, and since GCD queues create threads that don't have run loops, NSTimer doesn't play well with GCD.
Check out this other StackOverflow question on the matter: Is it safe to schedule and invalidate NSTimers on a GCD serial queue?
To solve that problem, I implemented MSWeakTimer: https://github.com/mindsnacks/MSWeakTimer (and had the implementation checked by a libdispatch engineer at the last WWDC!)
Timer method won't be called since GCD queues create threads that don't have run loops
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
However when dispatched on main queue the timer method will be called as it will get added to main threads run loop.
dispatch_async(dispatch_get_main_queue(), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
Adding the timer to the runloop didn't work in my case. I had to create the timer on the main thread. I was doing this thread creation in a MultipeerConnectivity delegate.
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.interval invocation: self.invocation repeats:YES];
});