Ответ 1
Суть проблемы заключается в том, что кишки наблюдения за ключевыми значениями живут в NSObject
, а NSProxy
не наследуется от NSObject
. Я уверен, что любой подход потребует, чтобы объект NSProxy
сохранял свой собственный список наблюдений (то есть, что люди, находящиеся за пределами людей, надеются наблюдать за ним). Это само по себе добавит значительный вес в реализацию NSProxy.
Обратите внимание на цель
Похоже, вы уже пробовали, чтобы наблюдатели прокси фактически наблюдали реальный объект - другими словами, если цель всегда была заполнена, и вы просто перенаправили все вызовы на цель, вы также будете пересылать addObserver:...
и removeObserver:...
. Проблема в том, что вы начали, сказав:
NSProxy, похоже, работает очень хорошо, поскольку объекты в режиме ожидания для тех, кто еще не существует
Для полноты я опишу некоторые из этих проблем и почему он не может работать (по крайней мере, для общего случая):
Чтобы это сработало, вашему подклассу NSProxy
пришлось бы собирать вызовы методов регистрации, которые были вызваны до того, как была установлена цель, а затем передать их цели, когда она будет установлена. Это быстро становится волосатым, если вы считаете, что вы также должны обрабатывать удаление; вы не захотите добавить наблюдение, которое впоследствии было удалено (поскольку объект наблюдения мог быть удален). Вероятно, вы также не хотите, чтобы ваш метод отслеживания наблюдений сохранял любого из наблюдателей, чтобы это не создавало непреднамеренные циклы удержания. Я вижу следующие возможные переходы в целевом значении, которые нужно будет обрабатывать
- Цель была
nil
на init, позже будетnil
- Цель была установлена не
nil
, а затемnil
- Цель была установлена не
nil
, а затем изменилась на другое значениеnil
- Цель была
nil
(не для init), позже будетnil
... и мы сразу сталкиваемся с проблемами в случае №1. Вероятно, мы были бы здесь в порядке, если наблюдатель KVO наблюдал только objectValue
(так как это всегда будет ваш прокси), но скажите, что наблюдатель заметил keyPath, который проходит через ваш прокси/реальный объект, скажем objectValue.status
. Это означает, что оборудование KVO будет называться valueForKey: objectValue
на целевом объекте наблюдения и снова получит ваш прокси-сервер, после чего оно вызовет valueForKey: status
на ваш прокси-сервер и вернется nil
назад. Когда цель становится не nil
, KVO будет считать, что это значение изменилось из-под нее (т.е. Не соответствует KVO), и вы получите сообщение об ошибке, которое вы указали. Если у вас был способ временно заставить цель вернуть nil
для status
, вы можете включить это поведение, вызовите -[target willChangeValueForKey: status]
, выключите поведение, затем вызовите -[target didChangeValueForKey: status]
. Во всяком случае, мы можем остановиться здесь в случае №1, потому что у них такие же подводные камни:
-
nil
ничего не сделает, если вы назоветеwillChangeValueForKey:
на нем (т.е. механизм KVO никогда не узнает, чтобы обновить свое внутреннее состояние во время перехода к или изnil
) - заставляет любой целевой объект иметь механизм, посредством которого он временно будет лежать и возвращает
nil
from valueForKey: для всех ключей кажется довольно обременительным требованием, когда заявленное желание было "прозрачным прокси". - что это значит даже для вызова setValue: forKey: на прокси с целевым объектом
nil
? мы сохраняем эти ценности? ожидая реальной цели? мы бросаем? Огромная открытая проблема.
Одна из возможных модификаций этого подхода заключалась бы в использовании суррогатной цели, когда реальная цель nil
, возможно, пустая NSMutableDictionary
и переадресация KVC/KVO для суррогата. Это решит проблему неспособности значимо называть willChangeValueForKey:
на nil
. Все, что сказало, предполагая, что вы сохранили свой список наблюдений, я не уверен, что KVO будет терпеть следующую последовательность, которая будет связана с установкой цели здесь в случае №1:
- внешние вызовы наблюдателей
-[proxy addObserver:...]
, прокси пересылает в словарь суррогат - proxy вызывает
-[surrogate willChangeValueForKey:
], потому что цель установлена - прокси-вызовы
-[surrogate removeObserver:...
] на суррогате - прокси-вызовы
-[newTarget addObserver:...]
для новой цели - proxy вызывает
-[newTarget didChangeValueForKey:
], чтобы сбалансировать вызов # 2
Мне не ясно, что это также не приведет к той же ошибке. Весь этот подход действительно формируется, чтобы быть горячим беспорядком, не так ли?
У меня было несколько альтернативных идей, но № 1 довольно тривиально, а №2 и №3 - недостаточно простые или достаточно внушающие доверие, чтобы заставить меня хотеть записать время, чтобы закодировать их. Но, для потомков, как насчет:
1. Используйте NSObjectController
для вашего прокси-сервера
Конечно, он подбирает ваши keyPaths с дополнительным ключом, чтобы пройти через контроллер, но это своего рода NSObjectController's
целая причина бытия, правильно? Он может иметь содержание nil
и будет обрабатывать все установленные наблюдения и срывать их. Он не достигает цели прозрачного прокси-сервера пересылки вызовов, но, к примеру, если целью является наличие резервной копии для некоторого асинхронно генерируемого объекта, вероятно, было бы довольно просто, чтобы асинхронная операция генерации доставляла конечную объект к контроллеру. Это, вероятно, подход с минимальными усилиями, но на самом деле не затрагивает "прозрачное" требование.
2. Используйте подкласс NSObject
для вашего прокси-сервера
NSProxy's
Основная особенность заключается не в том, что в ней есть какая-то магия - основная особенность заключается в том, что в ней нет (все) реализации NSObject
. Если вы готовы приложить все усилия, чтобы отменить все NSObject
поведения, которые вы не хотите, и отключить их обратно в свой механизм пересылки, вы можете получить то же сетевое значение, которое предоставляется NSProxy
, но с механизм поддержки KVO оставлен на месте. Оттуда, это вопрос вашего прокси-сервера, который просматривает все те же ключевые пути на целевом объекте, которые наблюдались на нем, а затем ретранслирует сообщения willChange...
и didChange...
из целевого объекта, чтобы внешние наблюдатели рассматривали их как исходящие из вашего прокси-сервера.
... и теперь для чего-то действительно сумасшедшего:
3. (Ab) Используйте среду выполнения, чтобы привести поведение NSObject
KVC/KVO в ваш подкласс NSProxy
Вы можете использовать среду выполнения для получения реализаций метода, связанных с KVC и KVO, от NSObject
(т.е. class_getMethodImplementation([NSObject class], @selector(addObserver:...))
), а затем вы можете добавить эти методы (т.е. class_addMethod([MyProxy class], @selector(addObserver:...), imp, types)
) в ваш прокси-подкласс.
Это, скорее всего, приведет к процессу угадывания и проверки процесса выяснения всех частных/внутренних методов на NSObject
, которые вызовут общедоступные методы KVO, а затем добавив их в список методов, которые вы продаете оптом. Логично предположить, что внутренние структуры данных, которые поддерживают соблюдение KVO, не будут поддерживаться в ivars NSObject
(NSObject.h
означает отсутствие ivars - не означает, что это означает что-либо в наши дни), поскольку это будет означать, что каждый NSObject
экземпляр оплачивал бы космическую цену. Кроме того, я вижу много функций C в стеке следов уведомлений KVO. Я думаю, что вы, вероятно, дойдете до такой степени, что у вас было достаточно возможностей для NSProxy, чтобы стать первоклассным участником KVO. С этого момента это решение выглядит как решение NSObject
; вы наблюдаете цель и ретранслируете уведомления так, как будто они пришли от вас, дополнительно подталкивая willChange/didChange уведомления о любых изменениях цели. Возможно, вы даже сможете автоматизировать некоторые из них в своем механизме пересылки вызовов, установив флаг при вводе любого из вызовов общедоступного API KVO, а затем попытайтесь использовать все вызванные вами методы до тех пор, пока вы не очистите флаг, когда открытый API call return - зацепка, которая будет пытаться гарантировать, что использование этих методов иначе не испортило бы прозрачность вашего прокси.
Где я подозреваю, что это упадет, это механизм, при котором KVO создает динамические подклассы вашего класса во время выполнения. Детали этого механизма непрозрачны и, вероятно, приведут к еще одному долгую поездку по выяснению частных/внутренних методов, которые можно ввести из NSObject
. В конце концов, этот подход также является полностью хрупким, чтобы ни одна из внутренних деталей реализации не изменилась.
... в заключение
В реферате проблема сводится к тому, что KVO ожидает согласованного, узнаваемого, постоянно обновляемого (через уведомления) состояния через это ключевое пространство. (Добавьте "измененный" в этот список, если вы хотите поддерживать -setValue:forKey:
или редактируемые привязки.) Запрет на использование трюков, являющихся участниками первого класса, означает NSObjects
. Если один из этих шагов в цепочке реализует его функциональность, позвонив в какое-то другое внутреннее состояние, его прерогатива, но он будет отвечать за выполнение всех своих обязательств по соблюдению KVO.
По этой причине я полагаю, что если какое-либо из этих решений стоит усилий, я бы поместил свои деньги на "используя NSObject
как прокси, а не NSProxy
". Таким образом, чтобы достичь точной природы вашего вопроса, может быть способ сделать подкласс NSProxy
совместимым с KVO, но вряд ли это похоже на то, что он того стоит.