Specific expression in if condition causes 7 second delay in execution [duplicate] - iphone

Reposting with more concise and focused question after original question went unanswered. Also adding more insight into the problem after another day of research:
In my app delegate (didFinishLaunching), I set up a callEventHandler on CTCallCenter.
The idea is that when a callState changes, I post a notification with a userInfo dict
containing the call.callState. In my view, I observe this notification, and when the
userInfo dict contains a value of CTCallDisconnected, I want to unhide a view.
The problem I'm having is that the unhiding aspect is taking, almost consistenly, ~ 7 seconds.
Everything else is working fine, and I know this because I NSLog before and after the unhiding,
and those logs appear immediately, but the darned view still lags for 7 seconds.
Here's my code:
appDidFinishLaunching:
self.callCenter = [[CTCallCenter alloc] init];
self.callCenter.callEventHandler = ^(CTCall* call) {
// anounce that we've had a state change in our call center
NSDictionary *dict = [NSDictionary dictionaryWithObject:call.callState forKey:#"callState"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"CTCallStateDidChange" object:self userInfo:dict];
};
I then listen for this notification when a user taps a button that dials a phone number:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ctCallStateDidChange:) name:#"CTCallStateDidChange" object:nil];
Then, in ctCallStateDidChange:
- (void)ctCallStateDidChange:(NSNotification *)notification
{
NSLog(#"121");
NSString *callInfo = [[notification userInfo] objectForKey:#"callState"];
if ([callInfo isEqualToString:CTCallStateDisconnected]) {
NSLog(#"before show");
[self.view viewWithTag:kNONEMERGENCYCALLSAVEDTOLOG_TAG].hidden = NO;
NSLog(#"after show");
}
}
I've tracked the problem down to the if condition in the above code sample:
if ([[userInfo valueForKey:#"userInfo"] valueForKey:#"callState"] == CTCallStateDisconnected) {
If I simply replace that with:
if (1 == 1) {
Then the view appears immediately!
The thing is, those NSLog statements are logging immediately, but the view is
lagging in it's unhiding. How could that condition cause only part of it's block
to execute immediately, and the rest to wait ~ 7 seconds?
Thanks!

Try changing your code to this:
- (void)ctCallStateDidChange:(NSNotification *)notification
{
NSLog(#"121");
NSString *callInfo = [[notification userInfo] objectForKey:#"callState"];
if ([callInfo isEqualToString:CTCallStateDisconnected]) {
NSLog(#"before show");
[self.view viewWithTag:kNONEMERGENCYCALLSAVEDTOLOG_TAG].hidden = NO;
NSLog(#"after show");
}
}
Note:
The parameter is an NSNotification, not an NSDictionary
I would not compare strings with ==
No need to cast the view to change the hidden property
Use NO instead of false
Update: Got an idea: Could you try the following, please, in between the NSLogs?
dispatch_async(dispatch_get_main_queue(), ^{
[self.view viewWithTag:kNONEMERGENCYCALLSAVEDTOLOG_TAG].hidden = NO;
});
Reading the CTCallCenter doc, it seems the callEventHandler is dispatched on "the default priority global dispatch queue", which is not the main queue where all the UI stuff happens.

Looks like there is no problem with your hidden code. If I were you, I would comment out all the code after the call ends, and uncomment them one by one to see what is the problem.

Hm... try to call [yourViewController.view setNeedsDisplay] after you change hidden property. Or avoid hidden, use alpha or addSubview: and removeFromSuperview methods instead.

djibouti33,
Where you put this sentence to listen when a user taps a button that dials a phone number?on WillResignActive function?
this sentence --> [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ctCallStateDidChange:) name:#"CTCallStateDidChange" object:nil];
Thanks for your time,
Willy.

Related

CTCallState event handler not getting called if no breakpoint is set

I am experiencing the weirdest bug ever, I am running the app on my iPhone, and I have the following code:
self.callCenter = [[CTCallCenter alloc] init];
[self.callCenter setCallEventHandler: ^(CTCall* call) {
if ([call.callState isEqualToString: CTCallStateDisconnected]) {
NSLog(#"call ended");
dispatch_sync(dispatch_get_main_queue(), ^(void) {
NSLog(#"on main thread!");
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:weakSelf.recordRef forKey:kContactKey];
[[NSNotificationCenter defaultCenter] postNotificationName:kCallEndedNotification object:nil userInfo:userInfo];
});
}
}];
For some reason, this block of code is not called. But if I put a breakpoint on the if statement, then it gets called. Any idea why this might be happening?
It's, I believe, an iOS SKD 6 bug. Because event "CTCallStateDisconnected" should be called in the moment, when your app is in background, so it's not called at all. The reason, why it's ok when you put breakepoint there, is, I think, because of time when the event is called. If it's stopped at breakepoint, call is still going and event is called when your app is in foreground.
Look here, in iOS5 this was working:
setCallEventHandler test

iphone - notification not being fired

Goal:
I want to use the Observer Pattern so that when one uiimageview receives a different background image, then 2 other uiimageviews will listen for that change, and then change themselves.
Strategy:
Based on what I read about observer pattern in objective-c, I decided to implement the nsnotificationcenter.
Code:
self refers to the RemoteViewManagerController, updateButtons is the method that will be called when the ImageSwap event is fired, and object refers to the "main" uiimageview, that is, the uiimageview that when changed will cause changes in other uiimageviews.
- (void)registerButtonObserver:(UIView *)currentView
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateButtons:) name:#"ImageSwap" object:[self.view viewWithTag:1]];
}
setDefaultButtons is invoked, and we iterate through the buttons and target buttons based on tag. The "main" uiviewimage has a tag of 1. So we call setImageChange to change the background image of that button, and as a result, I want to fire the ImageSwap event, to change the other two uiimageview buttons, and I pass in those buttons part of the userinfo dictionary. The idea is when updateButtons is invoked, I can reference those buttons in the userinfo dictionary.
- (void)setDefaultButtons:(UIView *)currentView
{
for( UIView *view in currentView.subviews ) {
if( [view isKindOfClass:[UIButton class]] ) {
if( view.tag == 1 ){
[self setImageChange:#"fence" forButton:view];
NSArray *keys = [NSArray arrayWithObjects:#"subview1", #"subview2", nil];
NSArray *objects = [NSArray arrayWithObjects:[self.view viewWithTag:4], [self.view viewWithTag:5], nil];
NSDictionary *items = [NSDictionary dictionaryWithObjects:objects
forKeys:keys];
NSLog(#"But we sure to get here right");
[[NSNotificationCenter defaultCenter]postNotificationName:#"ImageSwap" object:view userInfo:items];
}
else if(view.tag == 2){
[self setImageChange:#"siren" forButton:view];
}
else if(view.tag == 3){
[self setImageChange:#"auxiliary" forButton:view];
}
}
}
}
Note that I know that we get to the postNotificationName line, because this line does fire: NSLog(#"But we sure to get here right");
I don't get any errors. But this line in RemoteViewManagerController.m:
- (void)updateButtons:(NSNotification*)notification
{
NSLog(#"Do we get here?");
}
is never called.
I believe that when two subviews have the same tag, -viewWithTag: just returns the first one that it finds. So if there happen to be two views with tag=1, it's quite possible that you're observing the wrong one. Try changing the object parameter in you -addObserver... call to nil, which will indicate that you want to observe that notification for all objects.

"EXEC_BAD_ACCESS" error when reading NSUserDefaults

I'm having an error occur when I read the NSUserDefaults (via InAppSettingsKit). I'm not sure if it's my code that's the issue though. I have set up an observer to check if there are any changes to the NSUserDefaults:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(setOptions)
name:NSUserDefaultsDidChangeNotification
object:nil];
The method this calls is used to update the 'map type' of an MKMapView:
- (void)setOptions
{
// Get the map style
NSString *mapStyle = [[NSUserDefaults standardUserDefaults] stringForKey:kMapType];
// Update map style
if ([mapStyle isEqualToString:#"Hybrid"])
{
map.mapType = MKMapTypeHybrid;
}
else if ([mapStyle isEqualToString:#"Map"])
{
map.mapType = MKMapTypeStandard;
}
else if ([mapStyle isEqualToString:#"Satellite"])
{
map.mapType = MKMapTypeSatellite;
}
[mapStyle release];
}
The app is set up such that you press a button and the InAppSettingsKit is initialised, within this I change the setting for the map type to be displayed and go back to the main screen in my app. At this point the map seems to update correctly and there are no issues. The issue occurs when I try to re-launch the InAppSettingsKit to change the map type again.
Does anyone know if it's my code that's the issue, if so how can I go about fixing it?
just remove the code-line: [mapStyle release]
the stringForKey: will return an autoreleased NSString. So your code is not responsible for releasing. It works fine in the first iteration because the first release call will dealloc that string but the NSUserDefaults still has a pointer to that String but is not used. In the second iteration you get that pointer and try to call isEqualToString on that dealloc-ed object wich will cause the BAD_ACCESS

Unhiding a view is very slow in CTCallCenter callEventHandler

Reposting with more concise and focused question after original question went unanswered. Also adding more insight into the problem after another day of research:
In my app delegate (didFinishLaunching), I set up a callEventHandler on CTCallCenter.
The idea is that when a callState changes, I post a notification with a userInfo dict
containing the call.callState. In my view, I observe this notification, and when the
userInfo dict contains a value of CTCallDisconnected, I want to unhide a view.
The problem I'm having is that the unhiding aspect is taking, almost consistenly, ~ 7 seconds.
Everything else is working fine, and I know this because I NSLog before and after the unhiding,
and those logs appear immediately, but the darned view still lags for 7 seconds.
Here's my code:
appDidFinishLaunching:
self.callCenter = [[CTCallCenter alloc] init];
self.callCenter.callEventHandler = ^(CTCall* call) {
// anounce that we've had a state change in our call center
NSDictionary *dict = [NSDictionary dictionaryWithObject:call.callState forKey:#"callState"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"CTCallStateDidChange" object:self userInfo:dict];
};
I then listen for this notification when a user taps a button that dials a phone number:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ctCallStateDidChange:) name:#"CTCallStateDidChange" object:nil];
Then, in ctCallStateDidChange:
- (void)ctCallStateDidChange:(NSNotification *)notification
{
NSLog(#"121");
NSString *callInfo = [[notification userInfo] objectForKey:#"callState"];
if ([callInfo isEqualToString:CTCallStateDisconnected]) {
NSLog(#"before show");
[self.view viewWithTag:kNONEMERGENCYCALLSAVEDTOLOG_TAG].hidden = NO;
NSLog(#"after show");
}
}
I've tracked the problem down to the if condition in the above code sample:
if ([[userInfo valueForKey:#"userInfo"] valueForKey:#"callState"] == CTCallStateDisconnected) {
If I simply replace that with:
if (1 == 1) {
Then the view appears immediately!
The thing is, those NSLog statements are logging immediately, but the view is
lagging in it's unhiding. How could that condition cause only part of it's block
to execute immediately, and the rest to wait ~ 7 seconds?
Thanks!
Try changing your code to this:
- (void)ctCallStateDidChange:(NSNotification *)notification
{
NSLog(#"121");
NSString *callInfo = [[notification userInfo] objectForKey:#"callState"];
if ([callInfo isEqualToString:CTCallStateDisconnected]) {
NSLog(#"before show");
[self.view viewWithTag:kNONEMERGENCYCALLSAVEDTOLOG_TAG].hidden = NO;
NSLog(#"after show");
}
}
Note:
The parameter is an NSNotification, not an NSDictionary
I would not compare strings with ==
No need to cast the view to change the hidden property
Use NO instead of false
Update: Got an idea: Could you try the following, please, in between the NSLogs?
dispatch_async(dispatch_get_main_queue(), ^{
[self.view viewWithTag:kNONEMERGENCYCALLSAVEDTOLOG_TAG].hidden = NO;
});
Reading the CTCallCenter doc, it seems the callEventHandler is dispatched on "the default priority global dispatch queue", which is not the main queue where all the UI stuff happens.
Looks like there is no problem with your hidden code. If I were you, I would comment out all the code after the call ends, and uncomment them one by one to see what is the problem.
Hm... try to call [yourViewController.view setNeedsDisplay] after you change hidden property. Or avoid hidden, use alpha or addSubview: and removeFromSuperview methods instead.
djibouti33,
Where you put this sentence to listen when a user taps a button that dials a phone number?on WillResignActive function?
this sentence --> [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ctCallStateDidChange:) name:#"CTCallStateDidChange" object:nil];
Thanks for your time,
Willy.

UIProgressView update fail with UINotification

I try to solve this problem for several days now I have to ask you...
I've got a View (and a ViewController) with a UITableview. There is a TableViewController for that table which is generated in the ViewController. The TableViewController calls a DataSyncManager sharedInstant object (which is obviously in a separate class) which starts to sync data with the server.
I do it this way (first the refresh method):
-(void) refresh{
[serverQueueProgressView setProgress:0.0];
[syncingLabel setAlpha:0.5];
[serverQueueProgressView setAlpha:1];
[self performSelector:#selector(reloadTableViewDataSource) withObject:nil afterDelay:1.0];
}
Then the method reloadTableViewDataSource (of TableViewController) is called:
- (void)reloadTableViewDataSource
{
[dataSyncManager getEntriesFromServer];
}
dataSyncManager is my sharedInstance.
In the getEntriesFromServer method of dataSyncManager I do the loop with different sync items and call everytime
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ServerQueueProgress"
object:progress];
with the proper progress as NSNumber (that part works well). The message is now sent and catched by my ViewController (it works, I checked with a breakpoint, it also gets the right progress-NSNumber and converts it to float):
- (void)serverQueueProgress:(NSNotification *)notification {
if(![NSThread isMainThread])
{
[self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
return;
}
[queueProgressView setProgress:[[notification object] floatValue]];
}
This is one solution which I found here on stackoverflow. But the if is always skipped because obviously I'm on main thread.
Unfortunately the UIProgressview doesn't get updated, it just hangs around, but I connected it well in Interface Builder (I checked that by setting the progress in another method of ViewController.
I also tried to catch the Notification with my TableViewController and put in some other solutions, but no chance, the UIProgressView doesn't get updated live. Only after the sync is done.
Here is the mentioned code in TableViewController which also gets executed without errors (I also stepped it to make sure every line gehts executed well):
This is the method called when received a the notification:
- (void)serverQueueProgress:(NSNotification *)notification {
[self performSelectorOnMainThread:#selector(updateProgress:) withObject:[notification object] waitUntilDone:NO];
[serverQueueProgressView setProgress:[[notification object] floatValue]];
}
Which also calls updateProgress: of the same class:
- (void)updateProgress:(NSNumber *)newProgressValue {
[serverQueueProgressView setProgress:[newProgressValue floatValue]];
}
No chance. I tried many ways and implemented some in parallel as you see, but the ProgressView won't get updated live. Only at the end of syncing. What am I doing wrong??
EDIT: Here is my getEntriesFromServer and some other stuff in DataSyncManager:
- (void)getEntriesFromServer
{
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SynchingStarted"
object:nil];
[self completeServerQueue];
...
}
and completeServerQueue is the function which sends messages to my ViewController with the proper progress float value (it's only a dummy for loop, which gets executed properly... I've checked it):
- (void)completeServerQueue {
NSNumber *progress = [[NSNumber alloc] init];
for (int i=0; i<15; i++) {
progress = [[NSNumber alloc] initWithFloat:(100/15*i) ];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ServerQueueProgress"
object:progress];
sleep(1);
}
}
also, when you're having trouble, break the problem down a bit. Instead of:
[serverQueueProgressView setProgress:[[notification object] floatValue]];
do this;
float prog = [notification object] floatValue];
[serverQueueProgressView setProgress:prog];
then debugging would give a clue that this isn't working.
my guess is the problem isn't the code you've shown here, but other code in getEntriesFromServer. Are you using NSURLConnection? Something like:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
then you will get callbacks asynchronously that you can use to update your progress view.