Avoid This Dangling Pointer With ARC

2019-02-19 23:00发布

I have an object that holds a strong reference to a object:

@property (nonatomic, strong) NSObject *thing;

Elsewhere, I have a method that passes the object reference:

[thirdObject doSomething:secondObject.thing];

In one case (out of a million or billion), thirdObject ended up working with a dangling pointer, because the object got swapped and had no owner.

Can I avoid this by doing this? Is this different for ARC?

NSObject *thing = secondObject.thing
[thirdObject doSomething:secondObject.thing];

If not, how can I avoid this?

Edit: Message is "message sent to deallocated instance 0xwhatever"

1条回答
成全新的幸福
2楼-- · 2019-02-19 23:08

You cannot read and write properties on multiple threads without applying some kind of thread-safety. Now in principle, for a simple object like a string, maybe just applying atomic would be enough. See What's the difference between the atomic and nonatomic attributes? for more on exactly what that does.

Frankly, I really don't like atomic very much. I know what it does, but it seems a cumbersome way of getting to what you really want (and often winds up being less than you want). And it's not a very general solution; I can't customize an atomic accessor "a little" (like to add a setNeedsDisplay or the like).

That's why I like queue-based accessors. They're a little more work, but they're effective for a lot of problems.

@property (nonatomic, readwrite, strong) dispatch_queue_t thingQueue;
@property (nonatomic, strong) NSObject *thing;

- (id)init {
  ...
    _thingQueue = dispatch_queue_create("...", DISPATCH_QUEUE_CONCURRENT);
  ...
}

- (NSObject *)thing {
  __block NSObject *thing;
  dispatch_sync(self.thingQueue, ^{
    thing = _thing;
  });
  return thing;
}

- (void)setThing:(NSObject *)thing {
  dispatch_barrier_async(self.thingQueue, ^{
    _thing = thing;
  });
}

What I like about this system is that it allows all the readers as you want in parallel. When a writer tries to make an update, it is guaranteed not to starve, no matter how many readers are involved. And it always returns quickly. There is also a clear point in the queue when the value changes so that readers who requested the value prior to the writer will always get the old value, and readers after the writer will always get the new value, no matter how much contention there is on the queue.

The key is that the getter is synchronous, so it will wait until it can get the value, and the setter includes a barrier. The barrier means "no other blocks may be scheduled from this queue while I am running." So you have a bunch of reader blocks running in parallel, then the setter barrier comes along and waits for all the readers to finish. Then it runs alone, setting the value, and then the readers behind it can run in parallel again.

查看更多
登录 后发表回答