MKMapView still sending messages to delegate after it's superview has been de-alloc'ed - objective-c

EDIT: changed the title. I didn't know it at the time but this is a duplicate of Why am I crashing after MKMapView is freed if I'm no longer using it?
This question is similar to Why is object not dealloc'ed when using ARC + NSZombieEnabled but different enough that I thought it worth throwing out there in case anyone understands and can explain to me what is happening. The other question may be an XCode bug so I presume this could be similar.
Scenario:
RootViewController has a tableView displaying a bunch of items
Selecting a cell presents a modal detailViewController containing another tableView
One of the table cells in detailViewController contains an MKMapView showing the location of the item
mapView.delegate = detailViewController
Dismiss the modal detailViewController
Soon after this, the app crashes b/c the MKMapView sends mapView:viewForAnnotation: to the now dealloc'ed detailViewController. This crash repro'ed on a users device with an ad-hoc distribution build so the issue has nothing to do with NSZombieEnabled.
I was able to resolve the crash by adding:
_mapView.delegate = nil;
to the dealloc method of the tableViewCell containing the mapView.
QUESTION: why is it necessary to nil the delegate when the cell is dealloc'ed? It seems like the mapView should be dealloc'ed by ARC when the cell is dealloc'ed leaving this unnecessary. It is good practice to nil delegates but I didn't think it would be required in this case.
EDIT: all subviews of both detailViewController and the UITableViewCells are declared as (nonatomic, strong) properties ala:
#property (nonatomic, strong) MKMapView * mapView;
EDIT 2: Guess I need to get better at reading the docs. #fluchtpunkt is correct. Here's the relevant info from the MKMapView documentation:
Before releasing an MKMapView object for which you have set a
delegate, remember to set that object’s delegate property to nil. One
place you can do this is in the dealloc method where you dispose of
the map view.

MKMapView is not compiled with ARC and because of that the property for delegate is still declared as assign instead of weak.
From the MKMapView documentation:
#property(nonatomic, assign) id<MKMapViewDelegate> delegate
And from the Transitioning to ARC Release Notes:
You may implement a dealloc method if you need to manage resources other than releasing instance variables. You do not have to (indeed you cannot) release instance variables, but you may need to invoke [systemClassInstance setDelegate:nil] on system classes and other code that isn’t compiled using ARC.
For delegates of system classes (NS*, UI*) you have to use the "old" rule of setting delegates to nil when you deallocate the delegate object.
so add a dealloc method to your detailViewController
- (void)dealloc {
self.mapView.delegate = nil;
}

While it's true that the delegates for such classes should be explicitly set to nil, doing it in dealloc is already too late. You already lost your reference to the mapview during viewDidUnload. You should do the self.mapView.delegate = nil BEFORE viewDidUnload (so probably viewWillDisappear or viewDidDisappear)
From my experience, only MKMapView and UIWebView behave this way.

Related

Remove self as delegate from all

