Forcing an object to deallocate under ARC - objective-c

I'm working on an iPad photo collage app that draws perhaps hundreds of UIImageViews on the screen at once.
There is a button that lets the user "re-create", which is suppose to run a for loop to [photo removeFromSuperview] on all photos and then initialize a new batch, in that order.
I'm using ARC, and my console tells me that my Photo's dealloc method isn't being called until AFTER the next batch has been drawn, meaning I'm running into memory issues, even though I'm trying to remove the first set before adding the next set.
Is there a way to either 1) wait until all the photos have been properly dealloc'd or 2) force all the photos to dealloc immediately under ARC?

You are probably putting your image views in an autorelease pool without realizing it. You may be able to fix this by wrapping your own autorelease pool around your for-loop.
For example, I made a very simple test project with one image view and one button under my top-level view. When I tap the button, it removes the image view and creates a new one. It removes the image view by looping over the top-level view's subviews. Here's the code:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initImageView];
}
- (IBAction)redoWasTapped:(id)sender {
[self destroyImageView];
[self initImageView];
}
- (void)destroyImageView {
for (UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
[subview removeFromSuperview];
}
}
}
- (void)initImageView {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"picture.jpg"]];
imageView.frame = CGRectInset(self.view.bounds, 100, 100);
[self.view addSubview:imageView];
}
#end
When I ran this under the Allocations instrument with “Record reference counts” enabled, I saw that each removed image view was not deallocated during destroyImageView. Instead, it was deallocated later when the run loop called -[NSAutoreleasePool release].
Then I changed destroyImageView to manage its own autorelease pool:
- (void)destroyImageView {
#autoreleasepool {
for (UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
[subview removeFromSuperview];
}
}
}
}
When I ran it again under Instruments, I saw that each removed image view was deallocated during destroyImageView, at the end of the #autoreleasepool block.

ARC deallocs any object to which there are no more strong references. So to dealloc something, simply set all the variables pointing to it to nil and make sure the object is not involved in any circular reference.

Related

IBOutlet always nil

