Objective-C / Какао темы и головоломки сообщений - PullRequest
1 голос
/ 28 ноября 2009

У меня есть проблема, которая мучает меня некоторое время, я придумала решение, которое подробно опишу ниже, и, хотя оно, кажется, работает хорошо, я не с энтузиазмом отношусь к этому с точки зрения дизайна посмотреть, и мне любопытно, если у кого-нибудь будут какие-либо рекомендации по поводу лучшего способа сделать это.

По сути, у меня есть общий ресурс, скажем так, это каталог файлов. У меня есть один объект, который управляет этим ресурсом (мы назовем его экземпляром класса BossOfEverthing). BossOfEverthing обрабатывает добавление, удаление, изменение и извлечение данных из файлов в этом каталоге. Когда другой объект хочет получить доступ к общему ресурсу, он делает это через экземпляр BossOfEverthing. BossOfEverthing использует блокировки внутри, поскольку его клиентские объекты могут существовать и существуют в отдельных потоках. BossOfEverthing также поддерживает массив ссылок на клиентские объекты, которые соблюдают протокол BossOfEverthingClient. Когда BossOfEverthing собирается что-то изменить в общем ресурсе (возможно, из-за запроса от одного из своих клиентов), он заранее уведомляет всех клиентов, вызывая соответствующий селектор для каждого клиента, чтобы у всех них была возможность ответить первый. BossOfEverthing на самом деле является боссом, т.е. клиенты не могут сказать, одобряют ли они изменение общего ресурса, но им вначале предоставляется возможность выполнить любые необходимые действия по очистке. С моей точки зрения, у BossOfEverthing много «делегатов». Разница между тем, что мне нужно сделать, и обычным шаблоном делегирования заключается в том, что:

  1. Есть много делегатов / клиентов
  2. Делегаты / клиенты создаются и уничтожаются много раз в течение всего жизненного цикла экземпляра BossOfEverthing (который фактически существует в течение всего жизненного цикла всего приложения).

Когда объект хочет стать клиентом экземпляра BossOfEverthing, он вызывает [BossOfEverthing addMeToYourClientsList:] (обычно из своего метода init) и когда объект хочет перестать быть клиентом BossOfEverthing, он вызывает [BossOfEverthing removeMeFromYourClientList:] ( его метод dealloc). Таким образом, BossOfEverthing знает, кого уведомлять (и в каком потоке), когда изменяется общий ресурс.

Обычно я бы использовал уведомления или KVO для отправки сообщений клиентам, но проблема в том, что все клиенты должны иметь возможность ответить соответствующим образом, ДО того, как ресурс действительно изменится (как в обычном шаблоне делегирования). Ни уведомления, ни блокировка KVO во время ответа получателей.

Хорошо, все кажется великолепным, но возьмем такой сценарий:

  1. BossOfEverthing собирается изменить общий ресурс, например. [BossOfEverthing changeSomething] вызывается некоторым объектом в потоке 1
  2. [BossOfEverthing changeSomething] получает блокировку, связанную с клиентским массивом
  3. [BossOfEverthing changeSomething] начинает выполнять итерацию по клиентскому массиву и вызывать для каждого клиента метод SomeIsAboutToBeChanged в соответствующем потоке для каждого клиента
  4. Тем временем один из клиентов (clientX) собирается исчезнуть, поэтому он вызывает [BossOfEverthing removeMeFromYourClientList:] в потоке 2 из своего метода dealloc
  5. [BossOfEverthing removeMeFromYourClientList:] пытается получить блокировку, связанную с клиентским массивом в потоке 2, чтобы он мог удалить clientX

Что здесь происходит, так это то, что я попал в тупик, потому что:

  1. [BossOfEverthing changeSomething] (который получил блокировку в потоке 1) ожидает возврата [clientXthingIsAboutToBeChanged] из потока 2
  2. Поток 2 застрял в ожидании получения блокировки, которой в настоящее время владеет [BossOfEverthing changeSomething] в потоке 1, и он не может ответить на свой методthingIsAboutToBeChanged

