Deallocation while key value observers still registered (reverse geocoder) - iphone

When my view goes away I get the following message:
An instance 0x1c11e0 of class MKAnnotationView was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
(
Context: 0x0, Property: 0x1e98d0>
)
The code that defines and starts the reverse geocoding is:
geo=[[MKReverseGeocoder alloc] initWithCoordinate:droppedAt];
geo.delegate=self;
[geo start];
I have tried setting geo.delegate to nil right before the I dismiss the view. That would be too easy. I have also tried:
for (id <MKAnnotation> annotation in mvMap.annotations) {
[[mvMap viewForAnnotation:annotation] removeObserver:self forKeyPath:#"selected"];
}
Which throws an error that says:
* Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer for the key path "selected" from because it is not registered as an observer.
My view for annotation code is:
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKAnnotationView *aView;
aView=(MKAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:annotation.title];
if (aView==nil)
aView=[[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotation.title] autorelease];
else
aView.annotation=annotation;
[aView setImage:[UIImage imageNamed:selIcon]];
aView.canShowCallout=TRUE;
aView.draggable=YES;
return aView;
}
I'm sort of pushing buttons and flipping switches here while spinning in. Any idea of what I can do here?

You may have multiple issues, here. For every delegate you set up, you should clear it at dealloc. For every observer you set up, you should clear that, same with Notifications, etc.
So your dealloc should have (typed in web browser, you may have to adjust):
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
for (id <MKAnnotation> annotation in mvMap.annotations)
{
// remove all observers, delegates and other outward pointers.
[[mvMap viewForAnnotation:annotation] removeObserver: self forKeyPath: path];
}
gel.delegate = nil;
// etc., your other stuff
[super dealloc]; // Only in non-ARC legacy code. Modern stuff skips this.
}
Go through your setup (likely in viewDidLoad) and make sure that you un-do everything that you did in there. Specifically anything that triggers a callback.

I have got the same troubles with draggable pins. I have found that the following code solves them:
(void)mapView:(MKMapView *)theMapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState {
if (newState == MKAnnotationViewDragStateStarting) {
// some my code
} else if (newState == MKAnnotationViewDragStateCanceling) {
[annotationView removeObserver:theMapView forKeyPath:#"dragState"];
} else if (newState == MKAnnotationViewDragStateEnding) {
[annotationView removeObserver:theMapView forKeyPath:#"dragState"];
}
}

Related

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.

MKAnnotationView memory leak issue

I needed recently to test my app with Allocations because of memory warnings. Even there are no leaks, the heap keeps growing with annotations added to the map. Every time I zoom in or out, the old annotations are removed, new ones are created and added to the map :
All of the memory locations from the NumberedAnnotationView group show the marked line as the problematic in viewForAnnotation
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
static NSString *reuseId_big = #"bigcircle";
NumberedCircleAnnotationView * nca = nil;
//nca = (NumberedCircleAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:reuseId_big];
if ( nca == nil )
nca = [[[NumberedCircleAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId_big imageType:1] autorelease]; // THIS line
nca.delegate = self;
}
return nca;
}
The init looks like this :
-(id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier imageType:(int)imageType {
self = [super initWithAnnotation: annotation reuseIdentifier: reuseIdentifier]; // THIS line
if (self != nil)
{
// set stuff
}
return self;
}
Even after minutes, these autoreleased objects are still there. ( 17 and 24 is the number of annotations displayed on map and removed with [mapView removeAnnotations:[mapView annotations]]; each time I zoom in/out.
The others, I think, are some MapKit generated stuff. I'm experiencing this in the simulator with versions 5.0 and 5.1.
How could I fix this ? Is something that I'm missing ? Or is this the normal behavior of Mapkit ?
Thanks!
Is there any reason you are not using the
[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
method?
Seems like you are creating view for your annotations every time!
Check out this article:
http://www.highoncoding.com/Articles/804_Introduction_to_MapKit_Framework_for_iPhone_Development.aspx

Memory leak potential; can't use autorelease with a UIViewController

I can't make the static analyzer 'like' this code, but at the same time I cannot autorelease the object that gets stored into controller, so it is useless for the caller. With these 2 static methods I've tried to make it easier to display an activity controller over any view (without blocking tabs).
PZActivityOverlayController *view = [PZActivityOverlayController displayOverView:self.view];
// Later on, when complete
[PZActivityOverlayController remove:view];
Original code:
+ (PZActivityOverlayController *)displayOverView:(UIView *)aView {
PZActivityOverlayController *controller = [[PZActivityOverlayController alloc] initWithFrame:aView.superview.bounds labelText:#"Loading"];
[controller viewWillAppear:YES];
[aView.superview insertSubview:controller.view aboveSubview:aView];
return controller; // Potential leak of object stored into controller
}
+ (void)remove:(PZActivityOverlayController *)display {
[display viewWillDisappear:YES];
[display.view removeFromSuperview];
[display release]; // However it won't leak because it gets released here
}
Perhaps I have the chain of responsibility wrong here, but it's only for convenience. The alternative would be writing what's in the body of these methods everywhere (which violates DRY too much for me).
There's a rule (say, convention) - nonautoreleased objects are returned by methods alloc&init..., new, retain, copy. All the rest methods are HAVE TO return autoreleased object.
In your case I'd rewrite the code above:
....
return [controller autorelease];
}
+ (void)remove:(PZActivityOverlayController *)display {
[display viewWillDisappear:YES];
[display.view removeFromSuperview];
}
...
PZActivityOverlayController *view = [[PZActivityOverlayController displayOverView:self.view] retain];
// Later on, when complete
[PZActivityOverlayController remove:view];
[view release];

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.

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

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.