1

I have an application under development and I am struggling to understand why Xcode appears to be telling me it is holding on to (what seems) almost everything created, causing some memory pressure every so often..

So far I am presenting all of my views, information, etc. fairly simply. I have 2 ViewController's, one that appears upon launch, and the other once a login has been deemed successful.

The rest of the information that is presented is handled with various subclasses that I have created (most of which are subclasses of UIView, UILabel, or UIButton).

One class that I use very frequently is called MenuView, which is quite literally a class that creates...a menu.

- (MenuView *)createMenuWithFrame:<FRAME> size:<SIZE> type:<TYPE> view:<VIEW>{
    MenuView *menu = [MenuView alloc] initWithFrame:<FRAME>];
    /// Code for creating a UIView with a background image for a menu.
    ...
    /// Done. Now the menu gets added to the passed view <VIEW>. 
    return menu;
}

My issue seems to be persistent throughout each view that I present, as practically right now each view that is added to the hierarchy (shown) is a MenuView, or uses a menu.

EXAMPLE

I am presenting views quite simply, with calls to initialize a subclasses.

Let's take my settings view subclass SettingsView for an example.

HomeViewController.m, like stated above, view controller #2.

#pragma mark Settings
- (IBAction)openSettings:(id)sender {
    [SettingsView createSettingsViewForView:self.view];
}

SettingsView.m. NOTE: In my MenuView.m class I add the created menu to whichever UIView I pass to it.

#import "SettingsView.h"

UIView *screenView;

@implementation SettingsView  // a UIView subclass type in interface (.h)

#pragma mark - Create View
+ (void)createSettingsViewForView:(UIView *)view {
    // Here I create a MenuView. This method is just a wrapper of 
    // the menu class. It doesn't create a SettingsView, but rather 
    // creates another MenuView *menu.

    // The menu is given some content, then "shown" (alpha 0 -> 1) 
    // through the MenuView class by using [menu animateMenu:menu];
}

I understand that using images frequently will be "expensive" in terms of memory allocation, but I am quite certain it should still be released (removed from used memory) if handled correctly. In my case, I thought I was handling it correctly, and still think so..

Upon closing ANY MenuView item, I animate the menu out of view (alpha), then upon completing the animation, I remove the menu from the superview.

- (void)closeMenuView:(UIButton *)closeButton {
    MenuView *menu = (MenuView *)closeButton.superview;
    [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction animations:^{
        <CUSTOM TRANSITION OUT OF VIEW>
    } completion:^(BOOL finished) {
        [menu removeFromSuperview];
    }];
}

PROBLEM

When I present the settings view from the HomeViewController.m, my memory (which is already higher than I would like) spikes ~10 MB. I understand that optimizing the image sizes depending on the screen size / res will greatly reduce some of the memory usage, but that doesn't matter with the issue I am having.

Once the ~10 MB is added, from let's just say 88 MB to 98 MB, closing the menu has no effect whatsoever in the eyes of Xcode.

