0

I have a pretty standard case and for the sake of simplicity, assume there's MyController and its MyModel. Heres's an excerpt of MyModel:

@interface MyModel

  @property (nonatomic, weak) id<ModelObserver> *delegate;

@end

@implementation MyModel

- (id)initWithDelegate:(id<ModelObserver>)delegate
{
  self = [super init];
  if (self) {

    _delegate = delegate;

    // Adds observer for model load and changes
    [self addObserver:_delegate];
    ...
}

- (void)dealloc
{
  // Remove delegates and progress indicator
  [self removeObserver:_delegate];

@end

Here's an excerpt of MyController

@interface MyController

@property (nonatomic, strong) MyModel *_myModel;

@end

@implementation MyController

- (void)commonInit
{
  _myModel = [[MyModel alloc] initWithDelegate:self];
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
    // Custom initialization
    [self commonInit];
  }
  return self;
}

@end

MyModel has a delegate of type id (pointing to MyController). MyController has strong ivar _myModel. MyController creates model and passes himself as a delegate upon alloc. MyModel extends MyBaseModel, and a delegate (MyController) is added to observer list and gets notified about list model changes. The problem is, observer MyController gets deallocated before [self removeObserver:_delegate]; is able to remove it from observer list, so I'm getting crash sometimes later.

Already did debugging, and when execution stops on "[self removeObserver:_delegate];" The MyController still exists (po _delegate prints out object). However, _delegate is nil right after stepping into [self removeObserver:_delegate], I mean param is nil from inside.

UPDATE If I do "po _delegate" in dealloc I can see object, so it's not nil. But as soon as code enters [self removeObserver:_delegate] it's nil inside, so it's not passed as an argument to "removeObserver:". HOWEVER, when I return from "removeObserver:" to dealloc when debugging, _delegate is not nil again.

Centurion
  • 14,106
  • 31
  • 105
  • 197
  • What you describe in the last paragraph happens if the object that `_delegate` points to is already deallocated, but the instance variable has not yet been set to `nil`, see http://stackoverflow.com/questions/16122347/weak-property-is-set-to-nil-in-dealloc-but-propertys-ivar-is-not-nil/16123880#16123880. – Martin R Jun 11 '13 at 14:11
  • 2
    `id *delegate;` there should be no `*` here. – Mike Weller Jun 11 '13 at 14:14
  • @MartinR It's my first project with arc. Removing observer in dealloc was working in manual memory management. Do you have suggestions where should I remove delegates from observer list? – Centurion Jun 11 '13 at 14:19
  • 1
    Declaring the delegate property as `assign` instead of `weak` would probably solve this problem. – Martin R Jun 11 '13 at 14:41
  • @MartinR, Yep setting to assign (or unsafe_uretained) worked out. Thanks. Ps: you can put your comment as answer so I could accept it. – Centurion Jun 11 '13 at 15:00

1 Answers1

1

This is roughly what happens here:

  • When the MyController instance is deallocated, its instance variable _myModel is set to nil, which causes -[MyModel dealloc] to be called.
  • At that point, the _delegate instance variable is not yet nil, but accessing the delegate returns nil because the pointed to object is in the process of being deallocated.

(See Weak property is set to nil in dealloc but property's ivar is not nil for a more detailed analysis.)

Observing a weak property is generally dangerous, because the model has no control over when this object is deallocated.

If you just want to have the same behaviour as in manual reference counting, you can declare the delegate property as assign instead of weak. This implies a __unsafe_unretained instance variable and would solve the problem in your case.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • The weak delegate instance variable becomes nil when the delegate is deallocated itself. With __unsafe_unretained, all you'd have is a stale pointer to a deallocated object. Of course the observing mechanism also has a stale pointer to a deallocated object, so removing it _might_ work but its still very dangerous. – gnasher729 Mar 29 '14 at 13:24