I've already looked at all the other entries for this question. I have been stuck for a while on a problem I have been unable to solve, any suggestion for an avenue of approach would be sincerely appreciated.
I have a moderately large app that allocs a window controller then loads a window using an XIB containing a single window which contains an AVPlayerView that was placed in the window. The AVPlayerView is wired to an IBOutlet called playerView. The window loads when it is supposed to and then an AVPlayer is created with a URL and is to be assigned to the player of the AVPlayerView, however I always find that the IBOutlet playerView is always nil at the point of trying to make the assignment. I cannot figure out why the IBOutlet is nil. Another anomaly is that the AVPlayerWindow will not become key when so ordered even though there are no other windows on the screen. I know for a fact that I have the outlet wired up in the XIB. Any thoughts?
There are 11,000 lines of code in the app. Here is the tiny portion that attempts to attach the player to the AVPlayerView.
fileURL = [self prepareStringAndBecomeAURL:filePath isDirectory:NO needFileURL:NO];
// thePlayer = [self prepareToPlay:fileURL];
thePlayer = [AVPlayer playerWithURL:fileURL];
if (thePlayer == nil) {
[self popUpErrorMessageWithoutCode:#"Unable to create a video player."];
} else {
playerView.player = thePlayer;
if (thePlayer != nil) {
[theMainWindow orderOut:self];
[self showMoviesWindow:self];
[self performSelector:#selector(continueHandleMovieSelected) withObject:nil afterDelay:1]; // wait for window to appear
}
}
}
- (void)continueHandleMovieSelected
{
[playerView.player play];
}
No, no exceptions.
Window controller and nib are created in another module, an AppController:
- (IBAction)showMoviePlayerWindow:(id)sender
{
if (![self myMoviePlayerWindowController]) {
myMoviePlayerWindowController = [[MoviePlayerWindowController alloc] initWithWindowNibName:#"MoviePlayer"];
[self set_myMoviePlayerWindowController:myMoviePlayerWindowController];
}
[[[self myMoviePlayerWindowController] window] center];
[[[self myMoviePlayerWindowController] window] makeKeyAndOrderFront:self];
[[self myMoviePlayerWindowController] showWindow:self];
}

Xcode os x : Why drawRect could not access instance data

I am trying to draw something in my custom view, but not sure why drawRect could not access its instance data. Here is the steps I tried.
Create a Mac OS X app, with using storyboard checked.
In the storyboard, delete the view, then add a new custom view under the view at the same place. (I tried if the view is not deleted, same).
Assign EEGView class to the newly added custom view.
then run. From the log information, you will notice that the drawRect could not access the instance data although the instance variables get initialized and updated.
In viewCtroller.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
myView = [[EEGView alloc] init];
//[self.view addSubview:myView];
//Start Timer in 3 seconds to show the result.
NSTimer* _timerAppStart = [NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(UpdateEEGData)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timerAppStart forMode:NSDefaultRunLoopMode];
}
- (void)UpdateEEGData
{
// NSLog(#"UpdateEEGData.....1");
// myView.aaa = 200;
// myView.nnn = [NSNumber numberWithInteger:myView.aaa];
// make sure this runs on the main thread
if (![NSThread isMainThread]) {
// NSLog(#"NOT in Main thread!");
[self performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:TRUE];
}else
{
[self.view setNeedsDisplay:YES];
}
NSLog(#"UpdateEEGData.....2");
[myView setAaa:400];
myView.nnn = [NSNumber numberWithInteger:myView.aaa];
// make sure this runs on the main thread
if (![NSThread isMainThread]) {
// NSLog(#"NOT in Main thread!");
[self performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:TRUE];
}else
{
[self.view setNeedsDisplay:YES];
}
}
-(void)updateDisplay
{
[self.view setNeedsDisplay:YES];
}
In my custom view class EEGView.m
#implementation EEGView
#synthesize aaa;
#synthesize nnn;
-(id)init{
self = [super init];
if (self) {
aaa = 10;
nnn = [NSNumber numberWithInteger:aaa];
NSLog(#"init aaa: %i", aaa);
NSLog(#"init nnn: %i", [nnn intValue]);
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"drawRect is here");
NSLog(#"drawRect aaa: %i", aaa);
NSLog(#"drawRect nnn: %i", [nnn intValue]);
}
#end
Did I miss anything? Tested in Xcode 7.2 & 7.2. But if I leave the 'using storyboard' unchecked, it works.
Or is it a Xcode bug?
Any advice appreciated.
Thanks in advance.
If you've added EEGView view on storyboard, you shouldn't be also instantiating one in viewDidLoad. You've alloc/init'ed a new one, but it bears no relationship to the one that the storyboard created for you. So, the one created by the storyboard has drawRect called, but you're setting the properties in the separate instance that you created in viewDidLoad which was never added to the view hierarchy (and thus never will have its drawRect called).
When the storyboard instantiates the view controller's view, it will instantiate your EEGView for you. All you need to do is to hook up an IBOutlet for this view in order to get a reference to it from your view controller. (For example, you can control drag from the EEGView in the storyboard scene to the #interface for the view controller that you've pulled up in the assistant editor.)

objective c - resident and dirty memory not getting released after dismiss of collection view controller

I am loading a 2X2 collection view which has multiple cells and each cell has imageview.But when I am dismissing this controller the resident memory and the dirty memory keeps on increasing.
I have multiple views in my controller and collection view is one of them and I do addsubview and removefromsuperview to show different views in the controller. Before I dismiss the controller I remove all the subviews including the collectionview from the controller's subviews.
But this does not release the resident memory and after it exceeds 500MB the app crashes after throwing a memory warning.
Here is my code to remove the subviews before dismissviewcontroller -
-(void)removeSubViewsOfView:(UIView *)view{
NSArray *viewsToRemove = [view subviews];
for (int i = 0; i<[viewsToRemove count]; i++) {
UIView *v = [viewsToRemove objectAtIndex:i];
[v removeFromSuperview];
v=nil;
}
}
Also I am using ARC.
Some suggestions:
Make sure you set all your IBOutlets to nil on your views -dealloc method
If you're using view controller containment, call -removeFromParentViewController:
- (void)removeSubViewsOfView:(UIView *)view{
NSArray *viewsToRemove = [view subviews];
for (int i = 0; i<[viewsToRemove count]; i++) {
UIView *v = [viewsToRemove objectAtIndex:i];
[v removeFromSuperview];
NSViewController* vController = nil;//retrieve your view controller
[vController removeFromParentViewController];
v=nil;
}
}
It won't generate dirty memory, but it may leak objects if you keep mutual strong references between objects, aka. A retains B and B retains A
Hope it helps :)

Memory keep increasing even though I set it to nil (screenshot enclosed)

In my ParentViewController I type this:
- (void)updateView:(NSInteger)_index article:(AObject *)aObject {
UIView *aUIView = [someUIViews objectAtIndex:_index];
// clear view
for (UIView *view in aUIView.subviews) {
[view removeFromSuperview];
}
// create view
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;
}
In ChildViewController I type this:
.h file
#interface ChildViewController : UIViewController <UIWebViewDelegate> {
AObject *aObject;
UIWebView *webView;
}
.m file
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:(BOOL)animated];
webView.delegate = nil;
webView = nil;
aObject = nil;
}
Remark:
I have to use addChildViewController because I use UIWebViewDelegate in ChildViewController. If I do not use addChildViewController, I cannot retain the ViewController and functions of UIWebViewDelegate cannot be called.
I use ARC
Program Objective:
There is a scrolling view implementing by UIScrollView and everytime I scroll the screen to another page, function updateView will be called.
After I scroll to another page, the previous page will be disappeared so viewWillDisappear in ChildViewController will be called.
Screenshot:
http://postimage.org/image/mzmksm9d5/
I cannot post image directly because of low reputation here.
Based on the figure, ParentViewController is called at about the 4th second. Then the memory keep increasing rapidly without objects released. At about the 30th second, ParentViewController is gone away by clicking the back button on the navigation bar. Memory is dropped a bit but still somethings are remaining.
Goal:
What I wish to do is that the object of ChildViewController can be destroyed when viewWillDisappear is called and it is no longer used. So I expect the memory would always drop after it increased a bit. Otherwise, the memory will increase proportional to scrolling times and crash until memory is fully used.
Thanks in advance for any help you are able to provide.
Not sure if this will solve the problem, but try removing cvc.view explicitly from parent view.
Also, you are creating ChildViewController, but you are not releasing it. By calling init you increased its retain count, but you are not decreasing it anywhere. Like this (if you do not have some more code), your ChildViewController will not be released since retain count will never be 0 again.
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;

Alloc/init in custom UITableViewCell

I'm using DYRateView to display a star rating view in a custom UITableViewCell class. An instance of DYRateView (subclass of UIView) is allocated and initialized in my `layoutSubviews' method.
Is it a bad practice to alloc/init the view in `layoutSubviews'? When the cell is reused, does it alloc/init in every cell or reuse the view?
.h
#property (retain, nonatomic) IBOutlet DYRateView *rateView;
.m
- (void)layoutSubviews
{
[super layoutSubviews];
rateView = [[DYRateView alloc] initWithFrame:CGRectMake(100, 60, 160, 20) fullStar:[UIImage imageNamed:#"StarFullLarge.png"] emptyStar:[UIImage imageNamed:#"StarEmptyLarge.png"]];
NSLog(#"created");
rateView.padding = 10;
rateView.alignment = RateViewAlignmentCenter;
rateView.editable = YES;
rateView.delegate = self;
rateView.rate = _rating;
[self.contentView addSubview:rateView];
}
It is ok to allocate it in layoutSubviews, however you are doing it every time layoutSubviews is called, and it will be called more frequently than just after an allocation. So, the way you have it now will leak memory badly (unless you are using ARC). Furthermore, you will be constantly adding new rateView objects to your contentView on every invocation, with rather undesirable consequences.
Since the subview is owned by the cell view, if the cell view is reused, it too will be reused.
Instead, check if the subview exists or not, and allocate accordingly.
- (void)layoutSubviews
{
[super layoutSubviews];
if ( rateView == nil ) {
rateView = [[DYRateView alloc] initWithFrame:CGRectMake(...) fullStar:[UIImage imageNamed:#"..."] emptyStar:[UIImage imageNamed:#"..."]];
// rest of rateView set up here
[self.contentView addSubview:rateView];
}
}
I believe it will just reuse it. You can test it yourself
NSLog(#"Layout subviews, retain count is %d", rateView.retainCount);
Although, why don't you do this in the init method of the custom cell? And just set the frame in layoutSubviews? It looks cleaner.