1

hoping someone might be able to shed some light on the issue.

I am retrieving an array of custom objects using NSCoding + NSUserDefaults, and every time I reload the view, the memory continues to grow.

enter image description here enter image description here

Here is the code I use to retrieve the array of custom objects (which is called under ViewDidLoad:

NSData *dataRepresentingSavedArray;

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
dataRepresentingSavedArray = nil;
dataRepresentingSavedArray = [currentDefaults objectForKey:@"women"];
if (dataRepresentingSavedArray != nil)
{
    NSArray *oldSavedArray = nil;
    oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
    if (oldSavedArray != nil)
        women = [[NSMutableArray alloc] initWithArray:oldSavedArray];
    else
        women = [[NSMutableArray alloc] init];
}

The woman.backgroundimage is what causes the most memory loss, however when I removed that aspect from the custom object and NSCoder completely it still climbed in memory without release, but slower. The ViewController is part of a navigation controller (SWRevealViewController Pod) and when it is selected the memory increases until app crash. Thank you for your time!

EDIT: I figured I should also share how the data is archived. First the image is compressed:

                 CGFloat maxCompressionFactor = 0.1f;
                 CGFloat compressionFactor = 0.9f;
                 int maxImageSize = 60 * 1024;

                 NSData *imageData = UIImageJPEGRepresentation(_theimage, compressionFactor);

                 while ([imageData length] > maxImageSize && compressionFactor > maxCompressionFactor)
                 {
                     compressionFactor = 0;
                     imageData = UIImageJPEGRepresentation(_theimage, compressionFactor);
                 }

                 _theimage = [UIImage imageWithData:imageData];
                 _theimage = [self scaleImage:_theimage toSize:CGSizeMake(320.0,480.0)];

Then all values are stored:

 //store to woman object
                 Woman* woman = [[Woman alloc] initWithFull:nameOfGirl withdate2:date withintervalLength:string  withperiodLength:[NSString stringWithFormat:@"432000"] withpmsLength:[NSString stringWithFormat:@"432000"]initWithbackground:_theimage];

                 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

                 [women addObject:woman];

                 [userDefaults synchronize];
McCadi
  • 229
  • 1
  • 2
  • 9
  • Where is the women mutable array declared? – stevesliva Jul 19 '17 at 01:17
  • Thanks for your comment, I declare it in the .h as `@property (nonatomic) NSMutableArray *women;` – McCadi Jul 19 '17 at 18:18
  • 1
    I don't like that you replace a VC property (`women`) with a completely new array. You're changing the memory location that `women` points to. Why not use `[women setArray:oldSavedArray]`? Then, if something in the view or view controller is retaining `women`, you'd at least not have the previous `women` object around. Unfortunately, that wouldn't solve anything that might be retaining an object within the array. I would try hard to avoid replacing the data model object that backs a view object without deleting the old view object too. I think the view or controller is retaining something – stevesliva Jul 19 '17 at 19:05
  • that actually reduced the growth down to about .5-1 MB per load. Thank you. When the app returns active and I reload, I still loose about 20Mb. It doesn't release still. By your last thought did you mean I should make sure that (for example) the string object "woman.girlname" should not be assigned to an NSString which is recreated on every load? and should the NSString it be a VC property? – McCadi Jul 19 '17 at 23:42
  • 1
    The real problem here is that there's a view or controller object that's retaining something old that you want to replace with fresh data. But your `alloc`s are exacerbating that issue, because each `alloc` creates another object that can become a zombie. On the other hand, if you basically use the API equivalent of assignment to the dereferenced property, you still have something retaining that object, but at least you haven't created a new one. – stevesliva Jul 20 '17 at 14:57
  • 1
    I'd start trying to nil everything out when the view becomes inactive, and wrap the nilling in an `autoreleasepool` enclosure. I did this sort of work on OSX 3 years ago, but you can [at least get this gist of it here](https://stackoverflow.com/questions/23944436/should-i-need-to-unbind-cocoa-bindings-in-dealloc-of-windowcontroller). – stevesliva Jul 20 '17 at 14:58
  • setting the storyboard outlet backgroundimage.image to nil and wrapping in autorelease like you suggested has solved the memory issue. Thank you! I would like to mark it as an answer – McCadi Jul 20 '17 at 20:47

1 Answers1

1

This answer is just a synopsis of the comments.

The key to understanding what was going on was that some object-- likely the view controller itself, but potentially a custom view object, whatever-- was retaining the objects that you were creating from NSUserDefaults.

We reduced the number of allocs, and the amount of memory growth reduced, which seemed to confirm that each time ViewDidLoad is hit, an object was created but never deallocated because its retain count never hit zero. Some other object which stayed in memory was keeping a strong reference to it.

At that point, I simply suggested aggressive nil assignment in the right view lifecycle function (viewDidDisappear?), wrapped in an @autoreleasepool enclosure for good measure. That turns out to solve the problem entirely.

Assigning nil to the property within the VC ensures that a strong property's data does not stay in memory until the VC itself is deallocated. Using @autoreleasepool tells ARC to deallocate zero-reference-count objects that became zero-reference-count within the enclosure at the termination of the enclosure. I'm thinking of the "pool" as a ledger of reference count transactions within the enclosure. The enclosure keeps its own ARC books and clears its ledger when it's done.

stevesliva
  • 5,351
  • 1
  • 16
  • 39