Remove self as delegate from all - objective-c

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.

Related

Equivalent Storyboard function for Nib

I'm trying to convert a project on macOS that uses Storyboards to instantiate a ViewController through a delegate, although I'm running into some difficulty trying to convert it to use a Nib instead.
Currently the storyboard version of the code uses an App Delegate which is associated with two View Controllers. When a button is clicked the front window animates and flips over revealing another (back) window. The code that instantiates the View Controller is:
mainWindow = [NSApplication sharedApplication].windows[0];
secondaryWindow = [[NSWindow alloc]init];
[secondaryWindow setFrame:mainWindow.frame display:false];
// the below is what I'm not sure of - how to reference nib instead of storyboard?
NSStoryboard *mainStoryboard = [NSStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
NSViewController *vc = [mainStoryboard instantiateControllerWithIdentifier:#"BackViewController"];
[secondaryWindow setContentViewController:vc];
I'm not sure the proper way to reference a nib instead of a storyboard in the example shown above. The project I'm trying to convert is located here. I'm really hoping someone can help, thank you!
This is pretty easy to do. Just make an NSViewController subclass (or an NSWindowController subclass if you want it to control a whole window) for each of the two views. For each view, override -init and have it call super's implementation of -initWithNibName:bundle: with the name of the view's nib file:
#implementation MyViewController
- (instancetype)init {
self = [super initWithNibName:#"MyViewController" bundle:nil];
if (self == nil) {
return nil;
}
return self;
}
Note that if you can require a sufficiently recent version of macOS (I think it's 10.11 and higher off the top of my head, but it's possible that I could be off by a version or so), you don't even have to do this much, because NSViewController will automatically look for a nib file with the same name as the class.
Anyway, now you should be able to instantiate a MyViewController and insert its view into your view hierarchy, and manipulate it the same way you'd manipulate any other view:
MyViewController *vc = [MyViewController new];
[someSuperview addSubview:vc.view];
If you want to do windows instead, you can make an NSWindowController subclass instead of NSViewController. NSWindowController is slightly more annoying to use, since its initializers that take nib names are all convenience initializers, whereas the designated initializer just takes an NSWindow. So if you're using, say, Swift, you can't do it the way I did it above with NSViewController. Objective-C, of course, generally lets you do whatever you want, so you actually can get away with just calling super's -initWithWindowNibName:owner:, and I won't tell anyone, wink wink, nudge nudge. However, to be stylistically correct, you probably should just call -initWithWindow: passing nil, and then override windowNibName and owner:
#implementation MyWindowController
- (instancetype)init {
self = [super initWithWindow:nil];
if (self == nil) {
return nil;
}
return self;
}
- (NSNibName)windowNibName {
return #"MyWindowController";
}
- (id)owner {
return self;
}
This should get you a window controller that you can just initialize with +new (or +alloc and -init if you prefer), then call its -window property and manipulate the window as normal.

initWtihFrame: method for custom textfield not being called when created in a TableCellView

So I'm trying to create a custom NSTableView by subclassing NSTableCellView and a NSTextField inside of the cellview. I'm trying to write some init code in the initWithFrame: method for the NSTextField subclass (TableTextField), but it looks like the initWithFrame: method isn't being called at all, even when I create new rows for the NSTableView, which each contain an instance of the TableTextField. Here's the code in the TableTextField.m file:
#import "TableTextField.h"
#implementation TableTextField
- (id)initWithFrame:(NSRect)frame//This isn't even being called.
{
self = [super initWithFrame:frame];
if (self) {
self.ad = [[NSApplication sharedApplication] delegate];
}
return self;
}
- (void)mouseDown:(NSEvent*) theEvent{
NSLog(#"A");//TEST
self.ad = [[NSApplication sharedApplication] delegate];//This works. Why isn't it happening in init?
[self.ad.classViewController addHomeworkItem];
}
#end
I've been able to solve the problem by just putting the code i need in the mouseDown: method, which is really the only thing I need, but I feel like it's bad practice to redeclare self.ad every time i need to use it, rather than just declaring it once and accessing it for each use, and I can't seem to figure out why initWithFrame: isn't being called. I'm assuming it has to do with the way objects inside NSTableCellViews are initialized, but I haven't found a real explanation. Any suggestions on how to solve this, or explanations as to why it's happening would be welcome. Thanks!
Here's an image of how the NSTableView is set up (with 3 rows):
If you are using XIB files to layout the views, the initialiser method being called is initWithCoder:

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.

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];
...
}

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.