Unusual Memory Management Situations

1.

NSNotificationCenter presents some curious memory management features. As you are likely to want to use notifications (Chapter 11), you’ll need to know about these.

If you registered with the notification center using addObserver:selector:name:object:, you handed the notification center a reference to some object (usually self) as the first argument; the notification center’s reference to this object is a non-ARC weak reference, and there is a danger that after this object goes out of existence the notification center will try to send a notification to whatever is referred to, which will be garbage. That is why you must unregister before that can happen. This is similar to the situation with delegates that I was talking about a moment ago.

If you registered with the notification center using addObserverForName:object:queue:usingBlock:, memory management can be quite tricky, under ARC in particular, because:

  • The observer token returned from the call to addObserverForName:object:queue:usingBlock: is retained by the notification center until you unregister it.
  • The observer token may also be retaining you (self) through the block. If so, then until you unregister the observer token from the notification center, the notification center is retaining you. This means that you will leak until you unregister. But you cannot unregister from the notification center in dealloc, because dealloc isn’t going to be called so long as you are registered.
  • In addition, if you also retain the observer token, then if the observer token is retaining you, you have a retain cycle on your hands.

Consider, for example, this code, in which we register for a notification and assign the observer token to an instance variable:

self->_observer = [[NSNotificationCenter defaultCenter]

    addObserverForName:@"heyho"

    object:nil queue:nil usingBlock:^(NSNotification *n) {

        NSLog(@"%@", self);

    }];

Our intention is eventually to unregister the observer; that’s why we’re keeping a reference to it. It’s natural to do this in dealloc:

- (void) dealloc {

    [[NSNotificationCenter defaultCenter] removeObserver:self->_observer];

}

But that won’t work; dealloc is never called, because there’s a retain cycle: self is retaining the observer,but because of the block, the observer is also retaining self. Therefore we (self) are leaking and we are never unregistered.

There are two ways to break the retain cycle. One is to release the _observer object as we unregister it. However, dealloc still won’t be called until after we’ve done that, so we need to look for a place other than dealloc where we can unregister the _observer object and release it.

For example, if this is a UIViewController, then one such place might be the view controller’s viewDidDisappear:, which is called when the view controller’s view is removed from the interface:

- (void) viewDidDisappear:(BOOL)animated {

    [super viewDidDisappear:animated];

    [[NSNotificationCenter defaultCenter] removeObserver:self.observer];

    self->_observer = nil; // release the observer

}

When the observer is unregistered, it is released by the notification center. When we too release it, no one is retaining it any longer; the observer goes out of existence, and as it does so, it releases self, which is subsequently able to go out of existence in good order — dealloc will be called, and self no longer leaks. Alternatively, if the _observer ivar is marked as __weak, we can omit that last line; when the observer is unregistered, it will be released by the notification center, and since the notification center was the only thing retaining it, the observer is destroyed, releasing self as it goes.

This approach requires some careful management, however, because viewDidDisappear: can be called more than once over the life of a view controller. We have to register for the notification again in some symmetric location, such as viewWillAppear:. Plus, if self is not a view controller, finding an appropriate place other than dealloc to unregister may not be so easy.

A better solution, in my opinion, is not to allow the observer object to retain self in the first place. That way, there’s no retain cycle to begin with. The way to prevent a block from retaining self is not to mention self (or any instance variables of self) within the block. Since there is no retain cycle, dealloc will be called, and we can unregister the observer in dealloc, which is what we wanted to do all along.

So how can you refrain from mentioning self within a block if you need to send a message to self within the block? You use an elegant little technique, commonly called “the weak–strong dance” (Example 12-9). The efficacy of the weak–strong dance lies in the fact that self is never mentioneddirectly in the block. In actual fact, a reference to self does get passed into the block, but at that moment it’s a weak reference, which is enough to prevent self from being retained by the block and possibly causing a retain cycle. Once inside the block, this reference is converted to a strong reference, and everything proceeds normally.

Example 12-9. The weak–strong dance prevents a block from retaining self

__weak MyClass* wself = self;  self->_observer = [[NSNotificationCenter defaultCenter]

    addObserverForName:@"heyho"

    object:nil queue:nil usingBlock:^(NSNotification *n) {

        MyClass* sself = wself; 

 if (sself) {

            // refer to sself freely, but never to self  }

    }];

Here are the steps of the weak–strong dance, as schematized inExample 12-9:


We form a local weak reference to self, outside the block but where the block can see it. It is this weak reference that will pass into the block.


Inside the block, we assign that weak reference to a normal strong reference. Weak references are inherently volatile; there is a chance that a weakly referenced object, even self, may vanish out from under us between one line of code and the next. In that case, the weak reference will be nil, but messaging it is expensive, and directly referencing an instance variable of it will be disastrous; moreover, there is no thread-safe way to check whether a weak reference is nil. Assigning to a strong reference solves the problem.


We use the strong reference in place of any references to self inside the block. All action involving the strong reference is wrapped in a nil test because, if our weakly referenced object did vanish out from under us, there would be no point continuing.


2.

Another unusual case is NSTimer (Chapter 10). The NSTimer class documentation says that “run loops retain their timers”; it then says of scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: that “The target object is retained by the timer and released when the timer is invalidated.” This means that as long as a repeating timer has not been invalidated, the target is being retained by the run loop; the only way to stop this is to send the invalidate message to the timer. (With a non-repeating timer, the problem doesn’t arise, because the timer invalidates itself immediately after firing.)

When you called scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:, you probably supplied self as the target: argument. This means that you (self) are being retained, and cannot go out of existence until you invalidate the timer. You can’t do this in your dealloc implementation, because as long as the timer is repeating and has not been sent the invalidate message, dealloc won’t be called. You therefore need to find another appropriate moment for sending invalidate to the timer. There’s no good way out of this situation; you simply have to find such a moment, and that’s that.

A block-based alternative to a repeating timer is available through GCD. The timer “object” is a dispatch_source_t, and must be retained, typically as an instance variable (which ARC will manage for you, even though it’s a pseudo-object). The timer will fire repeatedly after you initially “resume” it, and will stop firing when it is released, typically by nilifying the instance variable. But you muststill take precautions to prevent the timer’s block from retaining self and causing a retain cycle, just as with notification observers. Here’s some typical skeleton code:

@implementation MyClass {

    dispatch_source_t _timer; // ARC will manage this pseudo-object

}

- (void)doStart:(id)sender {

    self->_timer = dispatch_source_create(

        DISPATCH_SOURCE_TYPE_TIMER,0,0,dispatch_get_main_queue());

    dispatch_source_set_timer(

        self->_timer, dispatch_walltime(nil, 0),

        1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC

        );

    __weak id wself = self;

    dispatch_source_set_event_handler(self->_timer, ^{

        MyClass* sself = wself;

        if (sself) {

            [sself dummy:nil]; // prevent retain cycle

        }

    });

    dispatch_resume(self->_timer);

}

- (void)doStop:(id)sender {

    self->_timer = nil;

}

- (void) dummy: (id) dummy {

    NSLog(@"timer fired");

}

- (void) dealloc {

    [self doStop:nil];

}

@end


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值