Сценарии Какао: удаление элементов в цикле, выходящем из синхронизации
Добавляя возможность написания сценариев к моей программе на Mac, я борюсь с общей проблемой программирования удаления элементов из индексированного массива, где индексы элементов смещаются из-за удаления элементов.
Допустим, мое приложение поддерживает хранилище данных, в котором хранятся объекты типа "Персона". В sdef я определил ключ какао allPersons
чтобы получить доступ к этим элементам. Мое приложение объявляет NSArray *allPersons
,
Это далеко, это работает хорошо. Например, этот скрипт хорошо работает:
repeat with p in every person
get name of p
end repeat
Проблема начинается, когда я хочу поддержать удаление элементов, например так:
repeat with p in (get every person)
delete p
end repeat
(Я понимаю, что мог бы просто написать "удалить каждого человека", что прекрасно работает, но я хочу показать, как "повторить" усложняет ситуацию).
Это не работает, потому что AppleScript продолжает использовать исходные номера элементов для ссылки на элементы даже после удаления некоторых из них, что естественным образом меняет элементы и их нумерацию.
Итак, учитывая, что у нас есть 3 человека, "Адам", "Бонни" и "Клайд", это произойдет:
get every person
--> {person 1, person 2, person 3}
delete person 1
delete person 2
delete person 3
--> error number -1719 from person 3
После удаления элемента 1 (Адам) другие элементы перенумеровываются в элементы 1 и 2. Вторая итерация удаляет элемент 2 (теперь это Клайд), а третья итерация пытается удалить элемент 3, которого больше не существует при этот момент.
Как мне это решить?
Могу ли я заставить механизм сценариев адресовать элементы не по их индексу, а по уникальному идентификатору, чтобы этого не произошло?
3 ответа
Это не ваш код ObjC, это ваше недопонимание того, как repeat with VAR in EXPR
петли работают. (Не совсем ваша вина: они 1. нелогичны и 2. плохо объяснены.) Когда он впервые сталкивается с вашим repeat
заявление, AppleScript отправляет ваше приложение count
событие, чтобы получить количество элементов, указанное EXPR
, который в этом случае является спецификатором (запросом) объекта, который идентифицирует все person
элементы в чем угодно. Затем он использует эту информацию для генерации своей собственной последовательности спецификаторов объекта по индексу, считая от 1 до результата вышеупомянутого count
:
person 1 of whatever
person 2 of whatever
...
person N of whatever
Вам нужно понять, что спецификатор объекта - это первоклассный запрос, а не указатель объекта (не то, что Apple это вам скажет): он описывает запрос, а не объект. Не обращайте внимания на изуродованный жаргон: Apple, ближайшие родственники IPC - это RDBMS, а не Cocoa, SOAP или любой другой источник сообщений, о котором современные разработчики так зациклились, как Единственный верный способ сделать... ну, ВСЕ.
Только когда этот запрос отправляется вашему приложению в событии Apple, он сравнивается с реляционным графиком, который ваш Apple-IPC View-Controller события - так называемая "модель объекта Apple" - представляет как идеализированное, удобное для пользователя представление пользователя вашей модели. дата, когда он фактически разрешается в конкретный объект Model или объекты, с которыми обработчик событий должен выполнить запрошенную операцию.
Таким образом, когда delete
командовать в вашем repeat
цикл говорит ваше приложение delete person 1 of whatever
все ваши оставшиеся элементы сдвигаются вниз на один. Но на следующей итерации repeat
цикл все еще генерирует спецификатор объекта person 2 of whatever
который ваш сценарий затем отправляет в ваше приложение, которое разрешает его второму элементу в коллекции - который изначально был третьим элементом, конечно, пока вы не переместили их все.
Или заимствовать фразу:
Ничто в AppleScript не имеет смысла, кроме как в свете реляционных запросов.
..
Фактически, подход, основанный на запросах событий Apple, на самом деле имеет большой смысл, учитывая, что изначально он был разработан для обеспечения эффективности при подключениях с очень высокой задержкой (т. Е. Ужасно неэффективный переключатель процессов в System 7), позволяющий одному событию Apple переносить одно или несколько событий. сложные запросы для манипулирования многими объектами одновременно. Это даже довольно элегантно [когда это работает правильно], но совершенно недооценено идиотами из Купертино, которые думают, что лучший способ заставить программистов не ненавидеть технологию - это еще больше лгать о том, как она на самом деле работает.
Итак, я предлагаю вам прочитать это, что тоже не лучшее объяснение, но все же чертовски лучше, чем все, что вы получите от этих кукол. И это, его первоначальный разработчик, который объясняет много смысла для создания высокоуровневой системы IPC на основе грубых запросов вместо обычной мелкоячеистой низкоуровневой передачи сообщений ОО.
О, и как только вы это сделаете, вы можете попробовать запустить это вместо:
delete every person whose name is "bob"
в этом и заключается весь смысл создания толстой декларативно-й абстракции, которая выполняет всю работу, чтобы пользователю не пришлось этого делать.
И когда ничего, кроме обязательного цикла на стороне клиента, не будет, вы либо захотите получить список спецификаторов объекта by-ID (которые являются ближайшими к безопасным, постоянным указателям, которые могут делать AEOM) из приложения, а затем итерировать что, или, по крайней мере, используйте свой собственный цикл итератора, который считает элементы в обратном порядке:
repeat with i from (count every person) to 1 by -1
tell person i
..
end tell
end repeat
так что, предполагая, что он выполняет итерацию по упорядоченному массиву на стороне сервера, удалится от последнего к первому, и, таким образом, избежит неловких ошибок в исходном сценарии.
НТН
re: "Если вы хотите, чтобы ваши элементы с возможностью написания сценариев можно было удалять, убедитесь, что вы используете спецификаторы NSUniqueIDS для их идентификации".
Да, Apple рекомендует использовать formUniqueId или formName для спецификаторов объектов, но вы не всегда можете это сделать. Например, в Text Suite у вас действительно есть только индексирование для работы; например, символ 1, слово 3, пункт 7 и т. д. У вас нет уникальных идентификаторов для текстовых элементов. Помимо удаления, на порядок могут влиять другие команды Standard Suite: открыть, закрыть, продублировать, создать и переместить.
Реализатор приложения - программист, но также и сценарист. Поэтому разумно ожидать, что сценарист сам решит некоторые проблемы. Например, если в приложении 5 человек, а сценарист хочет удалить людей 2 и 4, они могут легко это сделать даже при индексированном удалении:
delete person 4
delete person 2
Удаление с конца упорядоченного списка вперед решает проблему. AS также поддерживает отрицательные индексы, которые можно использовать для той же цели:
delete person -2
delete person -4
Ключ к решению этой проблемы лежит в реализации objectSpecifier
метод правильно, так что он возвращает NSUniqueIDSpecifier
,
Мой код до сих пор возвращал только указатель индекса, и это было неправильно для этой цели. Я думаю, что если бы я разместил свой код (который, к сожалению, слишком сложен для этого), кто-то, возможно, заметил мою ошибку.
Итак, я предполагаю, что правило таково: если вы хотите, чтобы ваши элементы с возможностью написания сценариев можно было удалять, убедитесь, что вы используете NSUniqueIDSpecifier
s, чтобы идентифицировать их. Для массивов элементов только для чтения, используя NSIndexSpecifier
Это (вероятно) безопасно, если ваш массив элементов имеет постоянное упорядочение.
Обновить
Как отмечает @foo, также важно, чтобы repeat
Команда выбирает ссылки на элементы с помощью … in (get every person)
и не только … in every person
потому что только первое ведет к адресации элементов по их идентификатору, а второе продолжает индексировать их как item N
,