iOS. Приемы программирования Нахавандипур Вандад
Выйти из таймера не составляет труда — можно просто вызвать метод экземпляра invalidate, относящийся к таймеру. После вызова этого метода таймер больше не будет инициировать никаких событий в своем целевом объекте.
А вот выходить из потоков немного сложнее. Когда поток находится в спящем режиме и вызывается его метод cancel, рабочий цикл этого потока выполнит свою задачу, а только потом осуществит выход. Рассмотрим это:
— (void) threadEntryPoint{
@autoreleasepool {
NSLog(@"Thread Entry Point");
while ([[NSThread currentThread] isCancelled] == NO){
[NSThread sleepForTimeInterval:4];
NSLog(@"Thread Loop");
}
NSLog(@"Thread Finished");
}
}
— (void) stopThread{
NSLog(@"Cancelling the Thread");
[self.myThread cancel];
NSLog(@"Releasing the thread");
self.myThread = nil;
}
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.myThread = [[NSThread alloc]
initWithTarget: self
selector:@selector(threadEntryPoint)
object: nil];
[self performSelector:@selector(stopThread)
withObject: nil
afterDelay:3.0f];
[self.myThread start];
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Данный код создает экземпляр класса NSThread и немедленно запускает поток. Поток в каждом цикле проводит 4 секунды в спящем режиме, а потом переходит к выполнению своей задачи. Тем не менее, прежде чем поток будет запущен, мы вызываем метод stopThread, относящийся к (написанному нами) контроллеру вида; это делается с трехсекундной задержкой. Данный метод вызывает метод cancel, относящийся к потоку, пытаясь заставить поток выйти из своего цикла. Теперь запустим приложение и посмотрим, что выводится в окне консоли:
…
Thread Entry Point
Cancelling the Thread
Releasing the thread
Thread Loop
Thread Finished
Итак, ясно видно, что перед выходом поток завершил текущий цикл, хотя запрос о выходе и был дан в середине цикла. Это очень распространенная ловушка. Чтобы избежать ее, нужно сначала проверять, не отменен ли поток, и лишь потом переходить к выполнению какой-либо задачи, для которой свойственны внешние побочные эффекты в цикле потока. Мы можем переписать код следующим образом. При этом операция с внешним эффектом (записыванием в регистрационный журнал) сначала проверяет, не отменен ли поток:
— (void) threadEntryPoint{
@autoreleasepool {
NSLog(@"Thread Entry Point");
while ([[NSThread currentThread] isCancelled] == NO){
[NSThread sleepForTimeInterval:4];
if ([[NSThread currentThread] isCancelled] == NO){
NSLog(@"Thread Loop");
}
}
NSLog(@"Thread Finished");
}
}
— (void) stopThread{
NSLog(@"Cancelling the Thread");
[self.myThread cancel];
NSLog(@"Releasing the thread");
self.myThread = nil;
}
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.myThread = [[NSThread alloc]
initWithTarget: self
selector:@selector(threadEntryPoint)
object: nil];
[self performSelector:@selector(stopThread)
withObject: nil
afterDelay:3.0f];
[self.myThread start];
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Глава 8. Безопасность
8.0. Введение
Безопасность — центральный элемент операционных систем iOS и OS X. Можно пользоваться функциями безопасности в iOS, чтобы без опасений хранить файлы на различных накопителях. Например, можно приказать iOS заблокировать и защитить на диске файлы с информацией из вашего приложения, если пользователь активизировал на устройстве защиту с применением пароля, а устройство перешло в режим блокировки. Если вы явно этого не затребуете, iOS не будет использовать с вашим приложением какого-либо защищенного хранилища данных. В таком случае ваши данные будут открыты для считывания любому процессу, имеющему доступ на считывание файловой системы вашего устройства. Существуют разнообразные приложения Mac, способные исследовать файловую систему устройства с iOS, не вызывая при этом джейлбрейка.
Джейлбрейк — это процесс предоставления доступа с правами администратора и снятия многих уровней защиты, действующих над операционной системой — например, над iOS. В частности, если устройство подверглось джейлбрейку, приложение может выполнить на нем неподписанный двоичный код. Но на обычном устройстве iOS приложение сможет быть выполнено на устройстве, лишь если оно имеет подпись Apple, полученную через App Store или верифицированный портал для разработки под iOS.
В Mac OS X Apple уже давно используется программа Keychain Access. Это инструмент, обеспечивающий пользователям iOS безопасное хранение данных на компьютере. Разработчики могут пользоваться программой Keychain Access, а также другими функциями обеспечения безопасности, выстроенными на базе общей архитектуры защиты данных (CDSA). Keychain Access может управлять различными связками ключей. Каждая связка ключей, в свою очередь, может содержать защищенные данные, например пароли. Если на машине с операционной системой OS X вы заходите под своим именем на сайт через браузер Safari, то вам будут предложены две возможности: приказать Safari запомнить ваш пароль либо отклонить этот запрос. Если вы прикажете Safari запомнить пароль, то браузер сохранит указанный пароль в заданной по умолчанию связке ключей.
Связки ключей в OS X и iOS различаются во многих отношениях. В OS X:
• у пользователя может быть много связок ключей, в iOS же действует всего одна глобальная связка ключей;
• пользователь может блокировать связку ключей. В iOS та связка ключей, которая используется по умолчанию, блокируется и разблокируется вместе с самим устройством;
• существует концепция стандартной связки ключей (default keychain), которая автоматически разблокируется операционной системой, когда пользователь входит в эту систему. Дело в том, что пароль стандартной связки ключей совпадает с паролем к пользовательской учетной записи. Как было указано ранее, в iOS есть всего одна связка ключей и она разблокируется iOS по умолчанию.
Чтобы помочь вам лучше понять природу связки ключей в OS X, прежде чем углубиться в изучение концепций iOS, связанных со связкой ключей и безопасностью, я хотел бы привести один пример. Откройте окно терминала на компьютере с Mac, введите следующую команду и нажмите Enter:
security list-keychains
В зависимости от настроек машины и имени пользователя вы получите примерно такой вывод:
«/Users/vandadnp/Library/Keychains/login.keychain»
«/Library/Keychains/System.keychain»
Как видите, здесь у меня две связки ключей: регистрационная и системная. Чтобы выяснить, какая связка ключей используется по умолчанию, введите следующую команду в окно терминала, а затем нажмите Enter:
security default-keychain
В типичной установке OS X эта команда вернет примерно такой результат:
«/Users/vandadnp/Library/Keychains/login.keychain»
Вывод свидетельствует, что у меня на машине по умолчанию используется регистрационная связка ключей. Поэтому по умолчанию все пароли, которые я приказываю запомнить различным программам в системе OS X, будут сохраняться именно в этой связке ключей, если конкретное приложение не решит, что пароль должен быть сохранен в другой связке ключей. Если нужной связки ключей пока не существует, приложению потребуется ее создать.
А теперь давайте попробуем кое-что интересное. Попытаемся узнать, какие пароли уже сохранены в нашей стандартной связке ключей. При этом будем исходить из того, что по умолчанию на машине используется связка ключей login.keychain, как мы определили ранее. Введите в окне терминала следующую команду и нажмите Enter:
security dump-keychain login.keychain | grep «password» — i
Аргумент dump-keychain для команды security в окне терминала дампирует все содержимое связки ключей в стандартный вывод. Для поиска паролей мы применили команду grep. Вывод этой команды может получиться примерно таким, как в следующем примере, в зависимости от того, какие пароли запомнил ваш компьютер:
«desc»<blob>="AirPort network password"
«desc»<blob>="AirPort network password"
«desc»<blob>="AirPort network password"
«desc»<blob>="AirPort network password"
«desc»<blob>="AirPort network password"
«desc»<blob>="AirPort network password"
«desc»<blob>="AirPort network password"
«desc»<blob>="AirPort network password"
«desc»<blob>="Web form password"
«desc»<blob>="Web form password"
«desc»<blob>="Web form password"
«desc»<blob>="Web form password"
Хорошо, все это очень интересно, но зачем я об этом рассказываю, как все это связано с iOS? Оказывается, что в архитектурном отношении связка ключей в iOS очень похожа на связку ключей в OS X, так как операционная система iOS создавалась на основе исходного кода OS X. Многие концепции iOS похожи на соответствующие элементы OS X, это касается и связки ключей. Необходимо отметить ряд очень важных аспектов, связанных со связками ключей в iOS, — в частности, поговорить о группах доступа и сервисах. Чтобы упростить вам изучение этой темы, я объясню, как эти концепции реализуются в OS X, а потом мы подробнее рассмотрим вариант связки ключей, применяемый в iOS.
На компьютере Mac нажмите клавиши Command + Пробел или просто нажмите значок Spotlight (Поиск) на верхней панели меню на экране (рис. 8.1).
Рис. 8.1. Нажмите значок Spotlight на панели меню для OS X
Когда откроется строка поиска, введите в нее запрос Keychain Access и нажмите Enter, чтобы открыть программу Keychain Access. В левой части окна этой программы находится область Keychains (Связки ключей). В ней выберите регистрационную связку ключей login, а затем в области Category (Категория) слева — запись Passwords (Пароли). Теперь перед вами должен открыться примерно такой интерфейс, как на рис. 8.2.
Рис. 8.2. Программа Keychain Access в Mac OS X
Keychain Access — это программа с графическим пользовательским интерфейсом, которая выстроена в OS X на базе интерфейсов API для работы со связкой ключей и обеспечения безопасности. Этот красивый и удобный интерфейс скрывает большую часть тех сложностей, которыми отличаются фреймворки безопасности в OS X. Теперь, если в системе есть какие-либо пароли, сохраненные в приложениях, например в Safari, вам потребуется дважды щелкнуть на одной из записей-паролей в правой части экрана. Откроется такое диалоговое окно, как на рис. 8.3.
Рис. 8.3. Диалоговое окно Keychain Access, в котором отображается информация о сохраненном пароле
Познакомимся с некоторыми свойствами пароля, показанного на рис. 8.3.
• Name (Имя) — имя пароля, присвоенное ему приложением, сохранившим этот пароль. Так, в данном случае мы имеем дело с паролем для входа в беспроводную сеть 206-NET. Это имя иногда называют подписью.
• Kind (Вид) — вид элемента. В данном случае пароль относится к виду «пароль для беспроводных сетей». Это обычная строка, ее можно использовать для выполнения запросов к связке ключей (рассмотрено далее).
• Account (Учетная запись) — обычно это ключ для того значения, которое мы хотим сохранить. Связка ключей использует хранилище для пар «ключ/значение», так же как словарь из языка Objective-C. Ключ — это произвольная строка, и большинство приложений, сохраняющих элементы в связке ключей, хранят ключи и значения именно в этой секции.
• Where (местоположение) — часто именуется сервисом. Это идентификатор сервиса, сохранившего данный элемент в связке ключей. Этот идентификатор — как раз тот пароль, который вы запоминаете, и связка ключей фактически не имеет с ним дел, так как вы считаете этот пароль осмысленным. В iOS принято задавать в качестве имени этого сервиса идентификатор пакета нашего приложения. Так мы отличаем данные, сохраненные в конкретном приложении, от сохраненных данных из других приложений. Подробнее поговорим об этом в дальнейшем.
Кроме того, вы видите на рис. 8.3 флажок Show Password (Показать пароль). Если установить этот флажок, система будет запрашивать разрешение на показ пароля к конкретному элементу. Если вы введете свой пароль и дадите такое разрешение, то программа Keychain Access получит для вас защищенный пароль и отобразит его на экране.
Мы можем использовать команду security в окне терминала для выборки ровно той же информации. Если ввести в окне терминала следующую команду:
security find-generic-password — help
то получится примерно такой вывод:
Usage: find-generic-password [-a account] [-s service]
[options…] [-g] [keychain…]
— a Match «account» string
— c Match «creator» (four-character code)
— C Match «type» (four-character code)
— D Match «kind» string
— G Match «value» string (generic attribute)
— j Match «comment» string
— l Match «label» string
— s Match «service» string
— g Display the password for the item found
— w Display only the password on stdout
If no keychains are specified to search, the default search list is used.
Find a generic password item.
Итак, если передавать команде security требуемые параметры один за другим, то можно получить свойства интересующего вас пароля (см. рис. 8.3):
security find-generic-password
— a «AirPort»
— s «com.apple.network.wlan.ssid.206-NET»
— D «AirPort network password»
— l «206-NET»
— g login.keychain
Как было показано ранее, команда — g запросит команду security отобразить пароль, ассоциированный с указанным элементом (при наличии такого пароля). Следовательно, после ввода этой команды в окно терминала вам будет предложено ввести пароль к вашей учетной записи перед продолжением работы. Аналогичным образом мы указывали пароль к учетной записи, чтобы отобразить пароль на рис. 8.3.
В iOS, притом что во всей операционной системе применяется единая глобальная область действия связки ключей, приложение все равно может считывать и записывать информацию лишь на небольшом экранированном участке связки ключей (по принципу работы в песочнице). Два приложения, написанные одним и тем же разработчиком (и подписанные одинаковым профилем инициализации с одного и того же портала для разработки в iOS), могут получать доступ к общей разделяемой области связки ключей, но при этом сохраняют и собственный ограниченный доступ каждый к своей связке ключей. Итак, два приложения, App X и App Y, написанные одним и тем же iOS-разработчиком, могут получить доступ к следующим областям связки ключей.
• App X — к области связки ключей приложения App X.
• App Y — к области связки ключей приложения App Y.
• Приложения App X и App Y обладают доступом к общему разделяемому участку связки ключей (с применением групп доступа, если программист соответствующим образом сконфигурирует разрешения для этого приложения).
• App X не может считывать информацию из связки ключей приложения App Y, а приложение App Y не может считывать аналогичные данные приложения App X.
Операционная система iOS просматривает разрешения приложения, чтобы определить требуемый тип доступа. Разрешения для конкретной программы кодируются в профиле инициализации, который использовался при подписывании приложения. Допустим, мы создали новый профиль инициализации под названием KeychainTest_Dev.mobileprovision. Этот профиль был размещен на Рабочем столе. С помощью следующей команды можно извлечь разрешения, соответствующие данному профилю, вот так:
cd ~/Desktop
Эта команда выведет вас на Рабочий стол. Уже здесь нужно выполнить следующую команду, чтобы считать разрешения из профиля инициализации:
security cms — D -i KeychainTest_Dev.mobileprovision | grep — A12 «Enh2ments»
Показанная здесь команда security декодирует весь профиль инициализации, после чего команда grep просмотрит раздел Enh2ments (Разрешения) в профиле и считает 12 строк текста от начала этого раздела. Если в вашем разделе с разрешениями содержится больше или меньше текста, то нужно соответствующим образом изменить аргумент — A12.
Вывод этой команды будет выглядеть примерно следующим образом в зависимости от вашего профиля:
<key>Enh2ments</key>
<dict>
<key>application-identifier</key>
<string>F3FU372W5M.com.pixolity.ios.cookbook.KeychainTest</string>
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>F3FU372W5M.*</string>
</array>
</dict>
Самый важный раздел, который нас здесь интересует, называется keychain-access-groups. Здесь указываются группы доступа к нашим элементам из связки ключей. Это групповой идентификатор для всех приложений, разработанных одним и тем же программистом. Здесь F3FU372W5M — это мой командный идентификатор с портала разработки для iOS. Идущий далее астериск показывает, в какие группы доступа я могу позже поместить те элементы, которые требуется хранить в защищенном режиме. В данном случае астериск означает любую группу. Поэтому по умолчанию это приложение получает доступ к элементам связок ключей любого приложения, которое разработано членами вышеупомянутой команды. Не волнуйтесь, если пока не совсем улавливаете суть. В этой главе вы подробно изучите тему безопасности в iOS, в частности, к концу главы будете знать все необходимое об использовании связок ключей в iOS.
Совершенно необходимо добавить к вашему приложению фреймворк Security (Безопасность), перед тем как переходить к изучению разделов этой главы. Большинство приемов, описанных в данной главе, работает со службами связки ключей в iOS, а для этого необходимо наличие фреймворка Security. В iOS SDK 7 появилась концепция модулей. Поэтому если вы просто импортируете обобщающий заголовок фреймворка безопасности в ваш проект, LLVM сам свяжет ваше приложение с нужным модулем безопасности — делать это вручную не придется. Вам всего лишь понадобится гарантировать, что в настройках сборки активизирована функция Enable Modules (Использовать модули), а также импортировать в проект следующий заголовок:
#import <Security/Security.h>
В Xcode 5 также появилась поддержка «возможностей» (Capabilities). Это новая вкладка, расположенная рядом с вкладкой Build Settings (Настройки сборки). Здесь вы можете с легкостью добавлять разрешения к вашему приложению и даже активизировать связку ключей без особых хлопот. Тем не менее, работая таким образом, вы теряете доступ практически ко всем деталям и не можете создавать собственные профили инициализации. В вашем распоряжении будут только джокерные профили инициализации, которыми мы обычно не пользуемся, реализуя в приложениях пуш-уведомления и другие возможности. Рекомендую хотя бы познакомиться с этой новой вкладкой. Просто щелкните на файле с вашим проектом в Xcode, посмотрите на правую часть экрана и выберите Capabilities (Возможности). Затем можно будет отключить компоненты iCloud и Keychain Access.
8.1. Обеспечение безопасности и защиты в приложениях
Постановка задачи
Требуется сохранять значения в связке ключей и обеспечить в приложении безопасное хранение данных.
Решение
Создайте в приложении профиль инициализации, в котором активизирована защита файлов.
Обсуждение
Профили инициализации, уже упоминавшиеся ранее (см. раздел 8.0), содержат разрешения. Разрешения указывают системе iOS, как ваше приложение использует возможности безопасности, предоставляемые операционной системой. В эмуляторе iOS используется код без подписи (отсутствует этап, называемый code signing), следовательно, такие концепции будут лишены смысла. Но при отладке приложения или отправке его в App Store вы должны будете гарантировать, что приложение подписано корректным профилем инициализации одновременно для схем Debug (Отладка) и Release (Выпуск).
Я продемонстрирую все шаги, необходимые для создания действительного профиля реализации при разработке, а также опишу профили Ad Hoc и App Store. Выполните следующие шаги, чтобы создать валидный профиль инициализации для целей разработки (с поддержкой отладки). Он будет использоваться с приложениями, над которыми мы будем работать в данной главе. Начнем с создания ID приложения.
Предполагается, что вы уже создали для себя валидные сертификаты на разработку и распространение приложения.
1. Перейдите в iOS Dev Center и войдите в систему под своими именем и паролем.
2. Найдите раздел iOS Development Program (Программа разработки для iOS) и выберите Certificates, Identifiers & Profiles (Сертификаты, идентификаторы и профили).
3. Найдите в левой части экрана область App IDs (Идентификаторы приложений) и нажмите кнопку +, чтобы создать новый такой идентификатор.
4. В области Name (Имя) введите имя Security App. На самом деле здесь можно указать любое имя, но, чтобы избежать путаницы в этой главе, лучше воспользоваться данным именем, которое я буду применять в примерах.
5. В области App Services (Сервисы приложения) установите флажок Data Protection (Защита данных) и убедитесь, что выбран параметр Complete Protection (Полная защита). Остальные настройки оставьте без изменений.
6. Убедитесь, что в области App ID Suffix (Суффикс идентификатора приложения) установлен флажок Explicit App ID (Явный идентификатор приложения), и введите в поле Bundle ID (Идентификатор пакета) имя сервиса с точкой в качестве разделителя. Рекомендую имя com.NAME.ios.cookbook.SecurityApp, где NAME — название вашей компании. Если у вас нет компании с названием, просто придумайте его! В примерах я использую название com.pixolity.ios.cookbook.SecurityApp, но вам потребуется уникальное имя, это вы не сможете использовать.
7. Выполнив эти шаги, нажмите кнопку Continue (Продолжить).
8. Теперь система запросит у вас подтверждения заданных настроек, прежде чем будет создан идентификатор приложения. Вы увидите картинку, примерно как на рис. 8.4.
Рис. 8.4. Подтверждение настроек App ID перед созданием идентификатора приложения
9. Когда закончите подготовку настроек, нажмите кнопку Submit (Отправить), чтобы создать ID приложения.
Прекрасно! Теперь у нас есть идентификатор приложения, но еще требуется создать профили инициализации. Я подробно расскажу, как создается профиль инициализации для разработки, а создание профилей Ad Hoc и App Store оставляю вам в качестве самостоятельной работы, так как процесс практически идентичен. Выполните следующие шаги, чтобы создать профиль инициализации для целей разработки.
1. В области Certificates, Identifiers & Profiles (Сертификаты, идентификаторы и профили) портала разработки выберите область Development (Разработка) в категории Provisioning Profiles (Профили инициализации). Затем нажмите кнопку +.
2. В открывшемся окне в области Development (Разработка) установите флажок iOS App Development (Разработка приложения для iOS) и нажмите кнопку Continue (Продолжить).
3. Когда система потребует выбрать идентификатор приложения (App ID), выберите тот App ID, который вы создали ранее. В моем случае это будет App ID, показанный на рис. 8.5. Сделав выбор, нажмите кнопку Continue (Продолжить).
Рис. 8.5. Выбор нового идентификатора приложения для нового профиля инициализации (для целей разработки)