Ты перетащил программу в Корзину, очистил Корзину - и вроде все. А потом замечаешь: в Системных настройках в автозагрузке висит призрак удаленного приложения, в ~/Library на десятки гигов хвостов, а при переустановке программа подхватывает старую лицензию и старые настройки, будто никуда не девалась. Это не баг. Это нормальная механика macOS, которую почти никто не объясняет.
В этом уроке разберем жизненный цикл приложения целиком: что такое .app-бандл и почему он самодостаточен, где реально лежат данные программы, чем sandboxed-приложение из App Store отличается от обычного, как удалить программу на mac полностью (а не оставить хвосты), как работает автозагрузка mac и login items в Tahoe, как принудительно убить зависший процесс и как переназначить дефолтное приложение для типа файла. Все на реальных командах с разбором вывода.

Анатомия .app и где на самом деле лежат данные
Приложение в macOS - это не один исполняемый файл, а каталог-бандл с расширением .app, который Finder показывает как единый объект. Внутри лежит исполняемый код, ресурсы и манифест Info.plist. Заглянуть внутрь можно так:
Код: Выделить всё
$ ls -la /Applications/Safari.app/Contents
total 24
drwxr-xr-x ... Frameworks
drwxr-xr-x ... MacOS
-rw-r--r-- ... Info.plist
drwxr-xr-x ... Resources
drwxr-xr-x ... _CodeSignature
$ /usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \
/Applications/Safari.app/Contents/Info.plist
com.apple.Safari
Для обычного (не sandboxed) приложения данные раскиданы по нескольким местам:
- ~/Library/Preferences - настройки в виде plist-файлов вида com.vendor.app.plist. Это и есть application support mac в смысле конфигурации.
- ~/Library/Application Support/<имя> - основное хранилище: базы, профили, плагины, иногда гигабайты кэшей моделей и проектов.
- ~/Library/Caches/<bundle id> - кэши, которые система может чистить сама, но обычно не торопится.
- ~/Library/Logs, ~/Library/Saved Application State - логи и сохраненное состояние окон.
- ~/Library/LaunchAgents - сюда программа может положить .plist для автозапуска (про это ниже).
Код: Выделить всё
open ~/LibraryКод: Выделить всё
chflags nohidden ~/LibraryApp Sandbox и контейнеры: почему данные App Store-приложений лежат иначе
Здесь начинается самое интересное и самое непонятное. Почти все, что ты ставишь из App Store, работает в App Sandbox - песочнице. Я сверился с документацией Apple (developer.apple.com, "Protecting local app data using containers"): такому приложению система запрещает писать куда попало. Вместо разбросанных по ~/Library файлов оно получает свой контейнер.
При первом запуске sandboxed-приложения macOS создает каталог по пути ~/Library/Containers/<bundle id>/, и внутри него подкаталог Data. Этот Data - подмена домашней папки для песочницы: внутри него своя Library/Preferences, свои Caches, свои Application Support. Программа физически не видит чужие контейнеры и не лезет в твой настоящий ~/Library без явного разрешения.
Код: Выделить всё
$ ls -1 ~/Library/Containers | head
com.apple.Safari
com.apple.mail
com.apple.Notes
com.tinyspeck.slackmacgap
$ ls -la ~/Library/Containers/com.apple.Notes/Data/Library/Preferences
-rw------- ... com.apple.Notes.plist
Как отличить sandboxed-приложение от обычного, не гадая по происхождению? Через права (entitlements) в подписи кода:
Код: Выделить всё
$ codesign -d --entitlements - /Applications/Notes.app 2>/dev/null \
| grep app-sandbox
[key] com.apple.security.app-sandbox
[value][true/]
Чистое удаление: как убрать программу на mac полностью
Launchpad в Tahoe заменен на новое Приложения (в Spotlight и Launchpad-режиме), но логика та же: значок с крестиком и удаление работают только для приложений из App Store - они удаляются через систему и тянут за собой контейнер. Обычные программы, перетащенные в /Applications вручную, так не удалить - им крестик не положен, только Корзина.
Когда ты тащишь .app в Корзину, ты удаляешь код, но не хвосты. Чтобы удалить программу mac начисто, есть два пути.
Путь 1 - утилита. AppCleaner (бесплатный) при перетаскивании в него .app сканирует ~/Library и находит связанные файлы по bundle ID и имени, показывает список, удаляет пачкой. Ставится и через Homebrew:
Код: Выделить всё
$ brew install --cask appcleaner
Код: Выделить всё
# 1. Закрыть приложение и убедиться, что процессов нет
$ pkill -x ExampleApp
# 2. Удалить сам бандл
$ rm -rf /Applications/ExampleApp.app
# 3. Найти хвосты по bundle ID и имени (НЕ удаляя вслепую)
$ find ~/Library -maxdepth 3 -iname "*example*" 2>/dev/null
/Users/you/Library/Preferences/com.example.app.plist
/Users/you/Library/Application Support/ExampleApp
/Users/you/Library/Caches/com.example.app
/Users/you/Library/Containers/com.example.app
/Users/you/Library/Saved Application State/com.example.app.savedState
- ~/Library/Preferences/com.example.app.plist
- ~/Library/Application Support/ExampleApp
- ~/Library/Caches/com.example.app
- ~/Library/Containers/com.example.app (если sandboxed)
- ~/Library/Logs/ExampleApp
- ~/Library/LaunchAgents/com.example.app.plist (автозапуск - чистить обязательно)
Код: Выделить всё
$ defaults read com.example.app # весь домен настроек
$ defaults read com.example.app SomeKey # одно значение
$ defaults delete com.example.app # снести весь домен
Код: Выделить всё
defaults deleteАвтозагрузка mac: login items, фоновые задачи и BTM
Автозапуск приложений на mac - это не одно место, а целая система, и в современных macOS ею рулит Background Task Management (BTM). Я сверился с гайдами Apple (support.apple.com/guide/deployment): в System Settings (Системные настройки) пункт "Основные" -> "Объекты входа и расширения" (Login Items & Extensions) показывает и приложения, запускаемые при входе, и фоновую активность приложений.
Что важно понимать про login items:
- С macOS 13 и далее в Tahoe приложение может зарегистрировать себя в автозапуске само, через системный API. Поэтому в списке появляются вещи, которые ты руками не добавлял.
- Когда программа впервые ставит свой фоновый агент, система показывает уведомление "В фоне теперь работает <приложение>". Это и есть BTM в действии.
- Удаление приложения не всегда убирает его запись из BTM сразу - отсюда призраки в автозагрузке.
- ~/Library/LaunchAgents - агенты текущего пользователя
- /Library/LaunchAgents и /Library/LaunchDaemons - общесистемные (нужен админ)
- /System/Library/... - системные, не трогаем, защищены SIP
Код: Выделить всё
$ launchctl list | grep -i example
- 0 com.example.app.helper
Код: Выделить всё
$ launchctl bootout gui/$(id -u)/com.example.app.helper
$ rm ~/Library/LaunchAgents/com.example.app.helper.plist
Код: Выделить всё
$ sfltool resetbtm
Принудительное завершение и переназначение дефолтных приложений
Зависло намертво. Самый быстрый путь - Cmd+Option+Esc, откроется окно "Принудительное завершение". Из терминала точнее:
Код: Выделить всё
$ killall "Google Chrome" # по точному имени процесса
$ pkill -f Helper # по подстроке в командной строке (осторожно)
$ ps aux | grep -i chrome # сначала найти, что именно убивать
$ kill -9 <PID> # крайняя мера по конкретному PID
Открыть "по умолчанию". Какое приложение открывает данный тип файла, решает база LaunchServices, а тип определяется через UTI (Uniform Type Identifier - строка вроде public.html). Через Finder это меню "Свойства" -> "Открывать в программе" -> "Настроить...". Из CLI удобнее duti (ставится из Homebrew):
Код: Выделить всё
$ brew install duti
# узнать текущий обработчик для .jpg
$ duti -x jpg
Preview.app
/System/Applications/Preview.app
com.apple.Preview
# назначить VLC обработчиком для всех ролей mkv
$ duti -s org.videolan.vlc public.mkv all
Код: Выделить всё
$ /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/\
LaunchServices.framework/Support/lsregister -kill -r -domain local -domain user
Типичные грабли
- Удалил .app - думал, что все. Хвосты в ~/Library остаются всегда. Для обычных программ - проверяй find по bundle ID; для App Store - сноси контейнер.
- Правил plist руками - изменения не применились или откатились. cfprefsd кэширует. Используй defaults read/write/delete, а не текстовый редактор по файлу.
- Призрак в автозагрузке после удаления. Запись осталась в BTM. Сначала Login Items в настройках и plist в LaunchAgents, в крайнем случае sfltool resetbtm + перезагрузка.
- kill -9 по привычке. Теряешь данные и можешь оставить блокировки. Сначала killall (TERM), девятка - только если завис намертво.
- rm -rf по маске *app* в ~/Library. Сначала find и глазами проверь список, потом удаляй. Маска ловит лишнее.
- Ищешь настройки App Store-приложения в ~/Library/Preferences и не находишь. Они в ~/Library/Containers/<bundle id>/Data/Library/Preferences.
Возьми любое не критичное приложение (например, поставь из Homebrew что-то простое) и пройди цикл:
- Узнай bundle ID:
Код: Выделить всё
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" /Applications/ИМЯ.app/Contents/Info.plist - Проверь, sandboxed ли оно:
Код: Выделить всё
codesign -d --entitlements - /Applications/ИМЯ.app 2>/dev/null | grep app-sandbox - Запусти, поработай, закрой. Найди все его следы: и отдельно глянь ~/Library/Containers, если sandboxed.
Код: Выделить всё
find ~/Library -maxdepth 3 -iname "*ИМЯ*" - Посмотри его настройки:
Код: Выделить всё
defaults read <bundle id> 2>/dev/null | head - Проверь автозапуск: и загляни в ~/Library/LaunchAgents.
Код: Выделить всё
launchctl list | grep -i ИМЯ - Удали начисто: бандл -> хвосты по списку из find -> . Убедись, что в Системных настройках в Login Items ничего не осталось.
Код: Выделить всё
defaults delete <bundle id>
Контрольные вопросы
- Почему данные приложения не лежат внутри .app-бандла и что из этого следует при переустановке?
- Чем путь к настройкам sandboxed-приложения из App Store отличается от пути для обычной программы? Назови оба.
- В автозагрузке висит призрак удаленного приложения, в Login Items его не убрать. Твой порядок действий?
- В чем разница между killall и kill -9 и почему девятку держат на крайний случай?
Приложение в macOS - это код в .app плюс данные в ~/Library, связанные одним bundle ID. Обычные программы разбрасывают данные по Preferences/Application Support/Caches, sandboxed из App Store держат все в ~/Library/Containers. Удаление .app не трогает хвосты, поэтому "чистое" удаление - это всегда найти данные по bundle ID и снести их осознанно, плюс вычистить автозапуск из Login Items, BTM и LaunchAgents. Принудительное завершение начинай с мягкого killall, а дефолтные приложения переназначай через duti и базу LaunchServices. Знаешь, где что лежит - управляешь системой, а не борешься с ней.