I am creating view controllers that hold some information to display(image, buttons, gestures, text, etc) and adding them to a scroll.
productsVCs = [[NSMutableArray alloc] init];
for (int i = 1; i <= [self.products count]; i++) {
productsSingle *single = [[productosSingle alloc] init];
single.view.frame = CGRectMake(x, y, 200, 148);
single.delegate = self;
[single setInfo:[self.products objectAtIndex:i-1]];
[scroll addSubview:single.view];
[productsVCs addObject:single];
}
As you can see, I'm doing three important things with these view controllers I'm allocating, I wrote what I think is needed to have ARC release these objects.
1) Set the delegate. -Do I have to set the delegate to nil?
2) Add the view as a subview. -Remove from superview?
3) Add the controller to an array. -Remove from the array?
My question is, when are these view controllers deallocated? Using ARC. Thanks for the help.
The object productsSinglewill be deallocated when your ViewController (that have the scroll view) will be deallocated.
ARC works at compile-time, and so ARC inserts for you the calls to release method on your objects. When you will compile the project, ARC insert the the calls to release method like this:
[single setInfo:[self.products objectAtIndex:i-1]];
[scroll addSubview:single.view];
[single release]; //ARC inserts this line
If you are using ARC you don't have to worry about releasing your objects since ARC will do it for you. Still you can set up strongly held instance variables in your controllers to be released in your viewDidUnload method.
Now, your view controllers will be deallocated when whatever is holding the reference to them is deallocated, in this case it would be the application itself or another controller that created the controller. If you don't hold a reference to it, it will get deallocated as soon as you exit the code block though.
Here's a little guide on using ARC
Related
Maybe this is NOT a duplicate question, as I have searched and tried many solutions about how to release objects under ARC.
The code is simple:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self recreateView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[self.view addGestureRecognizer:tap];
}
- (void)tapped:(UITapGestureRecognizer *)g
{
[self recreateView];
}
- (void)recreateView
{
#autoreleasepool {
for (UIView *v in self.view.subviews) {
[v removeFromSuperview];
}
MyView *vv = [[MyView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:vv];
}
[self _performHeavyWork];
}
- (void)_performHeavyWork
{
int j = 0;
for (int i = 0 ; i < 100000000; ++i) {
j += random() % 7;
j = j % 18747;
}
}
#end
ViewController simply add a tap gesture recognizer whose action is to remove the old subview before adding a new one. MyView is a subclass of UIView which simply log a message when dealloced.
#implementation MyView
- (void)dealloc
{
NSLog(#"dealloc");
}
#end
The only magic is that the -_performHeavyWork is called every time a new view is created. When you keep on tapping on the screen quickly, the ViewController will be busy creating and discarding views. However, the odd thing is that all the discarded views are not dealloc immediately, but at the time you have stopped tapping for a while.
This is the profile of the process:
As you can see, the memory keep growing if you keep on tapping and so many of MyView instances exist at the same time. And if you comment out [self _performHeavyWork];, everything will be back to normal. So my question is:
Why do this happen?
And how can I solve it?
I think the main problem is that you're performing heavy work on the main thread. If you put the hard stuff on a different thread (or GCD) you'll probably see what you're expecting.
Here's some speculation on what's happening.
iOS responds to changes in the UI exclusively on the main thread. So if you're using the main thread for something else, the taps get queued for later processing.
You tap the screen, the main thread starts processing your heavy work.
You tap the screen some more. iOS can't deal with your request so it queues the event.
Eventually your heavy work completes and returns control to iOS.
iOS takes the queue of events and processes them all in a single run loop, which means the main loops auto release pool is never drained.
But what about the manual auto release pool? Well, all UI related stuff happens on the main loop and on the main thread, so the removeFrmSuperview: won't happen until control returns to the OS. Until that happens, the view hierarchy still holds a reference to your views, hence the memory growth.
You should never rely on dealloc being called when you think the last reference is gone. It is quite possible that references are still there where you don't expect them, but most importantly, dealloc can be called by ARC on a background thread.
First, iOS keeps a reference to a view when you add it to the visible window's view hierarchy. So, when you create a new instance your UIView subclass in the block, it remains in memory beyond the autorelease block. Second, the call to removeFromSuperview: does not actually result in the view being released by ARC until the main thread completes, which means there is still a reference to the view after the autorelease block ends. The work you're performing delays the main thread. This delays removing the final reference to the view.
Also, the autorelease block will not help in the case of removing the view because the view in question was not allocated in the same instance of the autorelease block. IOW, the view being created and added to the view hierarchy is not in the same scope when being removed later in the same block. So, there is no benefit to having the remove call in the autorelease block.
What am I doing is I am creating lots of UIView in the background and keep them in a NSMutableArray to use later. But when I dismiss the view controller I check the memory in Xcode and it seems some of memory not being released. I checked; view controller is being deallocated.
Check please:
This happend after several showing and dismissing the view controller. Some of them is being released but not all.
Thanks.
Uncheck Enable Zombie Objects option under Edit Scheme. And try again.
A zombie is an object that has been deallocated, but references to it still exist and messages are still being sent to it
I think this link has more info for you
What is NSZombie?
I suppose you use arc, so it might be useful to explicitly release this in dealloc.
-(void)dealloc {
for(UIView *vw in self.arrayOfViews) {
vw = nil;
}
self.arrayOfViews = nil;
}
Using dealloc is a bit like the old days (pre-arc), but it will help you manage memory better.
!important! --> NEVER call [super dealloc]; when using arc!
In dealloc method release all views that you have in the array.
called the below method in your controller dealloc method
- (void)releaseViewArray
{
// Releasing views in the array
for (UIView *view in _viewArray) {
[view release];
}
// Releasing the array that holding the views
[_viewArray release];
}
In the Google Admob/DFP tutorial [1] they recommend to use the bannerView as instance variable , but I don't them to pollute my viewcontroller class, so I have a Util class, has a method that return an autoreleased DFPBannerView
- (DFPBannerView *) getBannerAds
{
DFPBannerView * bannerView;
bannerView = [[[DFPBannerView alloc] initWithAdSize:kGADAdSizeSmartBannerPortrait] autorelease];
...
return bannerView;
}
And in my all of my view controller classes, if I want to add banner into the view...
DFPBannerView * bannerView = [self getBannerAds];
[self.view addSubview: bannerView];
Questions:
Is my method memory safe, are there any memory leak? (I am not using ARC)
[1] https://developers.google.com/mobile-ads-sdk/docs/dfp/fundamentals
Yes, the code is fine in terms of memory handling in a manual referenced counted environment. (i.e. not ARC)
getBannerAds is returning an autoreleased object, which is correct.
When you add the subview using view addSubview, self.view will retain the bannerView for as long as it needs it and then be responsible for releasing it too.
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];
}