Using Instruments, I find opening the menu, then closing the menu WILL in fact lower Persistent Memory used, which is also what it should be (~11 MB + 1.5 MB when settings opened.

The other disturbing issue which makes me doubt my methods of "removing" the views, is that when I present and remove a larger (more complex) menu, one is called MailView, the memory will climb each time the view is "opened", but never releases any memory, so it continues to grow. Instruments tells me there is no memory leak, but clearly there is an issue of retaining this memory.

Here is a screenshot of a recording through instruments. This is traced from `Details -> Statistics -> Allocation Summary (Search for "Mail") -> MailView.

enter image description here

It seems for whatever reason my app continues to create MailView's without destroying them.


QUESTION

My question is (thanks for staying with me), is this in a bug with Xcode and/or Instruments in the reading of my memory usage? Or is this an internal issue with application. I know there is probably more I need to provide to get the best answer, but from what I have shown, and explained, each view is simply a UIView that gets added to the view hierarchy, then it is removed (subsequently "killing" any subviews chained in view underneath).

There are many posts here on SO about issues similar to this, but many suggest what I am doing (removeFromSuperview, = nil (which I tried), use Instruments, manually dealloc, though I am using ARC).

If anyone as an idea of something I am missing, or simply am unaware of, I would greatly appreciate you letting me know. Thanks

FYI

I understand that the iOS devices seem to basically hold on to everything it can, unless it A) thinks you don't need it anymore (most likely from timing out) or B) there is memory pressure, so it gets rid of the "least important" items. But again, this seems to not be the case, as I receive memory warnings on the occasion from these issues I am having.

UPDATE:

(I have just updated to 8.2.1 today so I was able to see the backtrace (with Malloc enabled in the scheme) now through the DMG in Xcode rather than having to go through Instruments)

Here is a snip of the structure when selecting one of the MailView items that it is holding. This is after I open and close Mail 2 times, hence the 2 views it is keeping. This is what I need to fix.

enter image description here

Here is a snip of the graph of the memory of another run having opened and closed the mail view many times.

enter image description here

Will Von Ullrich
  • 2,129
  • 2
  • 15
  • 42
  • have you tried putting the removeFromSuperview to the main queue? I am assuming that its being removed from a background thread? probably try dispatch_async(dispatch_get_main_queue(),^{ //removeFromSuperView}); – Joshua Dec 22 '16 at 07:50
  • ill give that another try lol – Will Von Ullrich Dec 22 '16 at 07:53
  • What happens is through Xcode's debug memory reading, the initial memory jump is about 7 MB, when "closed" goes down around 2.2, then each open an close it nets at around +0.3 MB, gradually increasing and growing the used memory. No clue what I'm missing here... – Will Von Ullrich Dec 22 '16 at 07:57
  • well it seems that there are other things that holds your object – Joshua Dec 22 '16 at 08:02
  • It's just a subclass of UIView with some (readwrite) properties, everything gets dumped into the view, which is added then removed from the subviews of my VC. I don't understand where the ties would be – Will Von Ullrich Dec 22 '16 at 08:10
  • 1
    Use the new "debug memory graph" tool (http://stackoverflow.com/questions/30992338/how-to-debug-memory-leaks-when-leaks-instrument-does-not-show-them/30993476#30993476) and see if you can see any of your classes that should have been deallocated that are still listed as being live. If so, click on that object on the left panel and it will show you precisely what is keeping the strong reference and, if needed, you can use "malloc stack" option and see precisely where that strong reference was established. There's no guessing required anymore. – Rob Dec 22 '16 at 17:05
  • "I understand that the iOS devices seem to basically hold on to everything it can" ... In a few cases (e.g. caching of images via `UIImage(named:)`), yes, but generally, no. You should discard what you don't need and only cache those items that (a) you will need again later; and (b) are appreciably expensive to recreate. The general pattern is that if you repeat some task a few times, you might see a little growth in the stasis memory level the first time you do it, but after that, it should be falling back to some consistent level. If it's continuing to grow, you have a problem. – Rob Dec 22 '16 at 17:19
  • 1
    This is the theoretical memory graph (http://stackoverflow.com/a/38532853/1271826) and you want to make sure you eliminate the "wasted" portion of that graph. – Rob Dec 22 '16 at 17:24
  • That is quite literally what I am experiencing. Unsettling as I am relying heavily on the built in practices of just adding and removing items - clearly I need to move deeper into handling the instances properly and discarding them completely. Just not sure how / where I need to begin – Will Von Ullrich Dec 22 '16 at 18:27
  • So, when you used the "debug memory graph" option and selected one of your `MailView`, instances, it should show you precisely what is maintaining the strong reference to it. Why don't you edit your question and show us what it showed you... – Rob Dec 22 '16 at 18:41
  • @Rob my answer below - hopefully it makes sense to you, as you, Joshua and myself had both understood that clearly there was something holding on to these objects. Just had trouble finding where. Makes sense now as well with the DMG showing traces to NSArray – Will Von Ullrich Dec 27 '16 at 15:03

1 Answers1

1

Well.. it seems that despite my best efforts of destroying any of the custom "views", that would most certainly be quite ineffective in destroying objects that are retaining these "views" that continue to be cloned and duplicated across the applications VM while running.

In particular, the culprit was no other than NSArray. For each view that was being created, a simple array, CUCells *cells of the views subviews (cells) was generated and stored. I had initially designed the subclass to be a strong reference to it's parent, but needed to change it to become independently created for each time it was "opened". Having made this change, cells was being initialized and allocated each time the view was opened, failing to be destroyed along with [cell removeFromSuperview].

  • A simple override of this method along with cells = nil and so forth with the other "strong" refs allowed for the application to correctly destroy all ties to the previously destroyed view.
Will Von Ullrich
  • 2,129
  • 2
  • 15
  • 42