Get to know the ManagedApp Framework
Privacy & Security 进阶 35m

认识 ManagedApp 框架

Get to know the ManagedApp Framework

2025年6月10日

在 Apple 官方观看视频

一句话判断

如果你在做企业应用,现在终于可以扔掉自己写的那套 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 里没有展开讲。
ManagedApp Enterprise MDM Security