У меня есть проблема, которая мучает меня некоторое время, я придумала решение, которое подробно опишу ниже, и, хотя оно, кажется, работает хорошо, я не с энтузиазмом отношусь к этому с точки зрения дизайна посмотреть, и мне любопытно, если у кого-нибудь будут какие-либо рекомендации по поводу лучшего способа сделать это.
По сути, у меня есть общий ресурс, скажем так, это каталог файлов. У меня есть один объект, который управляет этим ресурсом (мы назовем его экземпляром класса BossOfEverthing). BossOfEverthing обрабатывает добавление, удаление, изменение и извлечение данных из файлов в этом каталоге. Когда другой объект хочет получить доступ к общему ресурсу, он делает это через экземпляр BossOfEverthing. BossOfEverthing использует блокировки внутри, поскольку его клиентские объекты могут существовать и существуют в отдельных потоках. BossOfEverthing также поддерживает массив ссылок на клиентские объекты, которые соблюдают протокол BossOfEverthingClient. Когда BossOfEverthing собирается что-то изменить в общем ресурсе (возможно, из-за запроса от одного из своих клиентов), он заранее уведомляет всех клиентов, вызывая соответствующий селектор для каждого клиента, чтобы у всех них была возможность ответить первый. BossOfEverthing на самом деле является боссом, т.е. клиенты не могут сказать, одобряют ли они изменение общего ресурса, но им вначале предоставляется возможность выполнить любые необходимые действия по очистке. С моей точки зрения, у BossOfEverthing много «делегатов». Разница между тем, что мне нужно сделать, и обычным шаблоном делегирования заключается в том, что:
- Есть много делегатов / клиентов
- Делегаты / клиенты создаются и уничтожаются много раз в течение всего жизненного цикла экземпляра BossOfEverthing (который фактически существует в течение всего жизненного цикла всего приложения).
Когда объект хочет стать клиентом экземпляра BossOfEverthing, он вызывает [BossOfEverthing addMeToYourClientsList:] (обычно из своего метода init) и когда объект хочет перестать быть клиентом BossOfEverthing, он вызывает [BossOfEverthing removeMeFromYourClientList:] ( его метод dealloc). Таким образом, BossOfEverthing знает, кого уведомлять (и в каком потоке), когда изменяется общий ресурс.
Обычно я бы использовал уведомления или KVO для отправки сообщений клиентам, но проблема в том, что все клиенты должны иметь возможность ответить соответствующим образом, ДО того, как ресурс действительно изменится (как в обычном шаблоне делегирования). Ни уведомления, ни блокировка KVO во время ответа получателей.
Хорошо, все кажется великолепным, но возьмем такой сценарий:
- BossOfEverthing собирается изменить общий ресурс, например. [BossOfEverthing changeSomething] вызывается некоторым объектом в потоке 1
- [BossOfEverthing changeSomething] получает блокировку, связанную с клиентским массивом
- [BossOfEverthing changeSomething] начинает выполнять итерацию по клиентскому массиву и вызывать для каждого клиента метод SomeIsAboutToBeChanged в соответствующем потоке для каждого клиента
- Тем временем один из клиентов (clientX) собирается исчезнуть, поэтому он вызывает [BossOfEverthing removeMeFromYourClientList:] в потоке 2 из своего метода dealloc
- [BossOfEverthing removeMeFromYourClientList:] пытается получить блокировку, связанную с клиентским массивом в потоке 2, чтобы он мог удалить clientX
Что здесь происходит, так это то, что я попал в тупик, потому что:
- [BossOfEverthing changeSomething] (который получил блокировку в потоке 1) ожидает возврата [clientXthingIsAboutToBeChanged] из потока 2
- Поток 2 застрял в ожидании получения блокировки, которой в настоящее время владеет [BossOfEverthing changeSomething] в потоке 1, и он не может ответить на свой методthingIsAboutToBeChanged
Вот что я сделал, чтобы исправить это:
- Вместо того, чтобы clientX вызывал [BossOfEverthing removeMeFromYourClientList:] из своего метода dealloc, он вызывает его из своего метода освобождения (только когда retainCount == 1)
- [BossOfEverthing removeMeFromYourClientList:] вместо того, чтобы ждать блокировки, просто ПЫТАЕТСЯ ее получить, и если она не может, она возвращает НЕТ.
- Если [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] ставится в очередь в задержке.
Проблема с этим заключается в том, что:
- Клиентские объекты принадлежат к разным классам, поэтому я должен скопировать и вставить свой переопределенный метод выпуска в каждый из них. Есть кое-что, что раздражает меня при копировании и вставке одного и того же кода в несколько классов. Если бы target-c допускал множественное наследование, я мог бы создать другой класс BossOfEverthingClient, единственным определенным методом которого будет переопределенный выпуск.
- Вся эта процедура кажется немного запутанной.
В любом случае, большое спасибо за чтение моего длинного сложного поста, и я с нетерпением буду ждать любого предложения, которое кто-либо имеет.
Еще раз спасибо!