[Разработка под iOS, Xcode, Swift] Библиотека для работы с iOS-пермишенами, от идеи до релиза (часть 1)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет! Из этого мини-цикла статей ты узнаешь:
- Как унаследовать Swift-класс не целиком, а лишь то в нём, что тебе нужно?
- Как позволить юзеру твоей CocoaPods- или Carthage-библиотеки компилировать лишь те её части, что он действительно использует?
- Как раздербанить ресурсы iOS, чтобы достать оттуда конкретные системные иконки и локализованные строки?
- Как поддержать completion blocks даже там, где это не предусмотрено дефолтным API системных разрешений?
А вообще, здесь о том, как я попытался написать ультимативную библиотеку для работы с пермишенами в iOS — с какими неожиданностями столкнулся и какие неочевидные решения нашёл для некоторых проблем. Буду рад, если окажется интересно и полезно!Немного о самой либеОднажды мне пришла в голову следующая мысль — всем мобильным разработчикам то и дело приходится работать с системными разрешениями. Хочешь использовать камеру? Получи у юзера пермишен. Решил присылать ему уведомления? Получи разрешение.В каждой подобной ситуации идеальный разработчик ходит изучать документацию на сайте Apple, но чаще мы экономим время и просто гуглим готовое решение на Stack Overflow. Не сказал бы, что это всегда плохо, но так оказывается слишком легко упустить какой-нибудь важный нюанс.Быстрый поиск по GitHub показывает, что либы для работы с пермишенами уже давно существуют, причём их не одна и не две. Но какую ни возьми, везде одно и то же — либо перестала обновляться, либо что-то не поддерживает, либо документация на китайском.В итоге я решил написать собственную библиотеку. Попытаться сделать идеально. Буду благодарен, если напишете в комментариях, получилось ли. А вообще, PermissionWizard...
- Поддерживает новейшие фичи iOS 14 и macOS 11 Big Sur
- Отлично работает с Mac Catalyst
- Поддерживает все существующие типы системных разрешений
- Валидирует твой «Info.plist» и защищает от падений, если с ним что-то не так
- Поддерживает коллбэки даже там, где этого нет в дефолтном системном API
- Позволяет не париться о том, что ответ на запрос какого-нибудь разрешения вернётся в неведомом потоке, пока ты его ждёшь, например, в DispatchQueue.main
- Полностью написан на чистом Swift
- Обеспечивает унифицированное API вне зависимости от типа разрешения, с которым ты прямо сейчас работаешь
- Опционально включает нативные иконки и локализованные строки для твоего UI
- Модульный, подключай лишь те компоненты, что тебе нужны
Но перейдём наконец-то к действительно интересному...Как унаследовать класс не целиком, а лишь то в нём, что тебе нужно?Начав работать над PermissionWizard, я довольно быстро понял, что для большинства поддерживаемых типов разрешений мне нужны одни и те же элементы:
- Свойство usageDescriptionPlistKey
- Методы checkStatus и requestAccess
Было бы странно не унаследовать каждый класс, отвечающий за тот или иной тип пермишена, от универсального родительского класса, где всё это уже объявлено и частично имплементировано.Кроме того, я собирался задокументировать каждый метод, каждое свойство в библиотеке, а поскольку Swift и Xcode не позволяют переиспользовать комментарии к коду, подобное наследование убивает сразу двух зайцев — мне не нужно из класса в класс копировать одни и те же комментарии.Только вот оказалось всё сложнее, чем можно было ожидать:
- Некоторые типы пермишенов (например, дом и локальная сеть) не позволяют проверить текущий статус разрешения, не выполнив собственно запрос на доступ к нему, и унаследованное объявление checkStatus оказывается в таком случае неуместным. Оно лишь сбивает с толку — торчит в автоподстановке, хотя не имеет имплементации.
- Для работы с пермишеном геолокации не годится стандартное объявление requestAccess(completion:), поскольку для запроса на доступ необходимо определиться, нужен он нам всегда, или только когда юзер активно пользуется приложением. Здесь подходит requestAccess(whenInUseOnly:completion:), но тогда опять-таки выходит, что унаследованная перегрузка метода болтается не в тему.
- Пермишен на доступ к фотографиям использует сразу два разных plist-ключа — один на полный доступ (NSPhotoLibraryUsageDescription) и один, чтобы только добавлять новые фото и видео (NSPhotoLibraryAddUsageDescription). Видим, что опять-таки наследуемое свойство usageDescriptionPlistKey получается лишним — логичнее иметь два отдельных и с более говорящими названиями.
Я привёл лишь несколько примеров возникших проблем. Однако подобных исключений потребовали только некоторые типы пермишенов. Большинство, а всего их целых 18 штук, строится по одному и тому же неизменному скелету, отказываться от наследования которого совершенно не хочется.Решить подобную ситуацию можно по-разному. Например, раскидать все эти объявления свойств и методов по разным протоколам и в каждом отдельном случае наследовать лишь нужные. Но это громоздко и неудобно, в данном случае нашёлся способ изящнее — атрибут.
class SupportedType {
func requestAccess(completion: (Status) -> Void) { }
}
final class Bluetooth: SupportedType { ... }
final class Location: SupportedType {
@available(*, unavailable)
override func requestAccess(completion: (Status) -> Void) { }
func requestAccess(whenInUseOnly: Bool, completion: (Status) -> Void) { ... }
}
Переопределение метода, помеченное атрибутом @available(*, unavailable), не только делает его вызов невозможным, возвращая при сборке ошибку, но и полностью скрывает его из автоподстановки в Xcode, то есть фактически как будто исключает метод из наследования.Разумеется, я не открыл здесь никакой Америки, однако решение оказалось не слишком широко известным, поэтому решил им поделиться.Как позволить юзеру твоей CocoaPods- или Carthage-библиотеки компилировать лишь те её части, что он действительно использует?PermissionWizard поддерживает 18 видов системных разрешений — от фото и контактов до Siri и появившегося в iOS 14 трекинга. Это в свою очередь означает, что библиотека импортирует и использует AVKit, CoreBluetooth, CoreLocation, CoreMotion, EventKit, HealthKit, HomeKit и ещё много разных системных фреймворков.Нетрудно догадаться, что если ты подключишь такую либу к своему проекту целиком, пусть даже будешь использовать её исключительно для работы с каким-то одним пермишеном, Apple не пропустит твоё приложение в App Store, поскольку увидит, что оно использует подозрительно много разных API конфиденциальности. Да и собираться такой проект будет чуть дольше, а готовое приложение — весить чуть больше. Требуется какой-то выход.CocoaPodsВ случае с этим менеджером зависимостей решение найти относительно просто. Разбиваем библиотеку на независимые компоненты, позволяя выборочно устанавливать лишь те, что нужны разработчику. Заодно отделяем компонент с иконками и локализованными строками, поскольку нужны они далеко не всем.
pod 'PermissionWizard/Assets' # Icons and localized strings
pod 'PermissionWizard/Bluetooth'
pod 'PermissionWizard/Calendars'
pod 'PermissionWizard/Camera'
pod 'PermissionWizard/Contacts'
pod 'PermissionWizard/FaceID'
pod 'PermissionWizard/Health'
pod 'PermissionWizard/Home'
pod 'PermissionWizard/LocalNetwork'
pod 'PermissionWizard/Location'
pod 'PermissionWizard/Microphone'
pod 'PermissionWizard/Motion'
pod 'PermissionWizard/Music'
pod 'PermissionWizard/Notifications'
pod 'PermissionWizard/Photos'
pod 'PermissionWizard/Reminders'
pod 'PermissionWizard/Siri'
pod 'PermissionWizard/SpeechRecognition'
pod 'PermissionWizard/Tracking'
В свою очередь, «Podspec» нашей библиотеки (файл, описывающий её для CocoaPods) выглядит примерно следующим образом:
Pod::Spec.new do |spec|
...
spec.subspec 'Core' do |core|
core.source_files = 'Source/Permission.swift', 'Source/Framework'
end
spec.subspec 'Assets' do |assets|
assets.dependency 'PermissionWizard/Core'
assets.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'ASSETS' }
assets.resource_bundles = {
'Icons' => 'Source/Icons.xcassets',
'Localizations' => 'Source/Localizations/*.lproj'
}
end
spec.subspec 'Bluetooth' do |bluetooth|
bluetooth.dependency 'PermissionWizard/Core'
bluetooth.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'BLUETOOTH' }
bluetooth.source_files = 'Source/Supported Types/Bluetooth*.swift'
end
...
spec.default_subspec = 'Assets', 'Bluetooth', 'Calendars', 'Camera', 'Contacts', 'FaceID', 'Health', 'Home', 'LocalNetwork', 'Location', 'Microphone', 'Motion', 'Music', 'Notifications', 'Photos', 'Reminders', 'Siri', 'SpeechRecognition', 'Tracking'
end
Подключение каждого нового компонента не только добавляет в устанавливаемый дистрибутив библиотеки новые файлы с кодом, но и проставляет в настройках проекта флаги, на основе которых мы можем исключать из сборки те или иные участки кода.
#if BLUETOOTH
final class Bluetooth { ... }
#endif
CarthageЗдесь всё оказывается немного сложнее. Этот менеджер зависимостей не поддерживает дробление библиотек, если только не разделить их по-настоящему — на разные репозитории, к примеру. Выходит, нужен какой-то обходной путь.В корне нашей либы создаём файл «Settings.xcconfig» и пишем в нём следующее:
#include? "../../../../PermissionWizard.xcconfig"
По умолчанию Carthage устанавливает зависимости в директорию «Carthage/Build/iOS», так что вышеприведённая инструкция ссылается на некий файл «PermissionWizard.xcconfig», который может быть расположен юзером нашей библиотеки в корневой папке своего проекта.Очертим и его примерное содержимое:
ENABLED_FEATURES = ASSETS BLUETOOTH ...
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) $(ENABLED_FEATURES) CUSTOM_SETTINGS
Наконец, необходимо указать нашей либе, что она должна ссылаться на «Settings.xcconfig» как на дополнительный источник настроек для сборки. Чтобы это сделать, добавляем в проект библиотеки ссылку на указанный файл, а затем открываем «project.pbxproj» любым удобным текстовым редактором. Здесь ищем идентификатор, присвоенный только что добавленному в проект файлу, как на примере ниже.
A53DFF50255AAB8200995A85 /* Settings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Settings.xcconfig; sourceTree = "<group>"; };
Теперь для каждого имеющегося у нас блока «XCBuildConfiguration» добавляем строку с базовыми настройками по следующему образцу (строка 3):
B6DAF0412528D771002483A6 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A53DFF50255AAB8200995A85 /* Settings.xcconfig */;
buildSettings = {
...
};
name = Release;
};
Ты можешь спросить, зачем помимо флагов с нужными компонентами мы также проставляем некое CUSTOM_SETTINGS. Всё просто — в отсутствие этого флага мы считаем, что юзер библиотеки не попытался её настроить, то есть не создал «PermissionWizard.xcconfig» в корне своего проекта, и включаем сразу все поддерживаемые либой компоненты.
#if BLUETOOTH || !CUSTOM_SETTINGS
final class Bluetooth { ... }
#endif
На этом пока всёВ следующей части поговорим о том, как я среди 5 гигабайт прошивки iOS 14 нашёл нужные мне локализованные строки и как добыл иконки всех системных пермишенов. А ещё расскажу, как мне удалось запилить requestAccess(completion:) даже там, где дефолтное системное API разрешений не поддерживает коллбэки.Спасибо за внимание!
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Разработка под Android] Подменяем Runtime permissions в Android
- [Разработка под iOS, Разработка мобильных приложений] Адаптируем UITableView под MVVM
- [Работа с 3D-графикой, Разработка под AR и VR, AR и VR] Google закроет сервис публикаций 3D-моделей Poly
- [Информационная безопасность, Машинное обучение] Как предоставить табличные данные и сохранить при этом конфиденциальность (перевод)
- [Разработка под iOS, Смартфоны, Сотовая связь] Пользователи iPhone 12 жалуются, что у них пропадает связь
- [Программирование, Разработка под iOS, Objective C, Swift] Модуляризация iOS-приложения: зачем и как мы разбиваем Badoo на модули
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android] Как ВТБ помогает снизить комиссию за приём платежей до 0,4% с помощью QR-кода
- [Читальный зал, Научно-популярное, Будущее здесь] “Мир после капитала” Альберта Венгера (часть 5/7) (перевод)
- [Разработка под iOS, ООП] Зачем нужно понимать ООП
- [Разработка под iOS, Разработка мобильных приложений, Swift] Как мы стартовали Vivid Money для iOS
Теги для поиска: #_razrabotka_pod_ios (Разработка под iOS), #_xcode, #_swift, #_privatnost (приватность), #_konfidentsialnost (конфиденциальность), #_razreshenija (разрешения), #_permissions, #_biblioteki (библиотеки), #_nasledovanie (наследование), #_atributy (атрибуты), #_cocoapods, #_carthage, #_info.plist, #_razrabotka_pod_ios (
Разработка под iOS
), #_xcode, #_swift
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:10
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет! Из этого мини-цикла статей ты узнаешь:
А вообще, здесь о том, как я попытался написать ультимативную библиотеку для работы с пермишенами в iOS — с какими неожиданностями столкнулся и какие неочевидные решения нашёл для некоторых проблем. Буду рад, если окажется интересно и полезно!Немного о самой либеОднажды мне пришла в голову следующая мысль — всем мобильным разработчикам то и дело приходится работать с системными разрешениями. Хочешь использовать камеру? Получи у юзера пермишен. Решил присылать ему уведомления? Получи разрешение.В каждой подобной ситуации идеальный разработчик ходит изучать документацию на сайте Apple, но чаще мы экономим время и просто гуглим готовое решение на Stack Overflow. Не сказал бы, что это всегда плохо, но так оказывается слишком легко упустить какой-нибудь важный нюанс.Быстрый поиск по GitHub показывает, что либы для работы с пермишенами уже давно существуют, причём их не одна и не две. Но какую ни возьми, везде одно и то же — либо перестала обновляться, либо что-то не поддерживает, либо документация на китайском.В итоге я решил написать собственную библиотеку. Попытаться сделать идеально. Буду благодарен, если напишете в комментариях, получилось ли. А вообще, PermissionWizard...
class SupportedType {
func requestAccess(completion: (Status) -> Void) { } } final class Bluetooth: SupportedType { ... } final class Location: SupportedType { @available(*, unavailable) override func requestAccess(completion: (Status) -> Void) { } func requestAccess(whenInUseOnly: Bool, completion: (Status) -> Void) { ... } } pod 'PermissionWizard/Assets' # Icons and localized strings
pod 'PermissionWizard/Bluetooth' pod 'PermissionWizard/Calendars' pod 'PermissionWizard/Camera' pod 'PermissionWizard/Contacts' pod 'PermissionWizard/FaceID' pod 'PermissionWizard/Health' pod 'PermissionWizard/Home' pod 'PermissionWizard/LocalNetwork' pod 'PermissionWizard/Location' pod 'PermissionWizard/Microphone' pod 'PermissionWizard/Motion' pod 'PermissionWizard/Music' pod 'PermissionWizard/Notifications' pod 'PermissionWizard/Photos' pod 'PermissionWizard/Reminders' pod 'PermissionWizard/Siri' pod 'PermissionWizard/SpeechRecognition' pod 'PermissionWizard/Tracking' Pod::Spec.new do |spec|
... spec.subspec 'Core' do |core| core.source_files = 'Source/Permission.swift', 'Source/Framework' end spec.subspec 'Assets' do |assets| assets.dependency 'PermissionWizard/Core' assets.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'ASSETS' } assets.resource_bundles = { 'Icons' => 'Source/Icons.xcassets', 'Localizations' => 'Source/Localizations/*.lproj' } end spec.subspec 'Bluetooth' do |bluetooth| bluetooth.dependency 'PermissionWizard/Core' bluetooth.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'BLUETOOTH' } bluetooth.source_files = 'Source/Supported Types/Bluetooth*.swift' end ... spec.default_subspec = 'Assets', 'Bluetooth', 'Calendars', 'Camera', 'Contacts', 'FaceID', 'Health', 'Home', 'LocalNetwork', 'Location', 'Microphone', 'Motion', 'Music', 'Notifications', 'Photos', 'Reminders', 'Siri', 'SpeechRecognition', 'Tracking' end #if BLUETOOTH
final class Bluetooth { ... } #endif #include? "../../../../PermissionWizard.xcconfig"
ENABLED_FEATURES = ASSETS BLUETOOTH ...
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) $(ENABLED_FEATURES) CUSTOM_SETTINGS A53DFF50255AAB8200995A85 /* Settings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Settings.xcconfig; sourceTree = "<group>"; };
B6DAF0412528D771002483A6 /* Release */ = {
isa = XCBuildConfiguration; baseConfigurationReference = A53DFF50255AAB8200995A85 /* Settings.xcconfig */; buildSettings = { ... }; name = Release; }; #if BLUETOOTH || !CUSTOM_SETTINGS
final class Bluetooth { ... } #endif =========== Источник: habr.com =========== Похожие новости:
Разработка под iOS ), #_xcode, #_swift |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:10
Часовой пояс: UTC + 5