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];
}];
}];
Related
I am trying to run a while loop when I push the button, but I can not push the button because the while loop blocks the UI.
Is there a background thread where I can run the while loop and also push the UIButton?
Personally, I'd run a HUD activity indicator over the top of the UI and then run your loop in the background.
//Start the HUD here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Run your loop here
dispatch_async(dispatch_get_main_queue(), ^(void) {
//stop your HUD here
//This is run on the main thread
});
});
Try this
dispatch_async(dispatch_get_main_queue(), ^{
// your code
});
Once it dispatches, you will not have full control over the operation.
If you want to take the control of the operation. Use
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// Background work
}];
Way 1 :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Write your code here
});
Way 2 :
If you use performSelectorInBackground:withObject: to spawn a new thread.
Way 3 :
[NSThread detachNewThreadSelector:#selector(yourMethod:) toTarget:self withObject:nil];
There are many options for you, Grand Central Despatch is a good option, or you could use a NSTimer to trigger an event in the background every x milliseconds which may also work for you.
dispatch_async(dispatch_get_main_queue(), ^{
// your code
});
Or
NSTimer *refreshTimer;
refreshTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(YourMethodHere) userInfo:nil repeats:YES];
You can use dispatch_async for this purpose.You have to run the loop in the background thread, while the UI updates must be done in the main thread.Here is a link
How to identify Nsoperation dynamically.
I am creating a NSoperation subclass
- (id)initWithConnectDevice:(ConnectDevice *)cDevice toPeripheral:(CBPeripheral*)peripheral oPerationIndex:(int) index{
if (self = [super init]) {
operationIndex = index;
executing = NO;
finished = NO;
self.connectDevice = cDevice;
[self.connectDevice setDelegate:self];
self.connectedPeripheral = peripheral;
}
return self;
}
-(BOOL)isConcurrent{
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
-(void) terminateOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (void)start {
#autoreleasepool {
if (self.isCancelled){
[timer invalidate];
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}
-(void)timerFired:(id)sender{
NSLog(#"timerFired");
}
I am scanning for BLE devices. For example I will found 3 devices, 3 buttons will create.
I am calling this class every time when I am clicking on that button.That means, When I click on button, I am connecting to bluetooth device and get the data from that device for every second thats why I am using timer in start method.
Like that I have multiple bluetooth devices, whenever I clicked on button, I want to create multiple instances of Operation Queue class.
Now, I want to identify which data is coming from which thread.
Could you please help me....
This the way I am calling above class from viewcontroller
OperationQueue *queue = [[OperationQueue alloc] initWithConnectDevice:connectDevices toPeripheral:peripheral oPerationIndex:operationIndex];
queue.delegate = self;
[[[AppDelegate app] mainQueue] addOperation:queue];
operationIndex = operationIndex+1;
Each of your operations has two identifying properties already - the connected device and the index. When the timer fires, depending what you want to do with the data, you can use these properties to tell where the data is coming from.
You can either have a delegate property on the operation, where a delegate method is called when the timer fires that takes the device and the received data as parameters, or you the operation could have a block property, which takes a block to be executed whenever data is received - the block would have the device and the received data as parameters.
Assuming you want to update the UI when the data is received, be sure to call the delegate method or execute the block on the main thread.
It's possible this question is already out there, but I couldn't find it. My question is essentially this. If I have a repeating NSTimer that executes something that takes longer than the timer interval, will there be some thrashing that will crash the app? Alternatively, does the new time event not start until the task being executed completes?
Since the NSTimer runs on the run loop it was created in, I think it can't ever re-enter the method it calls. This document on the run loops confirms this (see the "Timer Sources" section:
"Similarly, if a timer fires when the run loop is in the middle of
executing a handler routine, the timer waits until the next time
through the run loop to invoke its handler routine"
You can always just schedule an nstimer that only occurs once and then reschedule it when the function completes.
- (void)myFunction {
......stuff that your method does
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(myFunction) userInfo:nil repeats:NO];
}
A repeating timer always schedules itself based on the scheduled firing time, as opposed to the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.
https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/Reference/NSTimer.html
As long as you avoid kicking off some asynchronous jobs, you'll be fine. If asynchronously dispatching tasks that routinely take longer than the interval between invocations of the timer, then that queue can get backed up. If doing animations, the timer will fire even though the animation may not be done.
Let me provide two examples. For both examples, let's imagine that we create a timer that fires once per second:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(handleTimer:)
userInfo:#"tick"
repeats:YES];
First example: Let's assume we have some serial queue:
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
Furthermore, let's assume we have a NSTimer handler that does something like:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
[self.queue addOperationWithBlock:^{
NSLog(#"%s starting some slow process; has %d operations queued", __FUNCTION__, self.queue.operationCount);
// to simulate a slow process, let's just sleep for 10 seconds
sleep(10);
NSLog(#"%s done", __FUNCTION__);
}];
}
Because the timer is firing every second, and because the timer handler returns almost immediately (because all it's doing is queueing up background operations), by the time the first queued operation (which takes 10 seconds) finishes and the second one starts, there are already 10 operations sitting on that background queue. And by the time the second background operation finishes, when the third operation kicks off, there are 19 operations queued up. It only gets worse because the NSTimer handler will simply keep getting called, firing more quickly than the slower background operations are getting cleared out of their queue. Obviously, if the handler did everything synchronously in the current queue, though, everything is fine, and there's no backlogging, no "thrashing" by the NSTimer.
Second example: Another example of this problem is animation. Let's assume that the timer handler method is doing something like the following, that starts a 10 second animation that moves a UIImageView:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
[UIView animateWithDuration:10.0
animations:^{
self.imageView.frame = [self determineNewFrame];
}
completion:nil];
}
This won't work (or more accurately, you'll see the subsequent invocations of the timer call handleTimer even though the previous animation is not done). If you're going to do this, you have to keep track of whether the animation is done. You have to do something like:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
if (!self.animating)
{
NSLog(#"%s initiating another animation", __FUNCTION__);
[UIView animateWithDuration:10.0
animations:^{
self.animating = YES;
self.imageView.frame = [self determineNewFrame];
}
completion:^(BOOL finished){
self.animating = NO;
}];
}
}
You either have to do some state flag (like my boolean animation flag) to prevent additional animations before the first one is done, or just not use recurring timers and simply kick off another timer in the completion block of the UIView animation class method.
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];
});
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.