Вот что я сделал, чтобы исправить это:

  1. Вместо того, чтобы clientX вызывал [BossOfEverthing removeMeFromYourClientList:] из своего метода dealloc, он вызывает его из своего метода освобождения (только когда retainCount == 1)
  2. [BossOfEverthing removeMeFromYourClientList:] вместо того, чтобы ждать блокировки, просто ПЫТАЕТСЯ ее получить, и если она не может, она возвращает НЕТ.
  3. Если [BossOfEverthing removeMeFromYourClientList:] возвращает NO для [clientX release], тогда [clientX release] вызывает [self executeSelector: @selector (release) withObject: nil afterDelay: 0.1], в противном случае он просто вызывает [super release]

Таким образом, у clientX есть шанс ответить на любые сообщения, которые [BossOfEverthing changeSomething] может отправлять, и позволить [BossOfEverthing changeSomething] завершить свою работу и снять блокировку, в то время как другой вызов [clientX release] ставится в очередь в задержке.

Проблема с этим заключается в том, что:

  1. Клиентские объекты принадлежат к разным классам, поэтому я должен скопировать и вставить свой переопределенный метод выпуска в каждый из них. Есть кое-что, что раздражает меня при копировании и вставке одного и того же кода в несколько классов. Если бы target-c допускал множественное наследование, я мог бы создать другой класс BossOfEverthingClient, единственным определенным методом которого будет переопределенный выпуск.
  2. Вся эта процедура кажется немного запутанной.

В любом случае, большое спасибо за чтение моего длинного сложного поста, и я с нетерпением буду ждать любого предложения, которое кто-либо имеет. Еще раз спасибо!

Ответы [ 2 ]

1 голос
/ 28 ноября 2009

Когда вы собираетесь перебрать клиентский массив, я бы сделал это так:

  1. Приобрести замок
  2. Сделать мелкую копию клиентского массива
  3. Снять замок
  4. Перебрать копию массива

При копировании массива на каждый клиентский объект накладывается удержание, которое предотвращает их освобождение во время итерации. Кроме того, блокировка снимается до начала итерации, что позволяет избежать тупика. Кроме того, он позволяет клиентам вызывать removeMeFromYourClientList: во время итерации, что может или не может быть полезным.

1 голос
/ 28 ноября 2009

Перегрузка -release - хороший признак того, что у вас проблемы ...

Настоящая проблема IMO заключается в том, что вы берете долгоживущую блокировку на шаге 2, чтобы перебрать список. Трудно сохранить эту блокировку атомарной из-за всех других шагов, которые могут произойти (некоторые из которых повторяются).

Решением этой проблемы является ослабление удержания данных. Во-первых, у каждого клиента должен быть уникальный идентификатор (его расположение в памяти хорошо, если у вас нет ничего более удобного). Создать словарь идентификатора-> клиента. Теперь, на втором шаге, заблокируйте словарь и сделайте снимок идентификаторов . Тогда сразу разблокируйте словарь. Когда абоненты хотят удалить себя, заблокируйте словарь, удалите их и разблокируйте.

Теперь, если кто-то исчезнет, ​​когда вы будете перебирать вопросы, это нормально. Вы возьмете свой идентификатор, поищите в словаре и обнаружите, что такого объекта нет. Бросьте идентификатор (это просто строка) и перейдите к следующему. Если кто-то появляется в середине итерации, вы не будете спрашивать его об этом раунде, но, надеюсь, это приемлемо на первой итерации (конечно, есть решения, если это не так).

РЕДАКТИРОВАТЬ: более простое решение всего этого может быть просто позволить BossOfEverthing -retain всем своим клиентам, прежде чем он начнет задавать им вопросы, а затем -release их, когда это будет сделано. Это гарантирует, что ни один из них не освободится во время вопроса. Это хорошее решение, если важно, чтобы все они действительно ответили на вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...