There's a way to remove self as observer from all notifications:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Is there a similar way to remove self as a delegate of all objects that the viewController is a delegate of?
I want to place it in dealloc method in my prototype viewController. The reason is that sometimes when I dismiss a viewController, I get a crash with a message that a UIScrollView (or any of the official subclasses) called a method on its delegate (the viewController) that has since been deallocated. Since I consciously intend to call no such methods, I'd rather set self.delegate = nil. But since this is a prototype viewController, I don't have outlets to those scrollViews, hence my question.
No, there is no such way. Once we are done with the delegate, it requires to simply nil that reference.
Also before calling any delegate method make sure, to check nil condition as well method availability condition like:
if (_delegate != nil && [self.delegate respondsToSelector:#selector(myDelegateMethod:)]) {
// then only call that delegate method
[self.delegate myDelegateMethod:param];
}
Once you are done with all your delegate things nil your reference:
self.delegate = nil;
I don't know any built-in mechanism for it. I think, that the code that was responsible for making your object a delegate should be responsible for freeing it of this responsibility. You could provide some interface for it, but it depends on your code.
Also, since delegates are weak-referenced, they will be automatically set to nil when the delegate object is deleted, but it's not your case, I believe.
UPDATE:
Since in your case delegates don't seem to be declared as weak, I guess, the only option is to keep track of all objects that set your viewController as their delegate manually.
For instance:
In your ViewController:
#interface YourViewController
#property (nonatomic, strong) NSMutableDictionary *objectsThatDelegateSomethingToUs;
#end
#implementation YourViewController
-(void)makeDelegateOfObject:(id)obj withDelegatePropertyName:(NSString*)delegatePropertyName {
[self.objectsThatDelegateSomethingToUs setObject:delegatePropertyName forKey:obj];
}
-(void)dealloc {
for (id obj in self.objectsThatDelegateSomethingToUs.allKeys) {
[obj setValue:nil forKey:[self.objectsThatDelegateSomethingToUs valueForKey:obj]];
}
}
#end
Where you set your viewController as a delegate:
scrollView.delegate = viewController;
[viewController makeDelegateOfObject:scrollView withDelegatePropertyName:#"delegate"];
But, sadly, in this case you'll have to set your ViewController as a delegate programmatically. If delegate property is always called delegate, NSMutableArray should do the trick.
Actually, it's weird that scrollView keeps working when it's parent ViewController is deallocated. May be this is the real problem, and it can be fixed somehow, but, unfortunately, I can't give you any advice on it now, so my answer is trying to deal with the problem that you originally asked about. I recommend you to leave the question open for a while, may be someone else will be able to suggest a better solution.
P.S. Check out Logan's comment to your question. If all objects that use your ViewController as their delegate, are parts of ViewControllers' view hierarchy, then his solution is simpler and more elegant.
Placing this code in my superclass' dealloc method solved my crashes:
for (id view in self.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
[view setDelegate:nil];
}
}
I was considering doing this in my viewController where I have direct access to the UIScrollView. However, because ARC disallows calling [super dealloc], this does not allow the dealloc code in my superclass to be called.

Released UIViewController is correctly destroyed but its -(void)dealloc method is not invoked

When I release a UIViewController, the UIViewController is correctly destroyed but its dealloc method is not called.
If the UIViewController has been destroyed (it is nil in the console), then the retain count should be 0, consequently I expect the -(void)dealloc method to be called.
I've also checked for subclasses of my UIViewController, overriding dealloc without calling the superclass method, but this is not the case.
This is how I initialize it:
myViewController = [[MyViewController alloc] initWithViewController:statusPicker];
What could be the reason ?
Thanks
If the UIViewController has been destroyed (it is nil in the console),
then the retain count should be 0, consequently I expect the
-(void)dealloc method to be called.
Well, no. The fact of being nil, doesn't mean the UIViewController has been released. Put it simple, the pointer for the UIViewController, is now pointing to nil, but the memory where the UIViewController reside is still being occupied. Instead of being called dealloc, two things might be happening:
1) There is something else with a reference to the UIViewController (example: when you pushViewController B from A, A got a reference to B).
2) You got a memory leak.
Again I am basing my answer in what you said:
#Lefteris Automatic Referencing Counting = NO
Check that you haven't got any circular references. For instance if your view controller implements a delegate protocol, check that your code doesn't retain this delegate.

didReceiveMemoryWarning Crash

-Using ARC
-I have 5 separate view controllers all subclassing a class I made called "UIViewControllerWithLoadingView" which subclasses UIViewController.
-In both the subclasses and superclass I allocate and deallocate properties like this:
#property (strong, nonatomic) NSURLConnection *urlConnection;
- (void)viewDidUnload
{
[super viewDidUnload];
self.urlConnection=nil;
}
-Now when didReceiveMemoryWarning is called, the sub classes viewDidUnload method acts fine. BBBUUTTT if I set properties to nil in the super class, UIViewControllerWithLoadingView, the application will crash. Particularly right where I set the properties of the sub class to nil. So for right now I just don't set the properties to nil in the superclass, which becomes problematic because the live bytes just keep piling up at run time.
The rule of thumb is that methods that "clean up"--like dealloc or viewDidUnload--should make the call to super after they do everything else. (And methods that "set up"--like init--call to super first.) I can't tell if that's your problem without seeing all your subclass implementations, but that would be a place to start.
The problem was in the superclass I had a view that extended uiview which had a property reference to the viewcontroller. Well dealloc is automatically called in arc so dealloc would actually set the viewcontroller itself to nil causing a crash. Once I removed the property of the viewcontroller in the custom view class the problem no longer occurred

Trying to set a property value, but it's nil

