С тех пор, как для прохождения модерации на Mac Store стала требоваться поддержка Sandbox, прошло уже 5 лет. Хотя возможности MacOS и Sandbox постепенно расширяются, разработчики, желающие публиковаться в официальном магазине Apple, по-прежнему ограничены в возможностях работы c этой ОС. Особенно остро эта проблема стоит для утилит и системных приложений.


Sandbox призван сделать macOS безопасней и уберечь пользователя от вредоносных и потенциально опасных приложений — все действия приложений, которые могут гипотетически принести вред, должны сопровождаться запросом к пользователю на предоставление доступа. В теории это разумная мера предосторожности, однако в действительности под санкции попадают многие приложения, которым пользовательские данные необходимы для осуществления базовых функций. В данной статье мы расскажем, как внедряли подобные функции в приложение, поддерживая совместимость с Sandbox, — возможно, этот опыт будет полезен для других разработчиков, работающих с официальным маркетом.

Мы столкнулись с подобной необходимостью при работе с утилитой для мониторинга системы MaCleaner X. Вся линейка продуктов MaCleaner распространяется исключительно через Mac App Store.

Отличительными особенностями MaCleaner X являются поддержка тачбара и кастомизированный дизайн: приложение автоматически определяет модель Mac и предлагает особый вариант интерфейса для каждого девайса. Однако в дополнение к этому мы в данный момент работаем над тем, чтобы реализовать в расширенной версии некоторые функции, которые пользователи хотели бы видеть в программе и которые для утилит, распространяемых вне Mac Store, считаются привычными.

В первую очередь это:

  • удаление приложений и всей связанной с ними информации;
  • ?удаление неиспользуемых служебных файлов из macOS (логи, локализации, temp);
  • получение системной информации о железе (например, о жестком диске);
  • мониторинг температуры и других показателей системы.

Способ решения

Для начала в общем виде наметим, по какой схеме будет происходить весь процесс:

  • Пользователь должен дать согласие на доступ для расширения функционала (фактически ему нужно будет осуществить несколько действий вне приложения);
  • Приложение всегда работает в песочнице, но в случае необходимости перед каждым действием, требующим администраторские права доступа, запрашивает у пользователя разрешение.

Для выполнения этой операции вне песочницы воспользуемся следующей возможностью macOS: приложения, которые поддерживают расширение своего функционала при помощи Apple Script, могут запускать эти скрипты вне песочницы, если они находятся в папке ~/Library/Application Scripts. Приложение, поддерживающее Sandbox, не может самостоятельно копировать в эту папку скрипты — это пресекается модераторами Apple.

Таким образом, последовательность действий у нас вырисовывается следующая:

  1. Реализация master-приложения, поддерживающего расширения при помощи Apple Script из папки Application Scripts.
  2. Прохождение модерации и публикация приложения в Mac Store
  3. Подготовка extension-приложения. В его задачи будет входить копирование нужных скриптов в общую папку c master-приложением. Для этого они должны иметь общую AppGroup, при помощи которой будет происходить обмен.
  4. Публикация extension-приложения на собственном сайте.

Пример

В качестве примера рассмотрим возможность получения информации о текущих носителях при помощи bash-команды system_profiler SPStorageDataType.

Подготовка Extension-приложения

Все, что требуется от этой компоненты — это копирование скрипта в определенную папку. Поэтому в его интерфейсе мы отобразим единственную возможность: включение или отключение отображения информации о носителях (чтобы не грузить пользователя подробностями, это может быть представлено как включение/отключение плагинов в master-приложении).

Apple Sript — bash

Содержимое файла extract_storage_info.scpt, который копируется ectention'ом:

do shell script "system_profiler SPStorageDataType"

Подготовка Master-приложения

Для корректной работы приложения с точки зрения пользователя необходимо осуществлять проверку доступности дополнительного функционала и скрывать соответствующие части UI:

- (BOOL) isStorageInfoAvailable {
   return [self _storageInfoScriptTask] != nil;
}
 
#pragma mark - Private
- (NSUserAppleScriptTask *)_storageInfoScriptTask {
   NSUserAppleScriptTask *result = nil;
   
   NSError *error;
   NSURL *directoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationScriptsDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
   if (directoryURL) {
       NSURL *scriptURL = [directoryURL URLByAppendingPathComponent: StorageInfoScriptFileName];
       result = [[NSUserAppleScriptTask alloc] initWithURL:scriptURL error:&error];
       if (error != nil) {
           NSLog(@"No AppleScript \"%@\" task error: %@", StorageInfoScriptFileName, error);
       }
   } else {
       // Возникнет в случае, если приложение запущено не в Sandbox
       NSLog(@"No Application Scripts folder error: %@", error);
   }
   
   return result;
}

