I have a static view that is used to display as a loading indicator in the UI.
static UIView *loadingView;
in my class Loading.m I add loadingView into my container when show() is called
+ (void)show {
if (loadingView == nil) {
UIWindow *containerView = UIApplication.sharedApplication.keyWindow;
loadingView = [UIView alloc] init];
[containerView addSubview:loadingView];
}
}
and when dismiss() is called I remove it from superview:
+ (void)dismiss {
if (loadingView != nil) {
[loadingView removeFromSuperview];
loadingView = nil;
}
}
I found loadingView will always be nil after being added into containerView, so I will keep adding more loadingView into my containerView and it will not be removed when dismiss() is called. When I print UIApplication.sharedApplication.keyWindow it shows an UIView has been added into the stack. It seems like loadingView has lost its reference into containerView after show() is completed.
What gives?
Indeed, this question can have deeper consequences:
You use static UIView. If your application is under ARC, system automatically decides if objects can live when they are not visible. So, you should think twice before using static declaration for visible objects.
If your object does respond to messages like removeFromSuperview and .superview is not nil - it means that the object is not nil for sure. As said above, it is bug of debugger (happens when running the app on device under XCode).
The goal of this post - to pay attention to the UIView objects ierarchy, where parent object has .subviews non-null NSArray property, indicating all objects, added as subviews. They will be autoreleased by ARC next to removed from the stack VC. So, in case of static UIView * declaration, ARC will keep loaded all its parent elements until it will be manually removed from superview. In other words, static UIView * is potentially dangerous construction and can lead to memory leaks or other conflicts. Better way to control loading indicator, for instance - is to check last subview element of current top VC:
if (self.view.subviews && [self.view.subviews.lastObject isKindOfClass: [loadview class]]) {
[self.view.subviews.lastObject removeFromSuperview];
}
In this case you have no risk to of calling nil object methods (app crashes as well) and headache of manual lifecycle control.
How you detected that's "loadingView" is nil?
Please verify it via code or using "po" in Debugger.
If you just saw "nil" value in variables list, sometimes it's Xcode debugger bug.
When variable "nil" in variable list, but it's not "nil".
Related
For example:
// CustomViewClass
- (void)showOnView: (UIView*)view {
[view addSubview: self.customView];
}
Then invoke this method at another class, like a view controller's viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
[self.customViewClass showOnView: self.view];
}
I pass a view controller's view to CustomViewClass as the above.
My question is: Would it cause some kind of retain cycle?
Are these views passed through parameter referenced weakly?
Or it's fine to that.
It would be highly appreciated if anyone explain it in both Swift and Objective-C.
What is Retain Cycle? - It's the condition when 2 objects keep a reference to each other and are retained, it creates a retain cycle since both objects try to retain each other, making it impossible to release.
In this case, self.customViewClass keep a reference to self.view but self.view doesn't keep any reference to self.customViewClass. Which keeps reference to self.customViewClass is self, not self.view.
So of course, it won't causes retain cycle
Don't believe in me ? 🤣 - Check it yourself by trying to log something inside dealloc method.
After you dismiss CustomViewController, if the code inside dealloc is called and log something, it means no retain cycle here. If not, it causes retain cycle.
For example
- (void)dealloc {
NSLog(#"BOOM RIGHT ANSWER!!!");
}
Memory leaks happens when two class having objects pointing to each other .For e.g.
class A{
var object_b = B()
}
class B{
var object_a = A()
}
Now consider your case :
// CustomViewClass
- (void)showOnView: (UIView*)view {
[view addSubview: self.customView];
}
Your" view" object is local variable .CustomViewClass doest not reference to superview "view".
Now when customview is added to superview:
- (void)viewDidLoad {
[super viewDidLoad];
[self.customViewClass showOnView: self.view];
}
When showOnView function of CustomViewClass called the superview simply adds subview CustomViewClass view.
For finding memory leaks always add deinit function in views and viewController class so that you can sure about class is deallocated or not.
deinit {
print("deinit called " + "Class name")
}
This won't lead to a retain cycle. It is a one way reference of object where parent view has it's reference in a secondary(custom) view. However there is no reference of that secondary view in the parent view. So unless that situation arises, you are quite safe as far as retain cycle is concerned and there is no need of a weak reference of the parent view.
At certain points in my application, I am adding a UIView to the keyWindow, and at other points I need to retrieve it in order to manipulate it.
I add the view like such:
self.minimizedView = [[UIViewCustomSubclass alloc] initWithFrame:viewFrame];
[[UIApplication sharedApplication].keyWindow addSubview:self.minimizedView];
Later, in another class, I am attempting to iterate through the subviews of the keyWindow, like this:
for (id subview in [UIApplication sharedApplication].keyWindow.subviews) {
NSLog(#"VIEW: %# CLASS %#", [subview description], [subview class]);
}
I am using id here because I want to be typesafe in accessing only the right subview, because I need to move it's frame and I want to be sure I'm only moving the correct one... further to that point, I've subclassed UIView and created the view that I am adding to the keyWindow to be of that type...
Inside the above for loop, I am attempting to ask for isMemberOfClass:
if ([subview isMemberOfClass:[UIViewCustomSubclass class]]) {
NSLog(#"The Subview %# of Class %# Should Be The One We Are Looking For", [subview description], [subview class]);
// assign subview to iVar etc
}
But this never executes - the subview always logs it's class type to be of UIView (the superclass) but never of the subclass... I've set a breakpoint and inspected the View properties right at the point where I add it to the keyWindow, and it seems to be of the correct subclass type there...
Can anyone spot something that I'm doing wrong?
-- EDIT --
OOPS. I just found one edge case / place in my code where I am allocating the view still as a UIView and not as the subclass - this fixed the problem.
I just witnessed a very strange issue where my view would ignore all of the delegate calls coming from a custom view because I called alloc/init on the item at the load. I'm curious as to why.
#synthesize customTextField;
-(void)viewDidLoad {
// by calling this alloc/init, none of the changes here actually update to the view
// everything is ignored from here on in.
// if I comment out the alloc/init line, everything works fine
self.customTextField = [[UITextField alloc] init];
self.customTextField.text = #"Some text";
// setting font and size as well
}
While I would still get calls to the text field delegate methods, none were linked to my specific text fields. I could not respond to just customTextField.
I do realize that calling alloc/init will give me a completely new instance of customTextField... but why wouldn't that new instance be linked to IB and my view?
Because IB linking != binding.
When you link a variable in IB, it's a simply sets the variable once on first load, that's it. It does no other special code to track any changes to it, for good reason.
For example:
You are designing a UITableViewCell, and if you have a cell that is selected, you must rearrange all of the content inside the cell. In this case, you determined it would be easier if you just recreated all of the subviews and re-added them into the view, so you do the following:
-(void) layoutSubviews {
if (cellIsSelected)
{
// custom button is an IBOutlet property, which is by default a subview of self
self.customButton = [UIButton buttonWithType:UIButtonTypeCustom];
[[self someSubView] addSubview:customButton];
}
else {
// where is customButton located now? is it a subview of self or `someSubView`?
self.customButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// [self addSubview:customButton];
}
}
Thus, it is much easier for IB to say let's set this once, and let the programmer figure the rest out than for IB to try and track all changes made to an object and report them the to the UI.
viewDidLoad is called after your nib is loaded, and creating a new UITextField instance at this point will not be associated with your nib. If you're setting up new instances manually you also need to manually setup the delegates, and add them as subviews of your view.
The XIB file has no way of knowing that you are changing the reference. Consider the following piece of code
NSObject *myObjA = [[NSObject alloc]init]; //create object
NSObject *myObjB = myObjA; //assign reference <- this is the your case after xib load
myObjB = [[NSObject alloc]init]; //create object, myObjA still lives on.
It's basically the same that happens when you load your XIB file; You get the reference to the instantiated object (equals myObjB in above example). You can do with the reference what ever you please but you do not change the interface instance just by creating a new object.
I have a simple question regarding ARC. I show a UIView if a user taps a button using addSuperView within a UIViewController. The UIView contains a close button, if tapped I want to remove the view.
I used to call a method within the UIViewController after animating the view offscreen:
- (void)viewDidClose:(UIView *)view
{
[view removeFromSuperview];
[view release], view = nil;
}
Now using ARC I changed it to:
- (void)viewDidClose:(UIView *)view
{
[view removeFromSuperview];
view = nil;
}
The question now is: I want to remove the protocol and the delegation to the view controller and do this within the UIView itself.
Pre-ARC (within view):
- (void)didStop
{
[self removeFromSuperview];
[self autorelease];
}
I can't use 'autorelease' in ARC nor set 'self = nil', as far as I know ARC comes in place as soon as I set the view to nil or replace it, but what if I don't replace it? Is [view removeFromSuperview] enough to take care of everything or does this leak?
Thanks a lot! I appreciate any help!
(Note that in your non-ARC version, you'd want the autorelease before the removeFromSuperview).
I've wondered about similar things. The 'danger' is that when you do the removeFromSuperview without first doing an autorelease, the object is immediately deallocated, even before the end of the method. That's skeezy.
My idea was that you'd make the method return self. In the non-ARC case, you'd create this pointer with autorelease but ARC should do that for you. You may not need the pointer in the caller, but I believe this will force ARC to do what you want.
So I'm having problems releasing some view controllers.
In essence the dealloc for the PhotoPostViewController never seems to get called, so I can't clear down the images contained within that are munching all the memory.
This is my UIViewController subclass, I can have up to 100 of these at any one time added as subviews to the main scroll view, the iPad gets tight for memory after that.
#interface PhotoPostViewController : UIViewController {
IBOutlet UIImageView *backgroundImage;
IBOutlet UIImageView *serviceImage;
}
Then in my main view class I have a method to create these views and add them to a scrollView. This method is typically called from a loop to create all the subviews I need.
- (void) addPost {
PhotoPostViewController *postView = [[PhotoPostViewController alloc] initWithNibName:#"PhotoPostViewController" bundle:nil];
[scrollView addSubview:[postView view]];
[viewControllers addObject:postView];
}
viewControllers is an NSMutableArray created in the main class init.
scrollView is a UIScrollView on my main view.
This all works fine, I know the limit of the memory usage on the iPad and keep within that at any given time, opening Popovers to give preview images and videos etc...
Doesn't run out of memory until I try to refresh the screen.
The code to do this is:
- (IBAction)didPressRefresh:(id)sender {
for(UIView *subview in [scrollView subviews]) {
[subview removeFromSuperview];
}
for(UIViewController *c in viewControllers) {
[c release];
}
[viewControllers removeAllObjects];
}
For the sake of simplicity I clear off all the subviews and try to release them before recreating the next set of subviews using the function above.
It removes them from the view, but runs out of memory adding the new set of view controllers. In my test cases the sets of view controllers are identical in content, so if it loads from clean first time, then it should load the second time and every other time after that if I release everything properly.
What actually happens is it crashes due to low memory when creating the second set of view controllers.
While debugging I've put breakpoints on the 'viewDidUnload' and 'dealloc' methods, but they never get hit.
It looks like the UIViewController itself is getting released, yet the UIImageViews within are not, clearly they'd usually get released by my code in the dealloc (or viewDidUnload) method.
So I'm confused.
Counting things it looks to me like the reference counts are fine. so how come the dealloc is not getting hit ?
Andi
You need to send the postView object the -release message after adding it to the viewControllers collection:
- (void) addPost {
PhotoPostViewController *postView = [[PhotoPostViewController alloc] initWithNibName:#"PhotoPostViewController" bundle:nil];
[scrollView addSubview:[postView view]];
[viewControllers addObject:postView];
[postView release];
}
The reason why you need to do this is because the collection sends the -retain message to all objects that are added to it, hence the memory leak and -dealloc not being hit.
EDIT:
Your -didPressRefresh: method should look like this:
- (IBAction)didPressRefresh:(id)sender {
[[scrollView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
[viewControllers removeAllObjects];
}