Избегайте этого свисающего указателя с ARC

У меня есть объект, который содержит ссылку strong для объекта:

@property (nonatomic, strong) NSObject *thing;

В другом месте у меня есть метод, который передает ссылку на объект:

[thirdObject doSomething:secondObject.thing];

В одном случае (из миллиона или миллиарда), thirdObject закончил работу с обвисшим указателем, потому что объект был заменен и не имел владельца.

Могу ли я избежать этого, выполнив это? Разве это отличается от ARC?

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

Если нет, как я могу избежать этого?

Изменить: Сообщение: "сообщение отправлено на освобожденный экземпляр 0xwhatever"

Ответ 1

Вы не можете читать и писать свойства на нескольких потоках без применения какой-либо безопасности потоков. Теперь, в принципе, для простого объекта, такого как строка, может быть достаточно просто применить atomic. См. В чем разница между атомными и неатомическими атрибутами? Подробнее о том, что это делает.

Честно говоря, мне очень не нравится atomic. Я знаю, что он делает, но это кажется громоздким способом добраться до того, что вы действительно хотите (и часто заканчивается меньше, чем вы хотите). И это не очень общее решение; Я не могу настроить аксессуар atomic "немного" (например, добавить setNeedsDisplay или тому подобное).

Вот почему мне нравятся аксессоры на основе очереди. Они немного больше работают, но они эффективны для множества проблем.

@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;
  });
}

Что мне нравится в этой системе, так это то, что она позволяет всем читателям, как вы хотите, параллельно. Когда писатель пытается сделать обновление, гарантированно не голодать, независимо от того, сколько читателей задействовано. И он всегда возвращается быстро. Также есть явная точка в очереди, когда значение изменяется, так что читатели, которые запрашивали значение до писателя, всегда получат старое значение, а читатели после писателя всегда получат новое значение, независимо от того, сколько раз в очереди.

Ключ состоит в том, что геттер является синхронным, поэтому он будет ждать, пока он не сможет получить значение, а сеттер включает в себя барьер. Барьер означает, что "никакие другие блоки не могут быть запланированы из этой очереди во время работы". Таким образом, у вас есть куча блоков считывателя, работающих параллельно, тогда барьер сеттера приходит и ждет, пока все читатели закончат. Затем он запускается один, устанавливая значение, а затем читатели за ним могут снова работать параллельно.