Зачем в работающем приложении на горячую обновлять локализацию? Никто не умрёт, если новая локализация применится при следующем перезапуске. Зато сколько жизни можно сэкономить не поддерживая такую маргинальную фичу.
guard let data = try? Data(contentsOf: url),
let infoPlist = String(data: data, encoding: .utf8)
else { fatalError(.infoPlistNotFound) }
Если do { try f() } catch { fatalError() } и f()! это в принципе одно и то же (потому что одинаково напечатается одинаковая ошибка и одинаково упадёт), то здесь же стало хуже. try! Data(...) напечатал бы гораздо больше полезной информации о том, почему же не получилось прочитать данные, чем просто "infoPlistNotFound". Что ещё и не факт, что правда. Файл-то может быть found, но не смочь прочитаться по тысяче причин: недостаточно прав, прочитался только до половины, да чёрт ещё знает почему. Полезная информация, которая могла бы быть в логах, и помочь расследованию, оказывается просто потеряна.
Глядя на код в репозитории, я увидел множество проблем с этим примером. Какие-то касаются кода, какие-то более концептуальные. Я даже и не знаю с чего начать.
Начну пожалуй с любимого:
static func fetchDomainsJson(remotePath: String) -> [Domain] {
guard let url = URL(string: remotePath) else { fatalError(.remotePathIsInvalid) }
do {
let data = try Data(contentsOf: url)
return try JSONDecoder().decode([Domain].self, from: data)
} catch {
fatalError(.failedToLoadRemoteFile(error))
}
}
Обожаю эти do { try f() } catch { fatalError(error) }. Почему бы просто не сделать try! – система точно так же "упадёт", и точно так же напечатает ошибку.
То же касается и url. По-моему, хорошим тоном было бы сразу дать функции нормальные данные (готовый URL), а не заставлять её пытаться из потенциального мусора создать себе нормальное окружение.
Напоследок, раз уж тут есть какие-то try, то пусть бы функция была throws, а делать ли try! пусть решают уровнем выше.
Я не понимаю, какой массив, из каких строк, и почему что-то нельзя с ним сделать?
Я предлагал читать данные о доменах (видимо для отображения в UI) прямо из Info.plist, причём туда можно записать не только имя домена и флаг NSExceptionAllowsInsecureHTTPLoads, но так же и любые другие пользовательские данные, как тот же description. Да, структура будет чуть другая, ну и что? Вместо массива объектов типа [{ domain, description, flag }] будет структура { domain: { description, flag} }. Попарсить её нет никакой проблемы, зато есть один источник правды.
Если вы не хотите, чтобы эти домены попадали в релизную сборку, можно включить в Build Settings настройку Preprocess Info.plist File (INFOPLIST_PREPROCESS), добавить в Info.plist Preprocessor Definitions (INFOPLIST_PREPROCESSOR_DEFINITIONS) строчку типа "DEBUG" для Debug версии, и тогда в Info.plist можно будет сделать так:
Я сейчас впервые в жизни это попробовал сделать, и у меня заработало. Однако, боюсь, что plutil с макросами в теле файла уже не сможет работать. Но я и склоняю к тому, чтобы это менять руками, такие обновления происходят очень редко, и на мой взгляд лучше бы их было видно в истории изменений.
Но вообще, я не понимаю суть проблемы. Скорее всего это всё находится на подконтрольных серверах, обновляют этот json люди, которые знают и о приложении, и о доменах, и о разработчиках. Info.plist модифицируется при сборке, нужен для отладки. Так может надо просто научить тех, кто меняет домены, пойти в репозиторий приложения и закомитить изменения в Info.plist? Зачем нужна вся эта шайтан-машина со скриптами?
Вместо отдельного "файла-справочника" в приложении, можно читать эти данные прямо из своего же Info.plist. Я подозреваю, что iOS не обидится, если помимо NSExceptionAllowsInsecureHTTPLoads записать ещё и description.
Ещё рекомендую посмотреть man plutil и man PlistBuddy. PlistBuddy почему-то никогда не попадает в $PATH, но он всегда есть в /usr/libexec/PlistBuddy.
Как я понимаю, задача заточена чисто под iOS, и а раз так, то надо и делать всё так, чтобы было максимально удобно для имеющихся инструментов. Например вместо json с самодельным форматом можно было бы сразу хранить нужную ветку Info.plist в формате plist (или json), и делать PlistBuddy -x "Merge remote.plist". Но у PlistBuddy есть фатальный недостаток: он когда видит несуществующий ключ, возвращает ненулевой код ошибки. Это неудобно.
Но можно сделать почти идеально с plutil: plutil -replace 'NSAppTransportSecurity' -json "{ ... }" -- Info.plist
Вот формат, который мы на самом деле хотим видеть в "настроечном" удалённом файле:
Можно было бы делать и plutil -remove 'NSAppTransportSecurity' -- Info.plist, но plutil тоже будет возвращать код ошибки, если ключа не было, это может быть неудобно.
$ plutil -remove 'NSAppTransportSecurity' -- Info.plist
Info.plist: Could not modify plist, error: No value to remove at key path NSAppTransportSecurity
$ echo $?
1
$ plutil -replace 'NSAppTransportSecurity' -json '{}' -- Info.plist
$ echo $?
0
Британский сервис аренды Fetch доставляет электромобили с помощью дистанционного управления
мне в google stadia было очень дискомфортно в гонки играть из-за задержки, а тут настоящую машину по улице везти
Безопасная локализация строк в iOS: Localinter
Зачем в работающем приложении на горячую обновлять локализацию? Никто не умрёт, если новая локализация применится при следующем перезапуске. Зато сколько жизни можно сэкономить не поддерживая такую маргинальную фичу.
Использование Command Line Tool на Swift в iOS проекте
Итак, вот мой вариант:
мы вроде согласились, что отдельный файл не нужен, поэтому обновляется только Info.plist;
Info.plist по дефолту ищется в текущей рабочей директории, или его можно передать аргументом
-infoPlist
;json с данными может читаться из трёх источников:
updater -from https://....
– URL в интернете;updater -from /local/path/on/disk
– файл на диске;updater -from -
(dash) или же без аргументов – прочитается из stdin.curl url | updater
;cat file | updater
;updater < file
;updater
и ввод с завершающим Ctrd+D;команда для удаления тоже не нужна, т.к. это просто "записать пустой массив", и это можно сделать как
echo "[]" | updater
;сохраняет тот же формат файла Info.plist (xml или бинарный).
Использование Command Line Tool на Swift в iOS проекте
Разве что это проблема для тех, кто парсит такие файлы регулярками, но это исправимо.
Использование Command Line Tool на Swift в iOS проекте
Ну и что? Никого же не смущает, что в Dictionary и NSDictionary никогда не было никакого порядка.
Использование Command Line Tool на Swift в iOS проекте
Похожий, но другой пример проблемы:
Если
do { try f() } catch { fatalError() }
иf()!
это в принципе одно и то же (потому что одинаково напечатается одинаковая ошибка и одинаково упадёт), то здесь же стало хуже. try! Data(...) напечатал бы гораздо больше полезной информации о том, почему же не получилось прочитать данные, чем просто "infoPlistNotFound". Что ещё и не факт, что правда. Файл-то может быть found, но не смочь прочитаться по тысяче причин: недостаточно прав, прочитался только до половины, да чёрт ещё знает почему. Полезная информация, которая могла бы быть в логах, и помочь расследованию, оказывается просто потеряна.Использование Command Line Tool на Swift в iOS проекте
Если будет "просто массив строк", то можно его прямо в исходниках и хранить.
Использование Command Line Tool на Swift в iOS проекте
Какой ещё порядок элементов? У словаря нет понятия "порядок элементов".
Использование Command Line Tool на Swift в iOS проекте
Глядя на код в репозитории, я увидел множество проблем с этим примером. Какие-то касаются кода, какие-то более концептуальные. Я даже и не знаю с чего начать.
Начну пожалуй с любимого:
Обожаю эти
do { try f() } catch { fatalError(error) }
. Почему бы просто не сделатьtry!
– система точно так же "упадёт", и точно так же напечатает ошибку.То же касается и url. По-моему, хорошим тоном было бы сразу дать функции нормальные данные (готовый URL), а не заставлять её пытаться из потенциального мусора создать себе нормальное окружение.
Напоследок, раз уж тут есть какие-то try, то пусть бы функция была throws, а делать ли try! пусть решают уровнем выше.
Использование Command Line Tool на Swift в iOS проекте
Я не понимаю, какой массив, из каких строк, и почему что-то нельзя с ним сделать?
Я предлагал читать данные о доменах (видимо для отображения в UI) прямо из Info.plist, причём туда можно записать не только имя домена и флаг NSExceptionAllowsInsecureHTTPLoads, но так же и любые другие пользовательские данные, как тот же description. Да, структура будет чуть другая, ну и что? Вместо массива объектов типа
[{ domain, description, flag }]
будет структура{ domain: { description, flag} }
. Попарсить её нет никакой проблемы, зато есть один источник правды.Использование Command Line Tool на Swift в iOS проекте
Как это понять?
Использование Command Line Tool на Swift в iOS проекте
Если вы не хотите, чтобы эти домены попадали в релизную сборку, можно включить в Build Settings настройку
Preprocess Info.plist File (INFOPLIST_PREPROCESS)
, добавить в Info.plistPreprocessor Definitions (INFOPLIST_PREPROCESSOR_DEFINITIONS)
строчку типа "DEBUG" для Debug версии, и тогда в Info.plist можно будет сделать так:Я сейчас впервые в жизни это попробовал сделать, и у меня заработало. Однако, боюсь, что plutil с макросами в теле файла уже не сможет работать. Но я и склоняю к тому, чтобы это менять руками, такие обновления происходят очень редко, и на мой взгляд лучше бы их было видно в истории изменений.
Использование Command Line Tool на Swift в iOS проекте
Но вообще, я не понимаю суть проблемы. Скорее всего это всё находится на подконтрольных серверах, обновляют этот json люди, которые знают и о приложении, и о доменах, и о разработчиках. Info.plist модифицируется при сборке, нужен для отладки. Так может надо просто научить тех, кто меняет домены, пойти в репозиторий приложения и закомитить изменения в Info.plist? Зачем нужна вся эта шайтан-машина со скриптами?
Использование Command Line Tool на Swift в iOS проекте
Кажется, так и есть:
Использование Command Line Tool на Swift в iOS проекте
Вместо отдельного "файла-справочника" в приложении, можно читать эти данные прямо из своего же Info.plist. Я подозреваю, что iOS не обидится, если помимо
NSExceptionAllowsInsecureHTTPLoads
записать ещё и description.Использование Command Line Tool на Swift в iOS проекте
Ещё рекомендую посмотреть
man plutil
иman PlistBuddy
. PlistBuddy почему-то никогда не попадает в $PATH, но он всегда есть в/usr/libexec/PlistBuddy
.Как я понимаю, задача заточена чисто под iOS, и а раз так, то надо и делать всё так, чтобы было максимально удобно для имеющихся инструментов. Например вместо json с самодельным форматом можно было бы сразу хранить нужную ветку Info.plist в формате plist (или json), и делать
PlistBuddy -x "Merge remote.plist"
. Но у PlistBuddy есть фатальный недостаток: он когда видит несуществующий ключ, возвращает ненулевой код ошибки. Это неудобно.Но можно сделать почти идеально с plutil:
plutil -replace 'NSAppTransportSecurity' -json "{ ... }" -- Info.plist
Вот формат, который мы на самом деле хотим видеть в "настроечном" удалённом файле:
Остаётся только скачать его и обновить ключ в Info.plist используя plutil:
Чтобы очистить ключ:
Можно было бы делать и
plutil -remove 'NSAppTransportSecurity' -- Info.plist
, но plutil тоже будет возвращать код ошибки, если ключа не было, это может быть неудобно.Использование Command Line Tool на Swift в iOS проекте
У UserDefaults есть разные "domains", и это так называемый Argument Domain: https://developer.apple.com/documentation/foundation/userdefaults/1410665-argumentdomain
Использование Command Line Tool на Swift в iOS проекте
или же можно использовать UserDefaults: аргументы вида "-arg value" доступны из UserDefaults.standard
Использование Command Line Tool на Swift в iOS проекте
но зачем вообще что-то парсить, если есть тот же Environment с именованными параметрами?
Использование Command Line Tool на Swift в iOS проекте
... а так же NSPropertyListSerialization