I declared a protocol in the header file of a Controller that manages a map view.
#protocol UCMapViewDelegate <NSObject>
#required
- (void)pushMapviewRight;
#end
I'm declaring the implementation of the protocol in another view controller (.h) and implement it in the .m file
// in the UCRootViewController.h
#interface UCRootViewController : UIViewController <UCMapviewDelegate>
// in the UCRootViewController.m
- (void)pushMapviewRight
{
NSLog(#"push mapview right");
}
I'm setting the delegate to a property that points to the rootviewController. This is done in the viewDidLoad() of my MapviewController, with a property #property (weak, nonatomic) id<UCMapViewDelegate> delegate;.
// in UCRootViewController
self.mapviewController.rootviewController = self;
// in UCMapViewController
self.delegate = (id<UCMapviewDelegate>)self.rootviewController;
Calling the delegated method. showMenu() gets executed when a button in the mapviewController gets pressed and it works. but the delegate method does NOT get called.
- (void)showMenu
{
NSLog(#"show menu");
[self.delegate pushMapviewRight];
}
But nothing happens.. what is wrong?! Help is greatly appreciated!
I fixed it. At first I used NSLog to verify that self was not nil (which is pretty obvious
, but still) I'm actually not sure why, but self.mapviewController.rootviewController = self; did not "carry over" to the point where I wanted to reference self.rootViewController, although self was not nil at the point where I set it to be the pointer to rootViewController.
I fixed it by creating another initWithRootViewController:(UCRootViewController*) ctrland passed self as an argument when I created the MapViewController.
Can someone explain why the valid reference to self (=rootViewController), was not available in the MapViewController?
Is the rootViewController property a strong reference or a weak one? It should probably be strong. If there are no weak references to the object, then it will be immediately released, and weak references get nilled out when the objects they point to are released. You need a strong reference somewhere in your application for objects to hang around.
As #CodaFi said, your codes look messy. Why do you set delegate property value in UCMapViewController? The delegate should be set in its parent when popup or prepare for the segue.
Basically, if your UCMapViewController has knowledge about UCRootViewController implementing a delegate method, why not call its method from riitVuewController directly? No need to set delegate at all.
Here is one example of using storyboard and segue, UCMyViewController is going to push segue to UCMapViewController:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
...
[segue.destinationViewController setDelegate:self.rootViewController];
...
}

Set value of property in main view controller from dynamically instantiated class (objective-c)

I am totally out of ideas on this... I've tried so many variations that I am dizzy...
I have a main UIViewController which, at the touch of a button, adds another UIViewController to one of its subviews. When the dynamic UIVC gets added, a property in the main UIVC is updated to hold a reference to it (called currentObject). This is working fine.
The problem I am having is that if I add another dynamic UIVC, the property holding the reference is initially updated correctly, but no matter what I try, I can't get the property to update when I touch the first dynamic UIVC. Everything I try to set "currentObject" from a dynamic UIVC gives me an "unrecognized selector sent to class" error and then bails.
I'm holding off from putting code into this post at first. Not sure what I would post that would be helpful.
Thanks in advance!
Updated:
in DynamicModuleViewController.h:
#interface DynamicModuleViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverControllerDelegate, UIGestureRecognizerDelegate, UITextViewDelegate, UIApplicationDelegate, MFMailComposeViewControllerDelegate>{
DynamicModule *currentObject;
}
#property(nonatomic, retain) DynamicModule *currentObject;
in DynamicModuleViewController.m:
#implementation DynamicModuleViewController
#synthesize currentObject;
-(void)addObject:(id)sender
{
DynamicModule *dm = [[DynamicModule alloc]init];
// positioning and PanGesture recognition code to allow dragging of module
currentObject = dm;
[mainView addSubview:currentObject.view];
}
#end
when added this way, from a button tap, it works fine.
Once more DynamicModules are instantiated, I need to update currentObject with the DynamicModule that was tapped last.
adds another UIViewController to one of its subviews
First of all this is a red flag to me. Adding a UIViewController's view as a subview is almost always the wrong way to manage a view hierarchy and a common mistake in iOS apps. Please see http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/ so I can stay on topic and avoid repeating myself.
currentObject = dm;
This sets the ivar backing your currentObject property directly. You are leaking the previous value of currentObject. You don't appear to be removing the previous currentObject's view from mainView. I suspect you are setting the currentObject to an autoreleased object, failing to retain it because you bypassed your setter, and eventually try to send a message to the released object resulting in either an "unrecognized selector" error as your message reaches whatever other object now occupies that memory address or a BAD_ACCESS error as you try to reference an object which no longer exists.
self.currentObject = foo is equivalent to [self setCurrentObject:foo] and is probably what you intended.
What about using the delegate pattern?