10

i have a situation like this:

NSMutableArray * A = [[NSMutableArray alloc]initwithObjects:@"one",nil];
NSMutableArray * B = [[NSMutableArray alloc]initwithObjects:@"two",nil];

[A addObject:B];
[B addObject:A];

now here is a retain cycle, How can i break this retain cycle? (using weak reference)....in above example. Thanks

Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
Matrix
  • 7,477
  • 14
  • 66
  • 97

5 Answers5

12

I would question the reason you need to do this in the first place - but if you do decide it's the best data architecture, I would use NSValue. To store a reference to an object (without retaining it) in a Foundation collection class, you can use +[NSValue valueWithNonretainedObject:]:

NSMutableArray *a = [[NSMutableArray alloc] initwithObjects:@"one", nil];
NSMutableArray *b = [[NSMutableArray alloc] initwithObjects:@"two", nil];

[a addObject:b];
[b addObject:[NSValue valueWithNonretainedObject:a]];

Then to get your object later on from the NSValue instance, you would do this:

NSMutableArray *c = (NSMutableArray *) [[b objectAtIndex:index] nonretainedObjectValue];

Addendum

I've changed the answer from using +[NSValue valueWithPointer:] to +[NSValue valueWithNonretainedObject:], since under ARC, the compiler will complain (due to casting from an object type to const void *). While functionally the same, the typecasting and method name makes more sense (and the compiler will actually let you compile without resorting to hackery).

Nick Forge
  • 21,344
  • 7
  • 55
  • 78
  • I'd suggest using NSValue to create weak reference. It's obviously easy to understand and use, maybe the official implementation of weak reference. Usage of CFArrayCreateMutable function is not bad, however, it brings some inconsistence of usage of NSMutableArray, especially in exposed interface. – Robin May 12 '11 at 03:18
  • I agree 100% - messing around with non-standard `CFArray`s seems like a recipe for memory related bugs down the track if/when you've forgotten that your `NSMutableArray` behaves differently to 99.9% of `NSMutableArray` instances. Debugging with the `NSValue` technique, however, won't be in any way confusing or ambiguous. – Nick Forge May 12 '11 at 03:43
8

The way I've solved this problem in the past is to use a subclass of NSProxy to break the cycle. I'll have an object that stores a weak reference to one of the arrays and passes all messages except memory management ones through to it.

     ┌──── NSArray A <────┐
     │                    │
     │                    │
     v        weak        │
ACWeakProxy ┈ ┈ ┈ ┈ ┈ > NSArray B

@interface ACWeakProxy : NSProxy {
    id _object;
}

@property(assign) id object;

- (id)initWithObject:(id)object;

@end

@implementation ACWeakProxy

@synthesize object = _object;

- (id)initWithObject:(id)object {
    // no init method in superclass
    _object = object;
    return self;
}

- (BOOL)isKindOfClass:(Class)aClass {
    return [super isKindOfClass:aClass] || [_object isKindOfClass:aClass];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation setTarget:_object];
    [invocation invoke];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [_object methodSignatureForSelector:sel];
}

@end

Then your code becomes

NSMutableArray * A = [[NSMutableArray alloc] initwithObjects:@"one", nil];
NSMutableArray * B = [[NSMutableArray alloc] initwithObjects:@"two", nil];

[A addObject:B];
ACWeakProxy * proxy = [[ACWeakProxy alloc] initWithObject:A];
[B addObject:proxy];
[proxy release];

// will print "two"
NSLog(@"%@", [[[B objectAtIndex:1] objectAtIndex:1] objectAtIndex:0]);

It is, however, up to you to make sure your weak reference doesn't vanish while you are still using it.

cobbal
  • 69,903
  • 20
  • 143
  • 156
  • This solution works well with ARC's weak references. If you declare your property (and ivar) as weak the value becomes nil when the reference is lost. `@property(weak) id object;` – David V Jan 27 '12 at 16:29
  • Wow this was a great find, thanks. I was using AVFoundation to record movie and unfortunately it seems `VideoFileOutput startRecordingToOutputFileURL:url recordingDelegate:` does not keep a weak reference and after some video source and sink had a cycle, but this proxy finally fixed the problem and now the object gets deallocated (it is a C++ wrapper for OSX) – dashesy Sep 13 '15 at 19:15
7

You could:

  • manually break the cycle by removing the objects
  • use CFArrayCreateMutable() and set the retain and release callback to NULL:

    CFArrayCallBacks acb = { 0, NULL, NULL, CFCopyDescription, CFEqual };
    NSMutableArray *noRetain = (NSMutableArray *)CFArrayCreateMutable(NULL, 0, &acb);
    

Even better though, take a step back and think about how you can achieve what you want differently. With a proper design this problem might not even occur.

Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
0

In iPhone, there is no concept of weak reference. In your cases, I believe that it will cause the problem of over releasing on either of them.

Usually, for 2 classes, I will do something like this:

Let one class own another by retaining and the other just use assign. Some sample code may clarify it

class A

@interface A {

}

@property (nonatomic, retain) B *b;
@end

class B

@interface B {

}
@property (nonatomic, assign) A *a;
@end
vodkhang
  • 18,639
  • 11
  • 76
  • 110
0

In the iPhone environment without garbage collection, a weak reference is more or less defined as a reference where the object is not retained. With that in mind (actually even without that in mind), you have no control over whether NSMutableArray creates a weak reference or not. In the GC environment, you still have the cycle, but it doesn't matter because once both objects unreachable, they'll both go away.

The only thing you can do is to manually break the cycle. You do that by removing one of the arrays from the other.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Can you explain - You state that when both objects are unreachable they both go. But it is my understanding that an object goes away only when the retain count is zero. Is this something specific to GC? – xcoder Feb 09 '12 at 03:32
  • @xcoder: yes, it is specific to GC. GC works by taking a set of root objects (globals + those on the stack + some others) and sees which others are reachable (i.e. have references) from those root objects. Anything else is deallocated. – JeremyP Feb 10 '12 at 11:55