1

`I admit that I am not an expert on ARC and retain cycles though through some research and some great articles (like this), I believe I get the basics.

However, I am currently stumped. I have a property defined as follows.

@property (nonatomic,retain) Foo *foo;

Inside my init, I do the following.

if(self = [super init]) {

    _foo = [[Foo alloc] initWithDelegate:self];

    // async the rest
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
                                             (unsigned long)NULL), ^(void) {

        __strong typeof(weakSelf) strongSelf = weakSelf;

        if (strongSelf.foo != nil) {
                        [strongSelf.foo runTests];
        }
    });
}
return self;
}

and here is my dealloc

- (void)dealloc {
     _foo = nil;
}

If the dispatch_aync block is commented out, I see my Foo dealloc get called immediately after foo is set to nil. With the block commented in, foo's delloc is not called.

I've got a retain cycle correct? Any idea how?

Ternary
  • 2,401
  • 3
  • 31
  • 54
  • 1
    BTW, I'd suggest using `strong` instead of `retain` in your definition of the `Foo` property. There's little practical impact in this case, but in ARC code, you should be using `strong` and `weak`, not `retain` and `assign`. Also, in ARC code, you no longer need to manually `nil` your instance variables, so you can retire that `dealloc` method. – Rob Sep 10 '14 at 16:10

1 Answers1

2

No, you do not necessarily have a retain cycle (now known as a "strong reference cycle" in ARC). You have code that, if foo existed by the time strongSelf was defined, foo will be retained until the dispatched code finishes.

The only potential strong reference cycle here is the delegate you passed to foo. If that delegate is defined as strong property of the Foo class, then you have a strong reference cycle. If it's defined as a weak property, then you have no strong reference cycle.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • If I'm not mistaken, once the `delegate` is property made `weak`, there is no need for the posted `dealloc` method nor is there any need for the `__weak self` use in the dispatch block. – rmaddy Sep 10 '14 at 16:14
  • The `dealloc` method can be removed in ARC regardless. If the `delegate` was `weak`, it's just not necessary. If the `delegate` was `strong`, you have a strong reference cycle (bad, bad, bad) and the `dealloc` would never get called, anyway (!). – Rob Sep 10 '14 at 16:18
  • Thanks for all the great input, I'm learning a ton here. So the delegate property of Foo is defined as `(nonatomic, assign)`. This code base was migrated too `ARC`, I think that's why we have the older property attributes. – Ternary Sep 10 '14 at 16:20
  • It means `__unsafe_unretained`, which means that it won't retain the object, but will leave a dangling pointer if the object is deallocated. That's why `weak` is so much better, that that pointer will be set to `nil` when the `delegate` is released, thereby eliminating whole categories of types of crashes. Replace that `assign` with `weak`. – Rob Sep 10 '14 at 16:21
  • @Rob by the way the reason I'm even chasing this is I'm relying on `foo` to be set to nil in dealloc to trigger `Foo`'s dealloc which sets the `delegate` to nil. What I'm seeing is an async operation inside `foo` completes after the `dealloc`. Then inside the completion block I call on the `delegate` and of course get an access violation. – Ternary Sep 10 '14 at 16:22
  • @Ternary No. When this object is released, it will automatically release its strong reference to `foo` anyway. Your `dealloc` was appropriate in pre-ARC code, but ARC eliminates the need for that `dealloc` method, and it's entirely redundant. Won't hurt, but should be removed in ARC to simplify your code. BTW, if that dispatch block is still running, the dispatch block, itself, will retain `foo`, so whether you `nil` it in `dealloc` or not, there is still one strong reference cycle (the dispatch block) that will keep it around. – Rob Sep 10 '14 at 16:24
  • If you want `foo` to be stopped and deallocated when this object is released, you have to implement your own cancellation logic within `foo`, which you can call from `dealloc`. A common pattern is to implement a `cancel` method which you'd call from `dealloc`. You'd then have `runTests` check frequently to see if `foo` was cancelled, and if so, terminate the tests immediately and return (thus ending the dispatch block and resolving that remaining strong reference). – Rob Sep 10 '14 at 16:28
  • @Rob great explanation, I see the benefits of the `weak` now. Will test now and report back. Will this be easier with swift? :) – Ternary Sep 10 '14 at 16:33
  • Changed my delegate to `(atomic, weak)` and when the async operation complete the delegate is already `nil`'d. *Beautiful*. I also changed `foo` to be `(atomic, strong)`. I think I'm good! – Ternary Sep 10 '14 at 16:36
  • So in my case, did I even need that weak/strong stuff anyway or should that always be the default? Seems like just lots of extra code. – Ternary Sep 10 '14 at 16:37
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60992/discussion-between-rob-and-ternary). – Rob Sep 10 '14 at 16:49