认识 ManagedApp 框架
Get to know the ManagedApp Framework
2025年6月10日
一句话判断
如果你在做企业应用,现在终于可以扔掉自己写的那套 MDM 配置解析代码了。
这场 Session 讲了什么
过去十年,企业 iOS 应用获取组织配置的方式几乎没有变过:MDM 服务器往设备推一份 Configuration Profile,应用拿到一堆 key-value,自己去解析。配置格式没有标准,安全性各凭本事,敏感信息明文躺在 UserDefaults 里的例子比比皆是。ManagedApp 框架把这些脏活一次性收编了——你用 Swift 声明配置 schema,框架负责安全传递、持久化、变更通知,甚至帮你把配置表单同步到 ABM/ASM 的管理界面。
这个框架最聪明的设计决策是强制要求”双模式运行”。每个配置项都必须有默认值,你的应用在没有 MDM 的个人设备上必须完整可用。这不是限制,而是逼你做正确的事:一套代码同时服务企业和个人用户,不用维护两个版本。对企业 IM、教育、金融合规类应用来说,这个框架直接解决了过去需要跟每家 MDM 厂商分别适配的痛点。
安全方面,证书类型配置走 Keychain 存储,API 密钥在内存中受保护,不会落盘。开发者调一个 API 读配置,底层的安全细节全部由框架接管。这大幅降低了”开发者一不小心把 token 存到 plist 里”这类事故的概率。
值得深挖的点
配置 Schema 的声明式设计
ManagedApp 要求你在代码中用声明式的方式定义配置项的类型——字符串、布尔、数字、整数、证书。这些定义不只是给编译器看的,它们会被框架提取后同步到 ABM/ASM 的管理后台。IT 管理员看到的不再是晦涩的 key-value 对,而是带类型、带描述的结构化表单。
这个设计背后的 trade-off 很明显:schema 一旦发布就不能随意改。加字段没问题,但删字段或改类型会影响已经在用的企业客户。所以配置项的命名和类型定义需要一开始就想清楚,像设计公开 API 一样谨慎。相比之下,旧的 App Config 方案虽然混乱,但至少 key-value 的灵活性让”加一个新配置”零成本——代价是没有任何类型保障,管理员填错了应用就崩。
框架选了”类型安全 + 配置即接口”的路线,换来的是更好的管理体验和更少的运行时错误。对长期维护的企业应用来说,这个交换值得。
强制双模式运行的工程意义
“没有 MDM 配置时应用必须正常运行”这条规则,初看像是在增加开发量——你得为每个配置项想默认值。但它实际上消灭了一个长期困扰企业应用的问题:同一套代码能不能同时卖给企业和个人用户?
过去常见的做法是维护两个 target,或者用 #if ENTERPRISE 编译开关。前者增加测试矩阵,后者容易遗漏。ManagedApp 的双模式设计让这个问题从架构层面消失了:你的配置读取代码天然兼容有/无 MDM 两种状态,isManaged 属性让你在 UI 层面做差异化展示,而不是在功能层面做分裂。
唯一需要注意的是默认值的”安全优先”原则。enableSSO 默认 false 没问题,但 allowedDomains 默认 ["*"] 在企业场景下可能不够安全。你需要根据配置项的语义分别设计默认策略,而不是一刀切。
代码片段
声明配置并读取
场景:企业 IM 应用需要从 MDM 接收服务器地址和功能开关。
import ManagedApp
struct AppConfig {
static func resolve() -> Self {
let config = ManagedAppConfiguration.current
return Self(
serverURL: config.string(forKey: "server_url")
?? "https://default.example.com",
maxUpload: config.integer(forKey: "max_upload_mb")
?? 50,
enableChat: config.bool(forKey: "enable_chat")
?? true
)
}
let serverURL: String
let maxUpload: Int
let enableChat: Bool
}
坑:key 名会直接出现在 ABM 管理界面,用 server_url 而不是 s,IT 管理员不是程序员。
监听配置变更
场景:IT 管理员在后台更新了配置,应用需要实时响应。
import ManagedApp
class ConfigWatcher {
private var observation: ManagedAppObservation?
func start() {
observation = ManagedAppConfiguration.observeChanges { config in
// 回调可能在任意线程
DispatchQueue.main.async {
self.apply(config)
}
}
}
private func apply(_ config: ManagedAppConfiguration) {
if let cert = config.certificate(forKey: "client_cert") {
NetworkManager.shared.setCertificate(cert)
}
}
}
坑:observeChanges 的回调不在主线程,直接更新 UI 会闪退。证书在设备锁定后可能不可用,需要预加载。
安全读取证书配置
场景:企业金融应用需要使用组织分发的客户端证书连接内网服务。
import ManagedApp
func configureTLS() {
let config = ManagedAppConfiguration.current
guard let clientCert = config.certificate(forKey: "mtls_cert") else {
// 非受管环境:使用应用内置的默认证书或跳过 mTLS
return
}
let identity = SecIdentity.from(certificate: clientCert)
URLSession.shared.configuration.urlCredential = .init(identity: identity)
}
坑:certificate(forKey:) 返回的证书由框架管理生命周期,不要自己存到 Keychain 里,会和框架的存储冲突。
最佳实践
维护企业应用时,建议先不动现有代码。花半天时间把 ManagedApp 框架引进来,定义一个最小的配置 schema——比如就三个字段:服务器地址、一个功能开关、一个证书。跑通从”定义 schema → ABM 看到配置表单 → 设备收到配置 → 应用读取”的完整链路。
确认链路通了之后,再逐步把旧的 App Config 解析代码迁过来。每迁移一个配置项就删掉对应的旧代码,不要留两套读取逻辑共存的中间状态。
配置项数量控制在 15 个以内。超过这个数,IT 管理员在 ABM 界面里翻配置表单会很痛苦。如果确实需要更多配置,考虑把相关配置打包成一个 JSON 字符串字段,应用端自己解析。
最后,isManaged 状态变化时做一次完整的配置重载,而不是逐字段响应变更。这能避免”配置 A 更新了但配置 B 还是旧值”的中间状态问题。
还有什么值得关注
- ManagedApp 框架和 ABM/ASM 的集成是双向的:你在代码里加一个带描述的配置项,ABM 管理界面自动生成对应的表单字段,不需要 IT 管理员手动配置 key-value。
- 框架对 App Config 的替代不是强制的,两种方案可以共存,但长期来看 ManagedApp 会是 Apple 推荐的方向。
- 证书类型的配置支持自动续期,但具体行为取决于 MDM 服务器的实现,这一点 Session 里没有展开讲。