Summary
I am trying to dynamically subclass objects to do some cleanup before dealloc. I add a subclass to the object and add my own dealloc method that does the cleanup and then calls [super dealloc]. This works for most cases but I am running into something strange when it happens to UIViewControllers. It seems some cleanup is not happening in dealloc because I am getting a crash when -hash is being sent to a deallocated view controller.
This happens when creating a view for a new view controller and it is growing some hash set in a class method of UIViewController. It seems to be a hash of view controllers for views because it is in a method +[UIViewController setViewController:forView:].
If I do not do the add my own dealloc method to the dynamic subclass everything is fine. Even if I only call [super dealloc] in my own version of dealloc it crashes the same way.
Does anyone have any idea what could be going wrong? Do I need to do something else other than calling [super dealloc] so that it still executes everything it should?
The Code
The dealloc method looks like this:
- (void)deallocWithRemoveAllAssociatedBindings {
[[BindingManager sharedInstance] removeAllBindingsAssociatedWithObject:self];
[super dealloc];
}
My dynamic swizzling method looks like this:
+ (void)createSubclassForObject:(id)object {
Class objectClass = object_getClass(object);
NSString *objectClassString = NSStringFromClass(objectClass);
NSString *subclassName = [NSString stringWithFormat:#"RemoveAllAssociatedBindings_%#", objectClassString];
Class subclass = objc_getClass([subclassName UTF8String]);
if (!subclass) {
subclass = objc_allocateClassPair(objectClass, [subclassName UTF8String], 0);
if (subclass) {
Method dealloc = class_getInstanceMethod(self, #selector(deallocWithRemoveAllAssociatedBindings));
class_addMethod(subclass, #selector(dealloc), method_getImplementation(dealloc), method_getTypeEncoding(dealloc));
[self addRemoveMethodToClass:subclass];
objc_registerClassPair(subclass);
}
}
if (!!subclass) {
object_setClass(object, subclass);
}
}
You can see the full code on github: https://github.com/drewag/property-bindings
you are never supposed to call dealloc on your own, thats apple's job. my suggestion would be to override the standard dealloc method and add what ever checks you need with an if statement to do your "custom dealloc" stuff. or you could just call your custom dealloc from inside dealloc just like how [super dealloc] is called... or just use ARC.
Related
I have this myViewController, that instantiates instances of itself.
Currently, I have a UIButton, that triggers the method
-(void)somethingImportant
However, I want that somethingImportant to happen during the ViewDidLoad, so I don't have to push that button.
But if I put somethingImportant in the ViewDidLoad of myViewController, it is recursively called as many times I have a subview of myViewController.
I tried to put somethingImportant in the application didFinishLaunchingWithOptions: of my app delegate, but somehow that does't work either.
EDIT
So here's the code that might be relevant. I have this UIScrollView with a lot of subviews of myViewController:
- (void)configureScrollView
{
for (int i = 0; i < [self.childViewControllers count]; i++) {
...
myViewController * theSubview =[self.childViewControllers objectAtIndex:i];
....
[theScrollView addSubview:[theSubview view]];
}
}
What is the best approach to make sure that somethingImportant is called only once?
I have this class, that instantiates instances of itself.
This inherently sounds like a bad idea and can easily lead to recursion if you're not careful. Therefore I would suggest you rethink your logic. If you need multiple instances of a class, you should be managing those instances from outside that class, not from within.
However, if you're still insistent on doing this - you can do something similar to what sschale suggests and use a variable to keep track of whether you've called your method or not.
The thing is you'll need to define this variable as static in order for it to be stored at class scope, not instance scope.
For example:
static BOOL firstCalled = NO;
- (void)viewDidLoad {
[super viewDidLoad];
if (!firstCalled) {
firstCalled = YES;
[self foo];
}
}
Each subclass should be calling [super viewDidLoad], on up the chain, so that code really should only be called once.
However, if you need to make sure it executes only once, add #property (nonatomic) BOOL runOnce; to that file's interface, and then in -(viewDidLoad) do:
if(!self.runOnce) {
//all code that is only run once
self.runOnce = YES;
}
In iOS, I pop from current viewController into previous one, but it doesn't go into dealloc.
Is this because there is another pointer pointing towards the current viewController, either in a different viewController or in the current one?
This is where I pop to previous view:
- (IBAction)fileUploadCancelTouched:(UIButton *)sender {
[self.fileToUpload cancel];
[self.view hideToastActivity];
[self.greenprogressBar removeFromSuperview];
[self.subView removeFromSuperview];
self.fileUploadCancelButton.hidden = YES;
if (self.commandComeBackToFinalScreen == 1) {
[self.navigationController popViewControllerAnimated:YES];
}
}
This is my dealloc function:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.greenprogressBar = nil;
self.fileUploadCancelButton = nil;
self.fileToUpload = nil;
[buttonHome_ release];
[buttonTestMeAgain_ release];
[buttonMarkMyTest_ release];
[examId_ release];
[sender_ release];
self.ob = nil;
[_fileUploadCancelButton release];
[super dealloc];
}
Check to make sure that ARC is not enabled in your project. If it is not ARC enabled then dealloc should be called unless your code is retaining your view controller. You should check through the Instruments tool if your pop commands reduces memory or not.
There may be some other reasons as mentioned in another answer that I am posting below:
The obvious reason is that something is retaining your viewController. You will have to look closely at your code. Do you do anything that in your class that uses delegates, since they sometimes retain the delegate. NSURLConnection will retain your class, and so does NSTimer. You can scatter code in you class and log your class's retain count, and try to find out where. In the code you showed so far the retain could should just be 1, since the class is only retained by the navigation controller.
Also, before you pop your view, get a reference to it, pop it with NO animation, and then send it some message that has it report the retain count (this would be some new method you write). That new method could also log other things, like whether it has any timers going, NSURLConnections, etc.
First of all, get rid of [super dealloc]. I know that's intuitive, but the documentation says don't do it.
In my own case, I had an observer & timer in my dealloc method, but that wouldn't run since the timer had a strong pointer to the controller.
Created a dedicated clean up method which removed the observer & invalidated the timer. Once that ran, the controller was correctly deallocated.
I'm trying to implement state restoration in an app that uses iOS 6+ and storyboards, but I am having problems finding a way to prevent duplicate calls to heavy methods.
If I simply start the app, then I need to setup the UI in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
}
This works fine in a normal, non-state-restoration world. Now I've added state restoration and after restoring some properties I need to update the UI with those properties:
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
// restore properties and stuff
// [...]
[self setupUI];
}
So what happens now is that first the setupUI method is called from viewDidLoad, and then again from decodeRestorableStateWithCoder:. I don't see a method that I can override that's always called last.
This is the normal order of method calls:
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
When using state restoration, this is called:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder
viewWillAppear
viewDidAppear
I can't place the call to setupUI in viewWillAppear because then it would also be executed every time you native back to a view.
It would be much handier if decodeRestorableStateWithCoder was called BEFORE viewDidLoad because then you could use restored properties. Sadly that not the case, so... how can I prevent doing the work in viewDidLoad when I know that I need to do it all over again in decodeRestorableStateWithCoder right after?
If you're doing state restoration programatically (i.e. not using storyboards), you can use + viewControllerWithRestorationIdentifierPath:coder:, init the view controller there and use whatever you need from the coder to do your pre-viewDidLoad initialization.
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
if ([[identifierComponents lastObject] isEqualToString:kViewControllerRestorationIdentifier]) {
if ([coder containsValueForKey:kIDToRestore]) {
// Can only restore if we have an ID, otherwise return nil.
int savedId = [coder decodeIntegerForKey:kIDToRestore];
ViewController *vc = [[ViewController alloc] init];
[vc setThingId:savedId];
return vc;
}
}
return nil;
}
I've found that trying to implement state restoration has shown up bad programming practices in my code, like packing too much into viewDidLoad. So while this works (if you're not using storyboards), the other option is to refactor how you're setting up your view controllers. Instead of using a flag, move code pieces to their own methods and call those methods from both places.
#property (nonatomic) BOOL firstLoad;
- (void)viewDidLoad {
[super viewDidLoad];
self.firstLoad = YES;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.firstLoad) {
[self setupUI];
self.firstLoad = NO;
}
}
Thanks to #calvinBhai for the suggestion.
Funny enough the decoding sequence is even different and exactly:
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
and it totally makes sense like this.
From the book "Programming iOS 9: Dive Deep into Views, View Controllers, and Frameworks" pages 386-387
The known order of events during state restoration is like this:
application:shouldRestoreApplicationState:
application:viewControllerWithRestorationIdentifierPath:coder:
viewControllerWithRestorationIdentifierPath:coder:, in order down the chain
viewDidLoad, in order down the chain; possibly interleaved with the foregoing
decodeRestorableStateWithCoder:, in order down the chain
application:didDecodeRestorableStateWithCoder:
applicationFinishedRestoringState, in order down the chain
You still don’t know when viewWillAppear: and viewDidAppear: will arrive, or whether viewDidAppear: will arrive at all. But in applicationFinishedRestoringState you can reliably finish configuring your view controller and your interface.
Yes, it would indeed be nicer if -decodeRestorableStateWithCoder: were called before -viewDidLoad. Sigh.
I moved my view setup code (which depends on restorable state) to -viewWillAppear: and used dispatch_once(), instead of a boolean variable:
private var setupOnce: dispatch_once_t = 0
override func viewWillAppear(animated: Bool) {
dispatch_once(&setupOnce) {
// UI setup code moved to here
}
:
}
The documentation states that "views are no longer purged under low-memory conditions" so dispatch_once should be correct for the lifetime of the view controller.
Adding to berbie's answer,
The actual flow is:
initWithCoder
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
Be aware that inside initWithCoder, you need to set self.restorationClass = [self class]; This will then force viewControllerWithRestorationIdentifierPath:coder: to be called.
I noticed that setting the splitViewController.delegate in willFinishLaunchingWithOptions causes viewDidLoad to be called even earlier. So if you move that to both didFinishLaunchingWithOptions then you can successfully configure your view controller inside - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder before viewDidLoad is called. It might be useful for you to do it there anyway since you'll have access to AppDelegate objects like persistentContainer.viewContext rather than need to register that object with restoration so it could have been accessed by reference in the ViewController's - (void)decodeRestorableStateWithCoder:(NSCoder *)coder.
One correction to MixedCase flow (which was very helpful, thank), the actual call flow is a bit different :
This is the normal order of method calls:
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
When using state restoration, this is called:
viewControllerWithRestorationIdentifierPath (decode any data that is needed for regular start-up)
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
decodeRestorableStateWithCoder (decode restorable state data, and set your controller UI)
I spent much time to get a better understanding in delegation in Objective-C. I got it working for most cases, but there is a problem in a specific case, which I find difficult to understand. Let me explain what I am trying to do:
I have a custom view called GridLayoutView, which is subclass of UIView. I also have a view controller SomeViewController, which is the delegate of GridLayoutView.
I have a custom initWithFrame method, and I am conditionally calling another initialization method baseInit. That method calls a delegate method at some time. Here is some code from GridLayoutView:
//
// Delegator
// GridLayoutView.m
//
#implementation GridLayoutView
- (id)initWithFrame:(CGRect)frame
numberOfRows:(NSUInteger)rows
numberOfCols:(NSUInteger)cols
{
self = [super initWithFrame:frame];
if (self) {
self.numberOfRows = rows;
self.numberOfCols = cols;
self.numberOfCells = rows * cols;
if (self.numberOfCells > 0) [self baseInit];
}
return self;
}
- (void)baseInit
{
// do some more initialization stuff here
// ...
// then call a delegate method
[self.delegate someMethod:someObj];
// However, this method is not called because self.delegate is nil
}
and some code from SomeViewController:
//
// Delegate
// SomeViewController.m
//
#implementation SomeViewController
// ...
// in some method
self.gridLayoutView = [[GridLayoutView alloc] initWithFrame:gridLayoutFrame
numberOfRows:rowsCount
numberOfCols:colsCount];
self.gridLayoutView.delegate = self;
// ...
The delegate method never gets called within baseInit, because the delegate is nil at that time and it gets set after initWithFrame and baseInit methods are done. I have confirmed this.
I sense that there is something wrong in my workflow of delegation. I have a solution but I don't think it is the best way to go. The solution is basically passing the SomeViewController instance to the delegator by modifying the initWithFrame method such as:
- (id)initWithFrame:(CGRect)frame
numberOfRows:(NSUInteger)rows
numberOfCols:(NSUInteger)cols
delegate:(id<GridLayoutViewDelegate>)aDelegate
This approach works, but I am uncomfortable due to passing SomeViewController to GridLayoutView in its initWithRect. I am wondering if this is a good way to go with delegation or is there a better approach? I would be very grateful if someone can clear this for me.
If I'm understanding you correctly, there aren't many options here.
Modifying your initializer (as you suggested) to pass in the delegate. There is nothing wrong with that, don't know why you don't like it.
Remove the dependency on the delegate during initialization and instead, send whatever delegate message is appropriate when the delegate property is set by overriding the setter:
- (void)setDelegate:(id<GridLayoutViewDelegate>)aDelegate
{
_delegate = aDelegate;
// send whatever message makes sense to the delegate
[_delegate someMethod:object];
}
EDIT - noticed your comment
Your initialization method should not take any significant amount of time. It's unclear what you mean by 'loading views'. If you simply mean creating and adding subviews to a view then that is fast and there should be no need to communicate progress to a delegate (which you can't do anyway b/c the initialization is on the main thread and UI won't update until all of init is complete).
If you mean loading data that takes a long time, you should disconnect that from initialization and load the data in a background operation, sending progress messages to a delegate.
i would implement the setDelegate function and then call
[self someMethod:someObj]; from there
I have a small question on what super means in xcode. For instance I have the following code. Would it work if i said [super dealloc] first in the dealloc method? Or should super always come last? What exactly is super - I know its the super class but is it the parent class or?
This is the .h file for this class
#interface TwitterTableViewController : PullRefreshTableViewController<TweetDelegate>
This is some code from the .m class for the above interface
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[self.twitterManager release];
[tweets release];
[lastRefreshedLabel release];
[super dealloc];
}
I have checked all over and dont have a satisfactory knowledge yet. If anyone can explain this in laymans terms would be best. thanks
super calls the implementation of the super-class. In your case this would be PullRefreshTableViewController.
Regarding if you should call super before/after your own code, it really depends on what you want to achieve by overriding the method.
In case of dealloc have a look at this question, as Nick Bull mentioned.
The super class can be regarded as a "parent class", yes, if you mean. [super dealloc] calls the implementation of the dealloc method of the super/parent class of the current object (or class, if you call it from a class method).
But, there's no such rule that "super must always come last". The reason why dealloc must always come last is that it destroys the object, so if you accessed your object after it returned, that would crash.
In addition to the existing answers here, the info in this answer may help you in deciding when to call "super".
The methods that are called when the child object/view/viewcontroller is created/initialized, the first thing you do is call the super (i.e., if you want to call it).
When the child object/view/viewcontroller is destroyed/removed, you call the super at the end of the method.
eg:
// methods called when the you are loading/showing with the view controller
-(void)viewDidLoad {
[super viewDidLoad];
//call super and then all your code goes here
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//call super and then all your code goes here
}
// methods called when the you are done with the view controller
- (void)viewWillDisappear:(BOOL)animated{
//all your code goes here and then call super
[super viewWillDisappear:animated];
}
- (void)viewDidUnload {
//all your code goes here and then call super
[super viewDidUnload];
}
Similarly, for -dealloc, the super is called at the end, for -init super is called at the beginning.
Hope this demystifies Super.
super stands for the super object / aka the father of the class you are impementing which extends
in your case is a PullRefreshTableViewController
this kind of behavior is the base of all the object oriented languages, like obj-c.
in the case you are analyzing the super class has a method called
-(UITableviewStyle *) initWithStyle:style
super specifies that the messages should be sent to the parent class (superclass) of the current class. Since you're inheriting from PullRefreshTableViewController, that's where they'll get sent.
super is the parent class, here PullRefreshTableViewController. Calling [super dealloc], which you should always do if you need to implement your own dealloc method, allows your parent class (and its parent class, and so on) to clean up after themselves. It's usually a good idea to perform your own releases, and only then let the parent class do the same, because you can never be sure what the parent class will sweep away from under your feet.