UISwitch latency between taps caused by Firebase - ios

I have a view with a bunch of UISwitch.
My problem is that when I tap on a switch I need to wait about 10 seconds before being able to tap any other switch of the view.
Here is my code :
-(void) didTapSwitch:(UISwitch *)sender
{
NSLog(#"BEGIN didTapSwitch, %#",sender);
DADudesManager *dudesManager = [DADudesManager getInstance];
DADude *updatedDude = [dudesManager.dudesList objectAtIndex:[[self.spendingDudesTableView indexPathForCell:sender.superview.superview] row]];
DAAccountManager *accountManager = [DAAccountManager getInstance];
[accountManager.accountsOperationQueue addOperationWithBlock:^{
NSLog(#"BACKGROUND OPERATION BEGINS switchDudeBeneficiates, %#",sender);
DASpendingsManager *spendingsManager = [DASpendingsManager getInstance];
[[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{updatedDude.dudeName: [sender isOn] ? #"1" : #"0"}];
NSLog(#"BACKGROUND OPERATION ENDS switchDudeBeneficiates, %#",sender);
}];
NSLog(#"END switchDudeBeneficiates, %#",sender);
}
My spendingObserver is a Firebase object initiated before.
When the code above is executed, the NSLogs show almost instantaneously in the console, the data is updated online at the same time, but the switches don't react to any tap for another 9 to 11 secs.
Of course commenting the line [[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{weakDude.dudeName: [weakSwitch isOn] ? #"1" : #"0"}]; removes the latency, so the problem must come from Firebase, but I have no clue what's going on.
I am probably missing something obvious as I'm pretty new to IOS development !

I can think couple of reasons.
You are sending the PayLoad in the main thread, which is causing the User INterface events to be suspended.
The code you ran, might be linked to other functions in the library you are using, that might be causing the lag.
TRY - >
try putting your code in an NSOperation and execute that. Or use GCD to do work on different thread just not the UI thread which is the main thead.

Step back and simplify. Make your switch code simply log the change in value. NSLog includes a timestamp, so you can tell when the switch events occur.
If do-nothing code responds quickly, as I suspect it will, then add log statements at the beginning and end of your switch action method. That way you can see if there is a delay between the beginning and end of the processing.
You could also run the app in instruments (time profiler) and see where your app is spending time.

Related

Result of calling performSelector: on a "sleeping" thread's current run loop?

I asked myself a question, but have no answer yet so I hope you could have one:
What happened if there is a NSThread having his current NSRunLoop(named runLoop1 for example) in "sleeping" state (well, runLoop1 called [NSThread sleepForTimeInterval:/*...*/]) while an other NSThread is calling [self performSelector:#selector(selector:) onThread:runLoop1 withObject:nil waitUntilDone:NO]?
I hope I'm understandable ^^
The runloop can be explained as an infinite loop:
while(!exit) {
// Do stuff here
}
This runs on a thread and if this thread is in sleep then so is the loop and no events will be called on it.
So then what are performSelector methods:
Imagine there is an array of invocations with that loop which will be executed when appropriate. Since there is a method to perform the selector after a delay there is also a time stamp.
while(!exit) {
NSMutableArray *notExecuted = [NSMutableArray new];
for(Executable *item in [self.pendingExecutables copy]) {
if(item.executionDate && [item.executionDate compare:[NSDate date]] == NSOrderedDescending) {
[notExecuted addObject:item];
}
else {
[item execute];
}
}
self.pendingExecutables = notExecuted;
}
So calling performSelector really does nothing but adds the data needed to perform it into some array. The runloop must be running for the execution to actually happen. So in your case nothing happens, the selector will not be performed because the loop is not executing since the whole thread is sleeping.
You can then also understand what happens if you block the main thread. No touch events, no system notifications, no nothing. It is all just kept in array and it will be called once the thread is unblocked and another loop occurs. The main loop also sends the date on each cycle which is then used for the watch dog. Since the OS works on another thread then your application it is the OS that will check that date and if it is relatively old you get to "application not responding" state. And then the OS may decide to kill your application.
Note that this is oversimplified but it is enough to get a basic understanding on how these things work.

NSTimer Logic Failing Somewhere

I've been able to reproduce a defect in our app twice, but most times I fail. So I'm trying to understand what could possibly be going on here and hopefully have some new things to try. Our app times out and logs the user out after 10 minutes using an NSTimer. Every time the screen is touched the timer is reset, this all works great.
When the user backgrounds the app and comes back, the following code gets called:
- (BOOL)sessionShouldTimeOut {
if (self.timeoutManager) {
NSTimeInterval timeIntervalSinceNow = [self.timeoutManager.updateTimer.fireDate timeIntervalSinceDate:[NSDate date]];
if (timeIntervalSinceNow < 0) {
return YES;
} else {
return NO;
}
}
return NO;
}
- (void)timeoutIfSessionShouldTimeOut {
if ([self sessionShouldTimeOut]) {
[self.timeoutManager sendNotificationForTimeout];
}
}
This (I suspect) is the code that's failing. What happens when it fails is the user logs in, hits the home page and locks their phone. After 10+ minutes, they unlock and the app isn't logged out. When they come back, it's the code above that gets executed to log the user out, but in some scenarios it fails - leaving the user still on the homepage when they shouldn't be.
Here's my current theories I'm trying to test:
The timer somehow fires in the background, which then runs the logout routine, but since we're in the background the UI isn't updated but the timer is invalidated (we invalidate the timer after logout) I'm not sure if UI code called from the background will be shown after the app is in the foreground, so this may not be a possibility.
The user actually is coming back a few seconds before the timer fires, then after a few seconds when it should have fired it doesn't since it was backgrounded for 10 minutes. Do timers continue to hit their original fire time if the app goes to the background?
Somehow, while in the background, self.timeoutManager, updateTimer, or fireDate are being released and set to nil, causing the sessionShouldTimeOut method to return NO. Can variables be nilled in the background? What would cause them to if they could be?
The logout routine gets run while the phone is taking a while to actually move to the app, potentially causing the UI updates to not be reflected?
I'm very open to other theories, as you can see a lot of mine are very very edge case since I'm not sure at all what's happening.
I'd appreciate any guidance anyone can offer as to what else I may be able to try, or even any insights into the underworkings of NSTimer or NSRunLoop that may be helpful in this scenario (the documentation on those is terrible for the questions I have)
In AppDelegate.h set
applicationDidEnterBackground:
UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
locationUpdater=UIBackgroundTaskInvalid;
} ];
This tells the os that you still have things going and not to stop it.

iOS. Save state when user exits an application?

For example:
- (void)someFunc {
[self someFunc1];
[self someFunc2];
[self someFunc3];
}
I call someFunc. As I understand if I interrupt the application then the application doesn't guarantee that all the inner code in someFunc will be performed.
I must call someFunc1, someFunc2 and someFunc3 only once.
The problems I don't know how to solve:
someFunc1, someFunc2 and someFunc3 should be called atomically.
storing info for next launch. For example if we successfully have performed someFunc1 only then at next launch the application should call someFunc2 and someFunc3 only.
I know about method applicationWillTerminate:, but I don't know how to solve the current issue with it.
EDITED
Multitasking is not a solution because Even if the device is running iOS 4 or later, the device may not support multitasking., so it doesn't solve the general problem and makes the final solution more difficult only.
EDITED
For those who spam with off topic answers: read the title first - Save state when user exits an application. Where have you seen here putting the application into background?
This does't make sense. If these functions are running on the main thread, there is no way that the application can terminate normally while your functions are running. This is because the events sent like applicationWillTerminate: are sent on the same thread.
If your function is running on a different thread to the main thread, you will need to save some state information after each function completes, but you still have a race condition.
It might be better to check your application's state before running each function. For example, if you have a three step login/registration process with a server, you should query the server to see if the stage has been completed already before running it.
It's difficult to be more specific without knowing what you are doing in these functions.
You should use background tasks !
Take a look at the documentation here :
Executing a Finite-Length Task in the Background
Put the call of someFunc in the middle of the background task.
If your app goes to the background state, you'll have extra time to finish the execution of the method.
Make your functions to return bool, and when you call them, store the bool value to nsdefaults.
When the app restarts,check the bools from sndefaults, and if they are NO, run the functions and update them.
Nobody wants to help. So my temporary solution:
to save a last state I use a writing to a file because it enables to set its operation as atomic/nonatomic
I have replaced this code with something like this:
typedef enum {
state1,
state2,
state3
} MyState;
#property (assign) MyState state;
-(void)someFunc {
switch (state) {
case state1:
{
[self someFunc1];
state = state2;
[self someFunc];
break;
}
case state2:
{
[self someFunc2];
state = state3;
[self someFunc];
break;
}
default:
break;
}
}

What's happen if control UI by performSelectorInBackground?

I read some informations that UI interface only update on MainThread.
I need to asynchronous update some UIButtons, so I use performSelectorInBackground and it's work fine on simulator and Device (iPad4).
[self performSelectorInBackground:#selector(toggleToUpperWhenSingleShift) withObject:nil];
- (void)toggleToUpperWhenSingleShift{
shiftStateIndicator = 1;
for (UIView *aPad in self.subviews) {
if ( [aPad isKindOfClass:[UIButton class]] ) {
UIButton *aButPad = (UIButton *)aPad;
NSMutableString *currentTitle = [NSMutableString stringWithString:[aButPad titleForState:UIControlStateNormal]];
NSString *firstChar = [currentTitle substringToIndex:1];
[currentTitle replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar uppercaseString]];
[aButPad setTitle:currentTitle forState:UIControlStateNormal];
[aButPad setTitle:currentTitle forState:UIControlStateHighlighted];
currentTitle = [NSMutableString stringWithString:[aButPad titleForState:UIControlStateSelected]];
firstChar = [currentTitle substringToIndex:1];
[currentTitle replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar uppercaseString]];
[aButPad setTitle:currentTitle forState:UIControlStateSelected];
}
}
}
I'm worried some unwanted functions will happen if I keep my code. Can anyone explain me detail about performSelectorInBackground?
Why not use it to update UI and why it's OK with my app?
Anyway to debug problem will appreciate!
performSelectorInBackground: is almost never what you want, for almost anything (and certainly not since the creation of GCD). It creates a new thread that you have little control over. That thread will run for as long as the method you dispatch to it.
By "little control" what I mean is that you don't get an NSThread object back, so it is very easy to accidentally call this method many times and fork an unbounded number of threads. I've seen this happen in several programs.
In iOS, you should almost never manually create a thread. GCD and NSOperation handle almost everything that manual threads could do, but better. You typically want thread pooling so you don't spin up and spin down threads all the time. GCD gives you that. You want to cap how many threads you create so you don't overwhelm the processor. GCD gives you that. You want to be able to prioritize your background actions easily. GCD gives you that, too.
All that said, I can't figure out why you're trying to do the above operation on a background thread. Almost all the work is UI updates. You must never, ever, try to modify the UI on a background thread. It is undefined behavior, and when it goes wrong, it goes very wrong. The fact that it's worked in a few cases means nothing. UIKit is not thread safe. You should just call toggleToUpperWhenSingleShift on the main thread. I don't see anything in that should block you, and the overhead of context switching to a background thread really isn't worth it here (even if it were safe, which it's not).
Making UI changes in a background thread is highly advised against by Apple.
You should either use performSelectorOnMainThread or send a message to the UI thread's dispatch queue and do your UI modifications from there.
dispatch_async(dispatch_get_main_queue(), ^{
// your code here
});
It is strongly recommended not to update UI controls etc from a background thread (e.g. a timer, comms etc). This can be the cause of crashes which are sometimes very hard to identify. Instead use these to force code to be executed on the UI thread (which is always the “main” thread).
Go to http://www.ios-developer.net/iphone-ipad-programmer/development/threads/updating-ui-controls-on-background-threads
for further reading.

NSOperation deadlocks and blocks NSOperationQueue

I use a subclass of NSOperation to upload large files to AWS S3 using Amazon's iOS SDK (v1.3.2). This all works fine, but some beta testers experience deadlocks (iOS 5.1.1). The result is that the NSOperationQueue in which the operations are scheduled is blocked as only one operation is allowed to run at one time. The problem is that I cannot reproduce the issue whereas the beta testers experience this problem every single time.
The operation is quite complex due to how the AWS iOS SDK works. However, the problem is not related to the AWS iOS SDK as far as I know based on my testing. The operation's main method is pasted below. The idea of the operation's main method is based on this Stack Overflow question.
- (void)main {
// Operation Should Terminate
_operationShouldTerminate = NO;
// Notify Delegate
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate operation:self isPreparingUploadWithUuid:self.uuid];
});
// Increment Network Activity Count
[self incrementNetworkActivityCount];
// Verify S3 Credentials
[self verifyS3Credentials];
while (!_operationShouldTerminate) {
if ([self isCancelled]) {
_operationShouldTerminate = YES;
} else {
// Create Run Loop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
// Decrement Network Activity Count
[self decrementNetworkActivityCount];
NSLog(#"Operation Will Terminate");
}
The method that finalizes the multipart upload sets the boolean _operationShouldTerminate to YES to terminate the operation. That method looks like this.
- (void)finalizeMultipartUpload {
// Notify Delegate
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate operation:self didFinishUploadingUploadWithUuid:self.uuid];
});
// Operation Should Terminate
_operationShouldTerminate = YES;
NSLog(#"Finalize Multipart Upload");
}
The final log statement is printed to the console, but the while loop in the main method does not seem to exit as the final log statement in the operation's main method is not printed to the console. As a result, the operation queue in which the operation is scheduled, is blocked and any scheduled operations are not executed as a result.
The operation's isFinished method simply returns _operationShouldTerminate as seen below.
- (BOOL)isFinished {
return _operationShouldTerminate;
}
It is odd that the while loop is not exited and it is even more odd that it does not happen on any of my own test devices (iPhone 3GS, iPad 1, and iPad 3). Any help or pointers are much appreciated.
The solution to the problem is both complex and simple as it turns out. What I wrongly assumed was that the methods and delegate callbacks of the operation were executed on the same thread, that is, the thread on which the operation's main method was called. This is not always the case.
Even though this was true in my test and on my devices (iPhone 3GS), which is why I did not experience the problem myself. My beta testers, however, used devices with multicore processors (iPhone 4/4S), which caused some of the code to be executed on a thread different from the thread on which the operation's main method was invoked.
The result of this is that _operationShouldTerminate was modified in the finalizeMultipartUpload method on the wrong thread. This in turn means that the while loop of the main method was not exited properly resulting in the operation deadlocking.
In short, the solution is to update _operationShouldTerminate on the same thread as the main method was invoked on. This will properly exit the while loop and exit the operation.
There are a number of problems with your code, and I can offer two solutions:
1) read up on Concurrent NSOperations in Apple's Concurrency Programming Guide. To keep the runLoop "alive" you have to add either a port or schedule a timer. The main loop should contain a autorelease pool as you may not get one (see Memory Management in that same memo). You need to implement KVO to let the operationQueue know when your operation is finished.
2) Or, you can adopt a small amount of field tested hardened code and reuse it. That Xcode project contains three classes of interest to you: a ConcurrentOperation file that does well what you are trying to accomplish above. The Webfetcher.m class shows how to subclass the concurrent operation to perform an asynchronous URL fetch from the web. And the OperationsRunner is a small helper file you can add to any kind of class to manage the operations queue (run, cancel, query, etc). All of the above are less than 100 lines of code, and provide a base for you to get your code working. The OperationsRunner.h file provide a "how to do" too.

Resources