Если скрипт доступен, его вызов осуществляется следующим образом:

NSUserAppleScriptTask * automationScriptTask = [self _storageInfoScriptTask];
if (automationScriptTask != nil) {
   [automationScriptTask executeWithAppleEvent:nil completionHandler:^(NSAppleEventDescriptor *resultEventDescriptor, NSError *error) {
       if (error != nil) {
           NSLog(@"AppleScript \"%@\" executing error: %@", StorageInfoScriptFileName, error);
           //TODO: Обработка ошибки в GUI
       }
       else {
           NSString* resultString = [resultEventDescriptor stringValue];
           NSLog(@"Result string:\n%@", resultString);
           // Парсинг и вывод информации
       }
   }];
} else {
   //TODO: Обработка ошибки в GUI
}
 

В нашем упрощенном примере, мы выводим полученную от выполненного скрипта строку как есть. Для стилизации под интерфейс приложения необходимо будет парсить результат и отображать только необходимую информацию.

Совместная работа приложений

В результате мы получаем следующую схему работы.

Master-приложение

  1. В случае недоступности дополнительного функционала скрывает элементы UI.
  2. В случае наличия расширяющих скриптов, Apple Script (по определенному формату) активирует UI и выполняет их.
  3. Если функции, которые выполняет Apple Script, требуют ввода данных администраторской учетной записи, пользователь увидит стандартный диалог авторизации — эту работу выполнит за нас Apple Script.

Extension-приложение

  1. Должно быть скачано пользователем с сайта разработчика. Мы говорим о специальном сайте, потому что необходимо разъяснить пользователю принцип функционирования системы и действия, которые ему необходимо осуществить для корректной работы. Отчасти это можно сделать через GUI extension-а.
  2. Несмотря на то, что работаем вне песочницы, мы предлагаем пользоваться общим AppGroup для компонент-решения.
  3. Этот компонент может быть представлен даже более широко — как Менеджер Расширений. Благодаря согласованному интерфейсу, master-приложение может расширяться по мере появления расширений в общей папке: они могут быть добавлены вместе с файлами описаний и конфигурациями.
  4. Копируемый Apple Script может даже содержать общую функцию task(bash_script), принимающую на вход произвольную bash-команду и выполняющую ее. Но этот подход открывает большую дыру в безопасности, а также обязует master-приложение содержать расширение перед публикацией в App Store.

Выводы

Плюсы подхода:

  • Расширение функционала приложения
  • Конфигурируемость расширения

Минусы:

  • Нерационально с точки зрения юзабилити. От пользователя требуется дополнительное действие — скачивание стороннего приложения.
  • Потенциальная дыра в безопасности и необходимость дополнительных проверок, согласованности форматов.

Данная статья предлагает решение проблемы с ограничениями функционала продукта, диктуемыми Sandbox, для разработчиков. В будущем мы расскажем вам о том, как ситуация выглядит со стороны пользователей. Если вы находили другие обходные пути, будем рады, если вы поделитесь ими в комментариях.
Поделиться с друзьями
-->

Комментарии (2)


  1. awoland
    16.03.2017 09:32

    Полагаю, что проще распространять и продавать приложение напрямую с сайта. Если это приложение действительно полезное и востребованное, потенциальные пользователи его найдут. Сам всегда (и принципиально) при наличии возможности выбора отдаю предпочтение версии, распространяющейся непосредственно и напрямую от разработчика, а не через AppStore. А вот функционал, который можно реализовать исключительно через AppStore (InApp P., iCloud Drive, Game Center и т.п.) имеет смысл реализовывать через дополнения (приложения-расширения) и распространять их через AppStore.
    Механизм подобных расширений использует сам Apple (расширения для последнего Xcode, например).


    1. EverydayTools
      16.03.2017 12:44

      Дело в том, что наша задача изначально заключалась именно в том, чтобы создать продукт, который распространяется через Mac Store, поэтому мы исходили из этих реалий.
      Наш опыт говорит о большей лояльности пользователей к продуктам, размещенным в официальном маркете, нежели распространяющихся через собственные сайты. Собственно, статья написана как раз для разработчиков, которые, как мы, работая с Mac Store ищут варианты расширения функционала своих продуктов, не отходя от идеи работы с официальным стором.