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中已正确注册voicepaper2scheme,且扩展的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是更稳定的替代方案:
- 配置主应用的Universal Links(需完成域名验证、上传apple-app-site-association文件等步骤)
- 在扩展中调用Universal Links:
func openVoicepaperApp(path: String = "") { guard let url = URL(string: "https://yourdomain.com/restore") else { return } extensionContext?.open(url, completionHandler: { success in // 处理结果 }) }
- 在主应用的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
相关产品推荐
相关产品推荐

