Личная утилита для мониторинга работы VPN: показывает текущий пинг до Cloudflare (1.1.1.1) и страну выходного узла. Помогает понять, жив ли VPN и через какой регион сейчас ходит трафик.
Состоит из трёх таргетов в одном Xcode-проекте:
- Ping — iOS-приложение (минимальный экран с инструкцией).
- PingWidget — iOS Widget Extension, интерактивный виджет на главный экран.
- Tick — macOS menubar-приложение с автозамером по таймеру.
Локальная утилита, в App Store не публикуется.
Вся история разработки в чате с Claude Code, можно продолжить:
claude --resume f24bd348-d95e-45df-ac3c-d6f256e74221
PingService.measure() делает один прогревочный HTTPS-запрос к https://1.1.1.1/cdn-cgi/trace (warmup, не учитывается — нужен чтобы TLS-handshake не задрал первый замер), затем 5 последовательных запросов, меряет RTT каждого, парсит loc=XX из ответа Cloudflare для определения страны.
Таймаут на запрос — 3 секунды. Если все 5 не дошли — loss=100%.
Возвращает PingResult с полями:
avg,min,max— статистика по успешным запросам в мс.loss— процент потерянных пакетов (0/20/40/60/80/100).timings: [Double?]— все 5 индивидуальных замеров (nil= таймаут).country— ISO-код страны датацентра Cloudflare (DE,RU,IT,GB...).
PingService.swift продублирован в PingWidget/ и Tick/ (одинаковая логика, разные таргеты).
Виджет размера systemSmall, полностью кликабельный — тап в любую точку запускает замер через MeasurePingIntent (App Intent, iOS 17+).
UI виджета:
- Цифра avg ms с градиентом цвета по значению (зелёный→жёлтый→красный, плавная HSB-интерполяция в диапазоне 30–220 мс).
- 5 столбиков пропорциональной высоты, цвета по тому же градиенту, с цифрами внутри.
- Капсула с полным названием страны (
Locale.localizedString(forRegionCode:)) в правом верхнем углу. - При
loss=100%— крупный «—» и подписьtimeout.
Хранение: последний результат пишется в UserDefaults.standard внутри extension'а (PingStore.save/load).
Приложение без иконки в Dock (LSUIElement=YES), только пункт в строке меню сверху через MenuBarExtra.
В menubar-label: только цифра, типа 46 ms (или — ms при таймауте, или wifi-иконка пока нет данных).
Дропдаун при клике:
- Цифра avg ms с градиентом + ISO-код страны рядом.
- 5 столбиков с цифрами (компонент
MacPingBars). - Пикер интервала автозамера: 5s / 30s / 1min / 5min / Off (хранится в
UserDefaults). - Тумблер «Launch at login» через
SMAppService.mainApp(требует наличия app в/Applications/). - Кнопки «Measure now» и «Quit».
PingMonitor (ObservableObject) держит Timer, по умолчанию замер раз в 30 секунд. Автопауза при засыпании экрана через NSWorkspace.screensDidSleepNotification и автозамер при пробуждении.
Все таргеты подписаны Personal Team (бесплатный Apple ID). На бесплатном профиле подпись iOS-приложения протухает раз в 7 дней — приходится переустанавливать через Xcode.
macOS Tick подписан Development-сертификатом, работает на этой машине бессрочно.
В Xcode:
- Выбрать схему
Ping, цель — подключённый iPhone. - Run (
⌘R). - На iPhone: добавить виджет на главный экран (long-press → «+» → найти Ping).
Personal Team (бесплатный Apple ID) выпускает сертификат всего на 7 дней. После этого iOS перестаёт запускать приложение и виджет — увидишь ошибку «не удалось проверить разработчика».
Раз в неделю:
- Подключить iPhone кабелем.
- В Xcode: схема
Ping, цель — iPhone, жмёшь Play (⌘R). - Готово. Виджет на главном экране сам подхватит, перенастраивать не надо.
Альтернативы:
- $99/год Apple Developer — сертификат на год, переподпись не нужна.
- AltStore/SideStore — автопереподпись, но требует своей возни с настройкой.
Из репозитория собирается Release-версия (universal-бинарник для Apple Silicon + Intel) и кладётся в /Applications. В конце удаляются временные копии из DerivedData, иначе Spotlight/Launchpad начинают показывать несколько одинаковых иконок «Tick».
cd /Users/george_ns/Developer/Ping
pkill -f "Tick$" 2>/dev/null
xcodebuild -scheme Tick -configuration Release -destination 'platform=macOS' \
ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO build
rm -rf /Applications/Tick.app
cp -R ~/Library/Developer/Xcode/DerivedData/Ping-*/Build/Products/Release/Tick.app /Applications/
rm -rf ~/Library/Developer/Xcode/DerivedData/Ping-*/Build/Products/Debug/Tick.app
rm -rf ~/Library/Developer/Xcode/DerivedData/Ping-*/Build/Products/Release/Tick.app
killall Dock 2>/dev/null
open /Applications/Tick.appДля пересборки DMG для раздачи друзьям после этого:
cd /Users/george_ns/Developer/Ping
mkdir -p dist/staging && cp -R /Applications/Tick.app dist/staging/
ln -sf /Applications dist/staging/Applications
hdiutil create -volname Tick -srcfolder dist/staging -ov -format UDZO dist/Tick.dmg
rm -rf dist/stagingПосле запуска — клик на цифру в меню → включить «Launch at login».
Важно: SMAppService запоминает путь к app. Если запустить Tick через Xcode (он живёт в DerivedData), а потом включить автозапуск — после ребута macOS будет искать Tick.app в DerivedData, что нестабильно. Поэтому только из /Applications.
В отличие от iOS, macOS не требует еженедельной переподписи — Development-сертификат живёт около года, и macOS не делает регулярных проверок свежести подписи у уже установленных приложений. Tick в /Applications будет работать сколь угодно долго, пока сам не удалишь.
- Открыть проект в Xcode — он перевыпустит сертификаты автоматически.
- Запустить команды установки выше.
Поле colo= в trace отвечает за конкретный датацентр (трёхбуквенный IATA-код аэропорта), loc= — за страну (ISO-2). Сейчас используем loc, но если когда-нибудь захочется датацентр для большей точности — в PingService.parseCountry достаточно поменять одну строку.
Примеры colo: FRA Франкфурт, AMS Амстердам, MXP Милан, DME Москва-Домодедово, LED Санкт-Петербург, IST Стамбул, WAW Варшава.