Where is this NSView being deallocated to cause bad access error? - objective-c

I am writing an application where the user should be able to click on a button and have the views switch. I have created IBActions that load my method switchSubView on the desired view, but have been having some issues in doing so. I have currently written methods to animate the transition between two NSViews connected through Interface Builder as IBOutlets. I am able to use the methods to transition between two views, but when I try to switch from the initial view to another view, then switch to another, and then switch back, I get EXC_BAD_ACCESS. I can't seem to debug the problem. The debugger shows both of the NSView objects to point to distinct memory locations. To clarify, I would like to reuse the views throughout the duration of the application's execution. I would not like any of the views to be deallocated.
Here is the code:
- (void)switchSubViews:(NSView *)firstView withView:(NSView *)secondView {
[firstView setSubviews:[NSArray array]];
[self prepareViews:firstView];
[[firstView animator] addSubview:secondView];
}
/* Make a "move" animation effect. */
- (void)prepareViews:(NSView *)prepareView {
CATransition * transition = [CATransition animation];
[transition setType:kCATransitionPush];
[transition setSubtype:kCATransitionFromRight];
[prepareView setAnimations:[NSDictionary dictionaryWithObject:transition forKey:#"subviews"]];
[prepareView setWantsLayer:YES];
}
Basically, I would call it like this, given initialView and newView:
[self switchSubViews:initialView withView:newView];
Can anyone help me debug this?
Thanks.
Update: After commenting out the for loop and the removeFromSuperview code to debug, I am still receiving the bad access error. The properties for the view are set to #property (nonatomic, strong), so shouldn't the views be retained? If not, how can I explicitly retain my NSView(s) with ARC enabled? Is it possible to tell ARC to retain it, or is it already retaining the view since it is set with #property(nonatomic, strong)?
Second Update: I researched a little and found out that I can cause ARC to retain the object by creating a strong pointer to the object. I tried it by creating instance variables:
#interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate> {
/* Some instance variables here... */
__strong NSView * retainFirstView;
__strong NSView * retainSecondView;
}
#property (nonatomic, strong) NSView * retainFirstView;
#property (nonatomic, strong) NSView * retainSecondView;
And then in the .m file:
#import <Quartz/Quartz.h> /* Animation. */
#import "AppDelegate.h"
/* Some other imports necessary... */
#implementation AppDelegate
#synthesize retainFirstView;
#synthesize retainSecondView;
/* Some subview switching code... */
[self setRetainFirstView:firstView];
[self setRetainSecondView:secondView];
#end
but I still receive the error. It seems that the view that I am swapping out is consistently being deallocated. I've spent significant time on this and haven't been able to get rid of the error. Any help would be appreciated. If this isn't the proper way to retain the view, please let me know.
Update 3: To test, I have disabled ARC temporarily and did this before calling setSubviews or addSubview:
[firstView retain];
[secondView retain];
and it still failed. I am quite confused, as I no longer see what could be causing the deallocation.
Update 4: I checked for zombies and messages sent to deallocated objects by doing Product -> Profile in Xcode, and to my surprise, I wasn't notified of any. I am very confused on why this is occurring.

You're getting EXC_BAD_ACCESS because calling -removeFromSuperview will deallocate that view if nothing else is retaining it. It's very likely that a view that is being deallocated (therefore having an object pointer that points to garbage) is later passed into -switchSubviews:withView, causing your crash. You should always retain views that you are going to be using later on before calling -removeFromSuperview to make sure that they are not deallocated.
As a sidenote, the code for -switchSubviews:withView: is needlessly complicated. It could be reduced to this:
- (void)switchSubViews:(NSView *)firstView withView:(NSView *)secondView {
firstView.subviews = [NSArray array];
[self prepareViews:firstView];
[[firstView animator] addSubview:secondView];
}

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.

retain cycle inside of block with local scope ivars

For the life of me, I cannot figure out what's going on here.
As an overview, I have an application that I've created a custom navigation bar, a custom containment view controller, and callbacks to tell me when expensive processes have been finished executing inside individual view controllers (such as a large set of images).
Inside my containment view controller when navigating to a child view controller, a call back is set on the child view controller to call the transition animation after it has finished it's set of expensive processes.
The callbacks are created as such
#interface UIViewController (CallBack)
typedef void (^CompletionBlock)(void);
#property (nonatomic, copy) CompletionBlock callBackBlock;
- (void)doneLoadingImages;
#end
static char const *const CompletionBlockTagKey = "CompletionBlockTag";
#implementation UIViewController (CallBack)
#dynamic callBackBlock;
- (CompletionBlock)callBackBlock
{
return objc_getAssociatedObject(self, CompletionBlockTagKey);
}
- (void)setCallBackBlock:(CompletionBlock)callBackBlock
{
objc_setAssociatedObject(self, CompletionBlockTagKey, callBackBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)doneLoadingImages
{
[self callBackBlock]();
self.callBackBlock = nil;
}
#end
These callbacks are set before the child view controller is added through addChildViewcontroller:, and are fired in a dispatch_async block such as this
__block __weak ThisUIViewController *me = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image1 = [UIImage imageForNibFromFileName:#"picture_name"];
dispatch_async(dispatch_get_main_queue(), ^{
me.imageViewToSet.image = image1;
[me doneLoadingImages];
});
});
This process goes through fine the first time a UIViewcontroller gets called from my container view controller through my navigation code. According to the profiler, it dumps memory properly as well after I navigate away from it indicating my retain count is 0 for it.
BUT, what happens when I navigate to the same UIViewcontroller again is that the images are loaded super fast, and the doneLoadingImages gets called super fast as well, causing hang ups on my main thread and causing the UI to become unresponsive until everything has been set UI wise.
My imageForNibFromFileName: method is just a convenience category method that uses imageForContentsOfFile: internally. Anybody have some insight on what I may be doing wrong here?
Turns out it wasn't a retain issue. I'm not exactly sure why, but I had to separate the images into their own dispatch_async call from the global_queue instead of chaining them all in one async block. If anybody has an explanation of why this has to be done or what's going on in the background, that would be appreciated.

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

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.

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?

Problem with NSCollectionView

I'm quite a rookie when it comes to Cocoa programming, so I hope some experts can hint me on the right direction with a problem I'm facing.
I have a NSCollectionView that is bound to a NSArrayController (A) of elements having two properties: an URL to a picture and a NSArray (B) of elements of another class.
For every element inside the NSArrayController (A) , I load a subview with a NSImageView that should display the image in the URL and a NSTableView that is bound to the elements of the NSArray (B).
Everything works fine, except for one very important thing: the URL to the picture is not immediately available when I create the subview, it becomes available later, but when it does, I don't see the picture loading in the NSImageView.
I would think of a problem in the KVC/KVO implementation, but the strange thing is that the image loads correctly if, when the URL becomes available, the subview is not visible (e.g in a portion of the scrollview that is not displayed).
Example: The NSScrollview size is such that it can display only two subviews at a time. I add three elements to the NSArrayController (A): the first two images don't load, if I scroll down the scrollview to see the third element, I find the image loaded correctly.
Any ideas about what could cause such a strange behaviour?
Thank you in advance
Luca
series.h
#interface Series : NSObject {
#private
NSMutableString * banner;
}
-(Series*)initWithEpisode:(Episode*)ep;
-(void)setBanner:(NSString*)_banner;
#property (retain, readwrite) NSMutableString *banner;
#end
series.m
#implementation Series
#synthesize banner;
-(Series*)initWithEpisode:(Episode*)ep
{
self = [super init];
if(self){
banner = [[NSMutableString alloc]initWithString:#"some invalid URL"];
}
-(void) setBanner:(NSString*)_banner
{
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
}
-(void)dealloc
{
[super dealloc];
[banner release];
}
SeriesListViewController.m
-(void)VideoChangedStatus:(Episode*)ep{
//This is the delegate called by the object Episode when it retrieves the URL via asynchronous call to a webservice
Series *tmp = [[Series alloc]initWithEpisode:ep];
[[seriesList objectAtIndex:[seriesList indexOfObject:tmp]]setBanner:[ep banner]];
}
The binding is done in the subview nib file, to the NSImageView: I set File's Owner of type NSCollectionViewItem and then bind Valueurl to representedObject.banner
I didn't subclass NSCollectionView nor NSCollectionViewItem
After days of trying I found a solution that works: apparently it's not enough to use the setString method, I need to re-intialize the property inside the setBanner method
-(void) setBanner:(NSString*)_banner
{
banner = [NSMutableString[NSString stringWithFormat:#"some root path/%#", _banner]];
}
Still, I'd be very glad to know if someone has an explanation of why setString was causing that strange (to me) problem and why this solution works.
Thank you
Luca
I’m not sure why you’ve declared banner to be a mutable string — it looks like an immutable string would suffice.
At any rate, when you write a custom setter method you need to send -willChangeValueForKey: and -didChangeValueForKey: to ensure KVO (and hence bindings) compliance:
-(void) setBanner:(NSString*)_banner
{
[self willChangeValueForKey:#"banner"];
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
[self didChangeValueForKey:#"banner"];
}