0

I assumed that NSArrayController's addObject and removeObject would work similarly to analogues in NSMutableArray.

However, it looks like addObject increases the refcount on its target object by 3, while NSArrayController:removeObject reduces the target's refcount by only 1, leaving a discrepancy of two, which results in a memory leak in my code.

NSArrayController *ac = [[NSArrayController alloc] init];
NSMutableArray *ma = [[NSMutableArray alloc] initWithCapacity:3];
Custom *obj = [[Custom alloc] init];
NSLog(@"Initial: %lu", [obj retainCount]);

[ma addObject:obj];
NSLog(@"After NSMutableArray:addObject: %lu", [obj retainCount]);
[ma removeObject:obj];
NSLog(@"After NSMutableArray:removeObject: %lu", [obj retainCount]);

[ac addObject:obj];
NSLog(@"After NSArrayController:addObject: %lu", [obj retainCount]);
[ac removeObject:obj];
NSLog(@"After NSArrayController:removeObject: %lu", [obj retainCount]);

[obj release];
NSLog(@"End: %lu", [obj retainCount]);

What I expected to see:

2013-02-10 23:11:22.344 Demo[27208:303] Initial: 1
2013-02-10 23:11:22.345 Demo[27208:303] After NSMutableArray:addObject: 2
2013-02-10 23:11:22.345 Demo[27208:303] After NSMutableArray:removeObject: 1
2013-02-10 23:11:22.345 Demo[27208:303] After NSArrayController:addObject: 2
2013-02-10 23:11:22.345 Demo[27208:303] After NSArrayController:removeObject: 1
2013-02-10 23:11:22.345 Demo[27208:303] End: 0

What I actually see:

2013-02-10 23:11:22.344 Demo[27208:303] Initial: 1
2013-02-10 23:11:22.345 Demo[27208:303] After NSMutableArray:addObject: 2
2013-02-10 23:11:22.345 Demo[27208:303] After NSMutableArray:removeObject: 1
2013-02-10 23:11:22.345 Demo[27208:303] After NSArrayController:addObject: 4
2013-02-10 23:11:22.345 Demo[27208:303] After NSArrayController:removeObject: 3
2013-02-10 23:11:22.345 Demo[27208:303] End: 2

Am I just using NSArrayController incorrectly?


Edit

Here are the results from Instruments. Instruments says that the object ends up in an autoreleasepool that eventually gets drained for the ArrayController. From what I understand, autoreleasepools are drained at the end of the runloop. But my dealloc method never gets called in my demo code, even when I manually add an NSAutoreleasePool and drain it.

NSMutableArray

Code

[ma addObject:obj];
NSLog(@"After NSMutableArray:addObject: %lu", [obj retainCount]);
[ma removeObject:obj];
NSLog(@"After NSMutableArray:removeObject: %lu", [obj retainCount]);

Profile

#   Address Category    Event Type  RefCt   Timestamp   Size    Responsible Library Responsible Caller
0   0x7fc02c80d170  Custom  Malloc  1   00:00.363.486   16  Demo    -[AppDelegate applicationDidFinishLaunching:]
1   0x7fc02c80d170  Custom  Retain  2   00:00.364.189   0   Demo    -[AppDelegate applicationDidFinishLaunching:]
2   0x7fc02c80d170  Custom  Retain  3   00:00.364.502   0   Demo    -[AppDelegate applicationDidFinishLaunching:]
3   0x7fc02c80d170  Custom  Release 2   00:00.364.503   0   Demo    -[AppDelegate applicationDidFinishLaunching:]
4   0x7fc02c80d170  Custom  Release 1   00:00.364.505   0   Demo    -[AppDelegate applicationDidFinishLaunching:]
5   0x7fc02c80d170  Custom  Release 0   00:00.364.852   0   Foundation  -[NSNotificationCenter postNotificationName:object:userInfo:]

NSArrayController

Code

// NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[ac addObject:obj];
NSLog(@"After NSArrayController:addObject: %lu", [obj retainCount]);
[ac removeObject:obj];
NSLog(@"After NSArrayController:removeObject: %lu", [obj retainCount]);

// [pool drain];

Profile

#   Address Category    Event Type  RefCt   Timestamp   Size    Responsible Library Responsible Caller
0   0x7ff1e923b8d0  Custom  Malloc  1   00:00.389.904   16  Demo    -[AppDelegate applicationDidFinishLaunching:]
1   0x7ff1e923b8d0  Custom  Retain  2   00:00.390.534   0   AppKit  -[NSArrayController _insertObject:atArrangedObjectIndex:objectHandler:]
2   0x7ff1e923b8d0  Custom  Retain  3   00:00.390.554   0   AppKit  -[_NSModelObservingTracker _startObservingModelObject:]
3   0x7ff1e923b8d0  Custom  Retain  4   00:00.390.576   0   AppKit  -[NSArrayController selectedObjects]
4   0x7ff1e923b8d0  Custom  Retain  5   00:00.390.948   0   AppKit  -[NSArrayController removeObject:]
5   0x7ff1e923b8d0  Custom  Release 4   00:00.390.977   0   AppKit  -[_NSModelObservingTracker _stopObservingModelObject:]
6   0x7ff1e923b8d0  Custom  Release 3   00:00.390.984   0   AppKit  -[NSArrayController _removeObjectsAtArrangedObjectIndexes:contentIndexes:objectHandler:]
7   0x7ff1e923b8d0  Custom  Release 2   00:00.391.387   0   Foundation  -[NSNotificationCenter postNotificationName:object:userInfo:]
8   0x7ff1e923b8d0  Custom  Release 1   00:00.395.916   0   Foundation  -[NSAutoreleasePool drain]
9   0x7ff1e923b8d0  Custom  Release 0   00:00.395.919   0   Foundation  -[NSAutoreleasePool drain]
Community
  • 1
  • 1
Chris
  • 470
  • 4
  • 11
  • 2
    Instruments will let you see every retain/release made to the object if you are curious where the extra retains come from. – Fruity Geek Feb 11 '13 at 07:28

1 Answers1

1

-retainCount is useless. Use Instruments to see if you have an actual memory leak in your code.

jatoben
  • 3,079
  • 15
  • 12
  • Where is your `-dealloc` method? `obj` appears to be an NSMutableString instance. – jatoben Feb 12 '13 at 05:39
  • My bad, I forgot to update the first part of my question. I have a class called Custom that inherits from NSObject with dealloc defined with a set breakpoint that gets triggered when adding/removing from an NSMutableArray but not an NSArrayController. – Chris Feb 12 '13 at 07:02