2

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"

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • 3
    I'm not sure what you are asking, nor whether you're using the term "dereferenced" correctly... –  Apr 10 '13 at 18:59
  • That's fair @H2CO3, basically I got a sigbart because the object I was holding didn't point to anything. Unfortunately I forget the exact terminology. Please feel free to fix the question and the title, or tell me how to. I'm merely asking whether explicitly declaring the variable in my method helps or not. – Dan Rosenstark Apr 10 '13 at 19:05
  • There must be multithreading involved here if that's happening. Can you post a runnable chunk of code that demonstrates the problem? – jscs Apr 10 '13 at 19:06
  • 2
    @Yar Thanks. It's rather a "dangling pointer" (that term tends to be used to describe invalid and leaking pointers, quite freely). "Dereferencing" is the act of taking the value behind the pointer, the actual data it holds the address of. –  Apr 10 '13 at 19:06
  • @JoshCaswell yes it's multithreading, since the `thing` gets updated on another thread. I'll fix the question to use "dangling pointer" Posting an example would be hard, though I guess I could contrive one. Then I'd actually be able to test it and also answer it, which I can do, but not right this second. Thanks! – Dan Rosenstark Apr 10 '13 at 19:39
  • Your question is kind of confusing, and I think you're approaching the problem from the wrong angle. A dangling pointer is the result of poor memory management. At some point in your code you've freed the memory it was pointing to, and you shouldn't have. You need to figure out when that happens, and change it. – Parker Kemp Apr 10 '13 at 19:52
  • It's an `NSString` instance. If it gets replaced WHILE `thirdObject` is using it, I believe that I get a dangling pointer, since it gets dealloced. This is, at least, my running theory. Like I said, this is really hard to test since it almost never happens empirically (and this was in the simulator). – Dan Rosenstark Apr 10 '13 at 20:05
  • 1
    @ParkerKemp I realize that you're right. I'm approaching the problem from the wrong angle, which is to go back to when I didn't have the problem and untangle ;) I didn't have the problem before because I was creating the `NSString` instance based on the value of a float, and there were never dealloc problems with that float ;)... So I'll go back to doing that. So yeah, thank you, your advice was right. – Dan Rosenstark Apr 11 '13 at 04:41

1 Answers1

7

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.

Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 2
    Thanks, still reading this, but is that first sentence second para missing a "don't?" – Dan Rosenstark Apr 11 '13 at 02:37
  • Hmmm... I'm definitely out of my depths here, but `atomic` does fix the problem, so I'll have to read over your answer a few hundred times. I still don't understand how I could get a dealloced instance ever... doesn't ARC call a retain on it before returning it? – Dan Rosenstark Apr 11 '13 at 03:43
  • Thanks, I see the whole problem now. I had moved from storing a primary to storing an NSString instance, so I suddenly had a "threading" problem. I'm amazed at how lucky I was before to be sharing floats only. Thanks for your help. – Dan Rosenstark Apr 11 '13 at 04:43
  • 1
    Fixed that comment about atomic... :) Yes, as you're seeing in a non-atomic accessor, you could get back a "bare" value (i.e. no additional retain/autorelease has been added). Then someone sets the property behind your back, releasing the object you're pointing to. This problem is exactly the same in ARC and in retain/release code. – Rob Napier Apr 11 '13 at 13:02
  • Regarding your solution: In my code, the variable in question is being read on the main thread, but set on a different thread. In that case wouldn't it be enough to have the setter use `dispatch_barrier_async`and dispatch to the main queue? Would that allow me avoid modifying the getter and introducing yet another queue? – Dan Rosenstark Apr 11 '13 at 22:02
  • 2
    If that's your use case, just use `dispatch_async` to the main queue for your writer. You should not use `dispatch_barrier_async` on a system queue. This is identical to how you would handle UI updates from a background thread. – Rob Napier Apr 11 '13 at 22:19
  • Awesome, thank you for the answer and the dialogue. This isn't so difficult to understand after all :) – Dan Rosenstark Apr 11 '13 at 23:04
  • @RobNapier Do you need a queue for each property, or is one queue for the whole object sufficient? My use case is a large number of "low traffic" ivars. – Sam Washburn Dec 29 '13 at 06:46
  • 1
    You can use one serial queue, but then all property writes will block on each other. Queues are cheap, but either way is fine if it's inconvenient to have multiple queues. – Rob Napier Dec 29 '13 at 19:18