Настройка Managed App Configuration для iOS-приложения
Managed App Configuration — механизм Apple MDM, который позволяет IT-администратору передавать конфигурацию в приложение без участия пользователя и без хардкода параметров в коде. MDM-сервер отправляет plist-словарь, приложение читает его из UserDefaults. Это стандарт для любого корпоративного iOS-приложения — работает с Jamf, Intune, Workspace ONE, MobileIron и любым другим MDM.
Как приложение читает Managed Config
Всё хранится в UserDefaults под ключом com.apple.configuration.managed:
func loadManagedConfiguration() {
guard let config = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed") else {
// Устройство неуправляемое или конфиг ещё не доставлен
applyDefaultConfiguration()
return
}
let backendURL = config["BackendURL"] as? String ?? AppDefaults.backendURL
let tenantID = config["TenantID"] as? String
let sessionTimeout = config["SessionTimeoutMinutes"] as? Int ?? 30
let enableDebugLogs = config["EnableDebugLogs"] as? Bool ?? false
AppConfig.shared.apply(
backendURL: backendURL,
tenantID: tenantID,
sessionTimeout: sessionTimeout,
debugLogs: enableDebugLogs
)
}
Одна ловушка: конфигурация может прийти не сразу при первом запуске, а через несколько секунд после MDM-checkin. Приложение не должно блокироваться в ожидании конфига — применяем дефолты, потом обновляем.
Реакция на изменения конфигурации
MDM может обновить конфиг в любой момент — например, сменить URL бэкенда при миграции инфраструктуры. Нужно реагировать без перезапуска приложения:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(managedConfigChanged),
name: UserDefaults.didChangeNotification,
object: nil
)
}
@objc private func managedConfigChanged() {
guard let newConfig = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed") else { return }
let newBackendURL = newConfig["BackendURL"] as? String
if newBackendURL != AppConfig.shared.backendURL {
// Переинициализируем сетевой слой с новым URL
NetworkManager.shared.reconfigure(baseURL: newBackendURL)
}
}
UserDefaults.didChangeNotification срабатывает при любом изменении UserDefaults — включая managed config. Фильтруем только нужный ключ, чтобы не перезагружать конфигурацию при каждом незначительном изменении.
Структура конфигурационного словаря
Рекомендуемый подход — JSON Schema для документирования ключей конфигурации. AppConfig Manager на стороне приложения:
struct ManagedConfig: Decodable {
let backendURL: String
let tenantID: String?
let sessionTimeoutMinutes: Int
let allowBiometricAuth: Bool
let supportedLanguages: [String]
let featureFlags: [String: Bool]?
enum CodingKeys: String, CodingKey {
case backendURL = "BackendURL"
case tenantID = "TenantID"
case sessionTimeoutMinutes = "SessionTimeoutMinutes"
case allowBiometricAuth = "AllowBiometricAuth"
case supportedLanguages = "SupportedLanguages"
case featureFlags = "FeatureFlags"
}
}
func decodeManagedConfig() -> ManagedConfig? {
guard let dict = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed"),
let data = try? JSONSerialization.data(withJSONObject: dict),
let config = try? JSONDecoder().decode(ManagedConfig.self, from: data) else {
return nil
}
return config
}
Типизированная структура лучше, чем ручное приведение типов из [AnyHashable: Any] — ошибки конфигурации обнаруживаются раньше.
AppConfig в MDM-консолях
В Jamf Pro конфигурация задаётся в Apps → App Configuration → XML Profile:
<dict>
<key>BackendURL</key>
<string>https://api.corp.example.com/v2</string>
<key>TenantID</key>
<string>CORP-EU-001</string>
<key>SessionTimeoutMinutes</key>
<integer>20</integer>
<key>AllowBiometricAuth</key>
<true/>
<key>SupportedLanguages</key>
<array>
<string>en</string>
<string>de</string>
</array>
</dict>
В Intune — через Apps → App Configuration Policies → Managed Devices → iOS/iPadOS → General. Значения вводятся в виде ключ-значение или в XML. Intune передаёт конфигурацию через Apple MDM протокол — тот же com.apple.configuration.managed ключ.
Тестирование без MDM-сервера
Для разработки конфигурацию можно эмулировать через UserDefaults.standard.set() в launch arguments или через отдельный debug-экран:
#if DEBUG
func injectTestManagedConfig() {
let testConfig: [String: Any] = [
"BackendURL": "https://staging-api.corp.example.com",
"TenantID": "TEST-001",
"SessionTimeoutMinutes": 5,
"AllowBiometricAuth": true
]
UserDefaults.standard.set(testConfig, forKey: "com.apple.configuration.managed")
}
#endif
Правильно также тестировать в Simulator с Managed Preferences через defaults write в терминале — это имитирует реальную доставку конфига через MDM без необходимости реального MDM-сервера.
Feedback Channel: отчёт состояния в MDM
MDM может не только передавать конфиг, но и читать состояние приложения через com.apple.configuration.managed.feedback. Приложение записывает туда статус:
UserDefaults.standard.set([
"LastSyncTime": ISO8601DateFormatter().string(from: Date()),
"ConfigVersion": "2.1",
"EnrollmentStatus": "active"
], forKey: "com.apple.feedback.managed")
MDM-сервер (Jamf, Intune) читает этот ключ при checkin и отображает в инвентаре устройства. Удобно для диагностики без обращения к пользователю.
Этапы настройки
Определение параметров конфигурации → разработка схемы словаря → реализация чтения + реакции на изменения → тестирование без MDM → публикация документации ключей для IT-отдела → настройка профилей в MDM-консоли → пилот → rollout.
Сроки: реализация Managed App Configuration в готовом приложении — 1–2 недели. Стоимость рассчитывается индивидуально.







