You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 18中Action Extension无法打开主应用,原方法失效求助

iOS 18中Action Extension无法打开主应用的问题排查与解决方案

问题背景

开发带有Action Extension的iOS应用,此前在iOS 17及更早版本中,从扩展通过URL Scheme打开主应用的功能正常运行,但在iOS 18中完全失效,导致恢复流程无法启动。

此前的实现方案(iOS 17及以前可用)

动作视图控制器中的原始实现

private func restore() {
    let publicURL = String(kUTTypeURL)
    let publicText = String(kUTTypeText)

    let extensionItem: NSExtensionItem = extensionContext?.inputItems.first as! NSExtensionItem
    let itemProvider = extensionItem.attachments?.first as! NSItemProvider
    if itemProvider.hasItemConformingToTypeIdentifier(publicURL) {
        itemProvider.loadItem(forTypeIdentifier: publicURL, options: nil, completionHandler: { [weak self] item, _ in
            if let fileURL: NSURL = item as? NSURL,
               let tmpURL = VPRealmManager.restore(url: fileURL as URL)
            {
                let sharedDefaults = UserDefaults(suiteName: Key.AppGroup.Identifier)!
                sharedDefaults.set(tmpURL.absoluteString, forKey: "backup_file_key")
                sharedDefaults.synchronize()
                self?.openVoicepaperApp(path: "restore")
            }
        })
    }

    closeExtension()
}

func openVoicepaperApp(path: String = "") {
    let url = NSURL(string: "voicepaper2://" + path)
    let selectorOpenURL = sel_registerName("openURL:")
    let context = NSExtensionContext()
    context.open(url! as URL, completionHandler: nil)

    var responder = self as UIResponder?
    while responder != nil {
        if responder?.responds(to: selectorOpenURL) == true {
            responder?.perform(selectorOpenURL, with: url)
        }
        responder = responder!.next
    }
}

简化实现方案

func openVoicepaperApp(path: String = "") {
    if let url = URL(string: "voicepaper2://" + path) {
        extensionContext?.open(url, completionHandler: nil)
    }
}

主应用中的URL处理代码(AppDelegate.swift)

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    // ... 其他URL处理逻辑 ...

    let DefaultsForAppGroup = UserDefaults(suiteName: Key.AppGroup.Identifier)!
    if url.absoluteString.contains("restore"), let fileUrl = DefaultsForAppGroup.object(forKey: "backup_file_key") as? String,
       let backupFileURL = URL(string: fileUrl)
    {
        // 展示弹窗并执行恢复操作
    }

    return true
}

问题核心

上述两种openVoicepaperApp实现方式,在iOS 18中均无法触发主应用的启动,导致恢复流程中断。


排查与解决方案建议

1. 修正扩展上下文的生命周期问题

原代码中restore()方法在异步加载ItemProvider的同时,同步调用了closeExtension(),可能导致extensionContext在open方法执行前就被销毁。iOS 18对扩展的生命周期管理更严格,需将closeExtension()移到异步回调或open方法的完成回调中:

// 修改后的restore方法
private func restore() {
    let publicURL = String(kUTTypeURL)
    let extensionItem = extensionContext?.inputItems.first as! NSExtensionItem
    let itemProvider = extensionItem.attachments?.first as! NSItemProvider
    
    if itemProvider.hasItemConformingToTypeIdentifier(publicURL) {
        itemProvider.loadItem(forTypeIdentifier: publicURL, options: nil) { [weak self] item, error in
            guard let self = self, let fileURL = item as? URL, let tmpURL = VPRealmManager.restore(url: fileURL) else {
                self?.closeExtension()
                return
            }
            
            let sharedDefaults = UserDefaults(suiteName: Key.AppGroup.Identifier)!
            sharedDefaults.set(tmpURL.absoluteString, forKey: "backup_file_key")
            // 弃用synchronize(),系统会自动同步
            self.openVoicepaperApp(path: "restore")
        }
    } else {
        closeExtension()
    }
}

// 修改后的open方法,添加完成回调
func openVoicepaperApp(path: String = "") {
    guard let url = URL(string: "voicepaper2://" + path), let context = extensionContext else {
        closeExtension()
        return
    }
    context.open(url) { [weak self] success in
        print("打开主应用结果:\(success)")
        self?.closeExtension()
    }
}

2. 适配iOS 18的URL Scheme权限与处理逻辑

  • 检查Info.plist配置:确认主应用的CFBundleURLTypes中已正确注册voicepaper2 scheme,且扩展的Info.plist中未限制对主应用的调用权限。
  • 切换到SceneDelegate处理URL:iOS 13+推荐使用SceneDelegate处理URL跳转,iOS 18可能不再优先触发AppDelegate的open方法,需添加SceneDelegate的处理逻辑:
// SceneDelegate.swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    let DefaultsForAppGroup = UserDefaults(suiteName: Key.AppGroup.Identifier)!
    if url.absoluteString.contains("restore"), let fileUrl = DefaultsForAppGroup.object(forKey: "backup_file_key") as? String,
       let backupFileURL = URL(string: fileUrl)
    {
        // 执行恢复操作
    }
}

3. 改用Universal Links替代URL Scheme

iOS 18对URL Scheme的调用限制可能进一步收紧,Universal Links是更稳定的替代方案:

  1. 配置主应用的Universal Links(需完成域名验证、上传apple-app-site-association文件等步骤)
  2. 在扩展中调用Universal Links:
func openVoicepaperApp(path: String = "") {
    guard let url = URL(string: "https://yourdomain.com/restore") else { return }
    extensionContext?.open(url, completionHandler: { success in
        // 处理结果
    })
}
  1. 在主应用的SceneDelegate中通过continueUserActivity处理:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL {
        // 解析Universal Links并执行对应逻辑
        if url.path.contains("/restore") {
            // 读取共享数据并恢复
        }
    }
}

4. 优化共享数据的传递方式

弃用UserDefaults的synchronize()方法(已废弃),改用更可靠的共享文件或FileCoordinator处理跨应用数据传递,避免iOS 18中可能的同步延迟问题:

// 示例:用共享文件传递路径
func saveBackupPathToSharedGroup(url: URL) {
    guard let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Key.AppGroup.Identifier) else { return }
    let filePath = sharedContainer.appendingPathComponent("backup_path.txt")
    try? url.absoluteString.write(to: filePath, atomically: true, encoding: .utf8)
}

// 主应用读取
func readBackupPathFromSharedGroup() -> URL? {
    guard let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Key.AppGroup.Identifier) else { return nil }
    let filePath = sharedContainer.appendingPathComponent("backup_path.txt")
    guard let pathString = try? String(contentsOf: filePath) else { return nil }
    return URL(string: pathString)
}

5. 排查系统级限制

  • 确认测试设备的iOS 18版本为正式版或最新测试版,排除beta版的临时bug
  • 检查主应用是否被系统限制启动(如后台刷新权限、App Tracking Transparency权限等)

内容的提问来源于stack exchange,提问作者Umeumeume

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:45:00