# SuperSDK iOS 接入文档 > **版本:dev** | **稳定性:开发中** — 内容随时变更,仅供内部参考 ## 概述 SuperSDK iOS 版提供与 Android 版功能对齐的 WebView 容器能力,包括 JSBridge、离线包、云控配置等。 ## 环境要求 | 项目 | 要求 | |------|------| | iOS 版本 | iOS 14.0+ | | Swift 版本 | Swift 5.0+ | | WebView | WKWebView | | 架构支持 | arm64(真机)、arm64 + x86_64(模拟器) | ## 集成方式 ### 方式一:远程 Swift Package Manager(第三方模板推荐) 第三方宿主和模板工程优先使用远程 SPM 包。发布时需要同时提供: | 环境变量 | 说明 | |----------|------| | `SPM_PACKAGE_URL` | 承载 generated `Package.swift` 的 HTTPS Git 仓库地址 | | `SPM_BINARY_BASE_URL` | 承载 `SuperSDK-.xcframework.zip` 的 HTTPS CDN 根地址 | ```swift // Package.swift dependencies: [ .package(url: "", exact: "") ] ``` 发布脚本会生成: ``` packages/ios-sdk/release/ ├── SuperSDK-.xcframework.zip ├── spm-package/Package.swift └── spm-release.json ``` `spm-release.json`、`spm-package/Package.swift` 和 zip 文件 checksum 必须一致。只有 `SPM_PACKAGE_URL` 与 `SPM_BINARY_BASE_URL` 都配置为真实 HTTPS 地址后,才能作为公网远程 SPM 包发布。 ### 方式二:XCFramework 手动集成 1. 从本目录获取 `SuperSDK.xcframework` 2. 将 `SuperSDK.xcframework` 拖入 Xcode 项目 3. 在 Target > General > Frameworks, Libraries, and Embedded Content 中设置为 **Embed & Sign** ``` SuperSDK.xcframework/ ├── ios-arm64/ # 真机 (arm64) └── ios-arm64_x86_64-simulator/ # 模拟器 (arm64 + x86_64) ``` ### 方式三:本地 Swift Package Manager(SDK 开发调试) ```swift // Package.swift dependencies: [ .package(path: "path/to/SuperSDK") ] ``` ### 方式四:源码集成 将 `SuperSDK/` 目录作为本地 Swift Package 引入项目。 ### 必要的 Info.plist 配置 **宿主必须在 App 的 Info.plist 填写 `NSMicrophoneUsageDescription`**(SDK / xcframework 不会替宿主带上这条)。H5 `getUserMedia` 麦克风依赖该用途说明;缺或空字符串时,iOS 15+ 容器 **Deny、不启动采集**(避免 TCC 因缺说明杀进程)。 ```xml NSMicrophoneUsageDescription 网页需要使用麦克风进行语音输入或录音。 ``` `createPageView()` / `createPageViewController(url:)` **无需再包一层 `WKUIDelegate`**。iOS 15+ `SuperWebView` 已实现 `requestMediaCapturePermissionFor`(microphone 有说明则 Prompt;camera 恒 Prompt;cameraAndMicrophone 无 mic 说明则 Deny)。iOS 14 无该 API,保持系统默认。getUserMedia 需 **https** 安全源;离线命中改写的 `super-offline://` 通常不是 secure context。 --- ## 初始化 > 注意:模块名为 `SuperSDK`(`import SuperSDK`),入口类为 **`SuperSDKManager`**。 > 用法 `SuperSDKManager.shared.xxx`。(iOS 入口类名与 Android 的 `SuperSDK` 不一致,详见发布说明。) 在 `AppDelegate` 中初始化 SDK: ```swift import SuperSDK @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Replace ab12cd34 with the real spid issued by the SuperSDK console. try! SuperSDKManager.shared.initialize( spid: "ab12cd34", provider: MySDKProvider() ) return true } } ``` **参数说明:** | 参数 | 类型 | 说明 | |------|------|------| | spid | String | 控制台签发的 8 位小写字母/数字项目标识 | | provider | SuperSDKProvider | 宿主实现的服务提供者 | --- ## 项目环境(API Environment) 项目环境对应控制台顶栏的 env(如 `prod` / `staging`),决定 SDK 拉取哪套云控配置(settings)以及埋点上报归属的环境。它不再是 initialize 参数,而是独立的运行时状态。 方法签名:`public func setAPIEnvironment(_ environment: String) throws` / `public func getAPIEnvironment() -> String` ```swift // 设置项目环境(initialize 前后均可调用;非法值抛 SDKIdentityValidationError.invalidEnvironment 且不落地) try SuperSDKManager.shared.setAPIEnvironment("staging") // 读取当前项目环境(未设置时返回 "prod",不要求已 initialize) let env = SuperSDKManager.shared.getAPIEnvironment() ``` **规则与行为:** - 取值限 1–10 位小写字母/数字(`^[a-z0-9]{1,10}$`),不 trim、不做大小写转换 - **SDK 不持久化该值**:进程启动默认 `prod`,宿主如需记忆用户选择,需自行持久化并在启动后调用 `setAPIEnvironment` - 运行时切换的副作用顺序:冲刷旧环境的存量上报事件 → 换值 → settings 清本地缓存并按新环境重拉 → 刷新存活 WebView 的 UA(仅影响后续导航) - 切换后 settings 拉取自动带 `env` 参数、上报 `basic_data.env` 与请求头 `X-Super-API-ENV` 均使用新值;H5 侧经 `getEnv()` 读到新值 - 该环境与 `HostConfigService.sdkApiBaseUrls`(API 域名)是不同维度:切环境不需要也不应该改 API 域名 --- ## 实现 SuperSDKProvider `SuperSDKProvider` 是宿主 App 向 SDK 提供能力的桥梁。所有方法均为可选实现(默认返回 `nil`)。 ```swift class MySDKProvider: SuperSDKProvider { private let hostConfigService = MyHostConfigService() func getHostConfigService() -> HostConfigService? { return hostConfigService } func getEventService() -> EventService? { nil } func getLoginService() -> LoginService? { nil } func getHostMethodService() -> HostMethodService? { nil } func getNavigationService() -> NavigationService? { nil } func getNativeBlockService() -> NativeBlockProvider? { nil } } ``` --- ## 核心 API ### 创建页面 ```swift // 方式 1: 创建 PageView 嵌入到自己的视图中 let pageView = SuperSDKManager.shared.createPageView() view.addSubview(pageView) pageView.frame = view.bounds pageView.load(url: "https://example.com") // 方式 2: 创建 ViewController(推荐,内置导航栏和生命周期管理) let vc = SuperSDKManager.shared.createPageViewController(url: "https://example.com") navigationController?.pushViewController(vc, animated: true) // 方式 3: 通过 PageController 打开全屏页面 SuperSDKManager.shared.getPageController().openPage(url: "https://example.com") ``` ### SuperPageDelegate - 页面事件监听 ```swift class MyViewController: UIViewController, SuperPageDelegate { override func viewDidLoad() { super.viewDidLoad() let pageView = SuperSDKManager.shared.createPageView() pageView.delegate = self view.addSubview(pageView) pageView.frame = view.bounds pageView.load(url: "https://example.com") } func pageDidStartLoading(_ page: SuperPageView) { // 开始加载 } func pageDidFinishLoading(_ page: SuperPageView) { // 加载完成 } func pageDidFailLoading(_ page: SuperPageView, error: Error) { // 加载失败 } func page(_ page: SuperPageView, didChangeTitle title: String?) { // 标题变化 } func page(_ page: SuperPageView, didChangeProgress progress: Double) { // 加载进度变化 (0.0 ~ 1.0) } } ``` ### SFeedback - 用户反馈 SDK 内置的用户反馈提交,与 H5 的 `SuperSDK.submitFeedback` 同一条链路:`POST /superapi/v2/feedback` → 控制台「业务运营 → 用户反馈」按项目 × 环境查看、搜索并标记处理状态。SDK 自动补齐 `spid / env / device_id / platform / app_version / sdk_version / os_version / device_model`,服务端记录提交时间与用户 IP;宿主只需提供业务字段。 ```swift let request = FeedbackRequest( scene: "settings", title: "打开设置页闪退", content: "进入设置页后约 1 秒必现闪退", subScene: "about", // 可选 userId: currentUserId, // 可选:业务用户 ID,SDK 不维护用户身份 extra: ["page": "settings"] // 可选:附加信息(≤4KB) ) SFeedback.submit(request) { result in switch result { case .success(let feedbackId): print("feedback stored: \(feedbackId)") case .failure(let error): // error.code: INVALID_ARGUMENT(本地校验失败,未发请求)/ NETWORK_ERROR / RATE_LIMITED / // UNAVAILABLE / PROTOCOL_MISMATCH,或服务端业务 code(INVALID_REQUEST / NOT_FOUND …) print(error.code, error.message) } } ``` | 字段 | 必填 | 限制 | |------|:----:|------| | scene | ✓ | ≤64 字符 | | title | ✓ | ≤200 字符 | | content | ✓ | ≤5000 字符 | | subScene | | ≤64 字符 | | userId | | ≤128 字符 | | extra | | JSON object,序列化后 ≤4KB | > 回调恒在主线程;所有字符串会被 trim,长度按字符数计(服务端为权威)。限流:每 IP 10 次/分钟、每项目 600 次/分钟。提交**不是幂等**的,SDK 不自动重试。 ### NativeBlockProvider - 原生组件嵌入 允许 H5 页面嵌入宿主 App 提供的原生视图(如广告、视频播放器等)。 ```swift class MyNativeBlockProvider: NativeBlockProvider { var supportedBlockTypes: [String] { return ["ad-banner", "video-player"] } func createBlockView(type: String, params: [String: Any]) -> UIView? { switch type { case "ad-banner": let adView = UIView() adView.backgroundColor = .systemBlue return adView case "video-player": let player = UIView() // 配置视频播放器... return player default: return nil } } func onBlockShow(blockId: String, view: UIView, type: String) { // Block 进入可视区域 } func onBlockHide(blockId: String, view: UIView, type: String) { // Block 离开可视区域 } func onBlockDetached(blockId: String, view: UIView, type: String) { // Block 被移除,释放资源 } } ``` ### OfflineController - 离线包管理 通过 `SuperSDKManager.shared.getOfflineController()` 获取。 ```swift let offlineController = SuperSDKManager.shared.getOfflineController() // 查询所有离线包状态 let packages = offlineController.getAllPackages() // 查询指定离线包 let state = offlineController.getPackage(host: "example.com") // 根据 URL 查询对应的离线包 let state2 = offlineController.getPackageForUrl("https://example.com/page") ``` #### 离线包远程更新 SDK 支持从远程站点自动获取并安装最新离线包,无需宿主额外接入代码即可生效。 **自动更新行为:** - **探测时机**:打开任意 http(s) 页面时,SDK 在页面加载过程中后台静默探测该 host 站点根路径下的 `super_meta.json`(站点侧发布规范见 [`OFFLINE_PKG_SPEC.md`](https://www.superplatform.ai/offline-pkg-spec.md)),不阻塞页面渲染,站点未接入(404)视为正常状态 - **生效时机**:探测到新版本并下载安装成功后,**下次加载**该 host 页面时生效,不会刷新当前已打开的页面 - **大小上限**:自动模式下单包 **50MB** 以内才会自动下载,超过则跳过(仅显式调用可下载) - **探测频控**:同一 host 最小探测间隔 30 分钟,避免频繁开关页对站点造成请求压力 **显式触发更新**:宿主可主动调用 `preloadPackage(host:listener:)` / `preloadAllPackages(listener:)` 立即检查更新,绕过频控与大小上限: ```swift class MyUpdateListener: OfflineUpdateListener { func onProgress(host: String, progress: Int) { // 下载进度 0-100 } func onComplete(result: OfflineUpdateResult) { if result.isSuccess { // 更新成功,下次加载该 host 页面生效 } } } // 立即检查并更新指定 host 的离线包(绕过频控与 50MB 上限) offlineController.preloadPackage(host: "example.com", listener: MyUpdateListener()) // 立即检查并更新全部已装离线包 offlineController.preloadAllPackages(listener: MyUpdateListener()) ``` > 首次调用时若本地尚未安装该 host 的离线包,只要远程清单探测成功,同样会走首装流程(首装同样在下次加载时生效,不影响本次页面渲染)。 **站点侧要求**:站点需在根路径发布符合 `super_meta.json` v1 规范的清单文件,并保证声明的 `pkg_path` 与站点同源。完整字段定义、zip 打包工具链约束(`zip -r` deflate,不支持流式 data-descriptor 产物)见 [`OFFLINE_PKG_SPEC.md`](https://www.superplatform.ai/offline-pkg-spec.md)。 #### 离线页运行时约定(自 1.0.0.59 起) 离线命中时,SDK 通过自定义 scheme `super-offline://{host}/…` 请求级拦截整个离线包(对齐 Android,支持 `base:'/'` 绝对路径与 History 路由)。离线页在 iOS 上的 origin 因此是 `super-offline://{host}` 而非真实域名,接入方(尤其 H5 侧)需注意: - **非 secure context**:离线页不满足安全上下文,`Service Worker`、`crypto.subtle` 等受安全上下文限制的 API 不可用。若离线包依赖这些能力,请勿以离线包形态分发。 - **API 跨域**:离线页打自家后端为跨域请求,后端需返回 CORS 头;带 cookie 凭据时不能用 `Access-Control-Allow-Origin: *`,需回显具体 origin 并加 `Access-Control-Allow-Credentials: true`。 - **POST body**:经 `fetch`/`XHR` 发往 `super-offline://` 同源路径的 `POST`/`PUT` 请求体会被 WebKit 丢弃;API 请求一律走**绝对 https URL**。 - 页内资源引用用相对路径或 `base:'/'` 绝对路径均可(都会命中本地包);跨域绝对 https URL 资源不经离线拦截,照常走网络。 ### ShareController - 短链生成 通过 `SuperSDKManager.shared.getShareController()` 获取,提供短链生成能力(与 Android `SuperSDK.getShareController()` 对齐)。 ```swift let shareController = SuperSDKManager.shared.getShareController() // 生成短链 shareController.requestShortUrl(url: "https://example.com/very/long/path?a=1") { code, msg, shortUrl in if code == "SUCCESS", let shortUrl = shortUrl { print("短链: \(shortUrl)") } } // 通过 HTML 内容生成短链(访问短链时直接渲染 HTML,不做 302 跳转) shareController.requestShortUrlWithHtml(html: "...") { code, msg, shortUrl in // ... } // 两个方法均支持自定义短链前缀 shareController.requestShortUrl(url: "https://example.com/page", baseUrl: "https://s.a.com/") { code, msg, shortUrl in // ... } ``` 回调 `code` 取值:`SUCCESS` 成功(`shortUrl` 为短链字符串)、`BAD_REQUEST` 参数为空、`FAIL_API` 服务端返回错误(`msg` 为错误信息)、`FAIL_PARSE` 响应解析失败、`FAIL_REQUEST` 网络请求失败。 > H5 侧对应能力为 `SuperSDK.requestShortUrl()` / `requestShortUrlWithHtml()`;系统分享面板由 JSBridge `share` 方法内置提供(`UIActivityViewController`),无需宿主注入。 --- ## Service 协议说明 ### HostConfigService - 宿主配置 提供宿主 App 的基本配置信息。 ```swift class MyHostConfigService: HostConfigService { var appChannel: String { "AppStore" } var appVersionCode: Int { 1 } var appVersionName: String { "1.0.0" } var sdkApiBaseUrl: String? { nil } // 自定义 API 地址,nil 使用默认 func generateDeviceId() -> String? { nil } // 宿主接管 deviceId 生成,nil 使用 SDK 默认逻辑 } ``` | 成员 | 类型 | 说明 | |------|------|------| | `appChannel` | String | 渠道标识(如 AppStore) | | `netEnv` | ApiEnv | ⚠️ 已废弃(SDK 从未消费);项目环境请使用 `setAPIEnvironment(_:)` | | `appVersionCode` | Int | 版本号 | | `appVersionName` | String | 版本名称 | | `sdkApiBaseUrl` | String? | 自定义 API 地址 | | `generateDeviceId()` | String? | 宿主接管 deviceId 生成(可选,默认 nil) | **generateDeviceId - 宿主接管 deviceId 生成**(可选): - 仅在 SDK 需要**生成新 deviceId** 时调用(首次安装,Keychain 无既有值),已持久化的 deviceId 继续沿用,不受影响(缓存优先)。 - 返回非空字符串:SDK 采用该值作为 deviceId 并写入 Keychain;返回 `nil`/空串:走 SDK 默认生成逻辑(identifierForVendor → UUID 兜底)。 - 实现要求:**同步快速返回**,且多次调用应返回稳定值。 ### EventService - 事件上报 接收 SDK 产生的事件,对接宿主的埋点平台。 ```swift class MyEventService: EventService { func onEvent(_ event: Event) { // 上报到自己的埋点平台 Analytics.track(event.eventName, params: event.params) } } ``` **Event 属性:** | 属性 | 类型 | 说明 | |------|------|------| | `eventName` | String | 事件名称 | | `eventType` | Int | 事件类型 | | `timestamp` | Int64 | 时间戳 | | `params` | [String: Any] | 事件参数 | ### LoginService - 登录服务 当 H5 需要登录时,SDK 调用此服务。 ```swift class MyLoginService: LoginService { func requestLogin(from viewController: UIViewController, callback: @escaping (_ code: String, _ msg: String, _ loginInfo: HostLoginInfo?) -> Void) { // 拉起宿主的登录流程 LoginManager.shared.login(from: viewController) { result in if result.isSuccess { let loginInfo = HostLoginInfo( userId: result.userId, token: result.token, extra: nil ) callback("SUCCESS", "登录成功", loginInfo) } else { callback("FAIL", "登录失败", nil) } } } } ``` **HostLoginInfo:** | 属性 | 类型 | 说明 | |------|------|------| | `userId` | String | 用户 ID | | `token` | String | 登录 Token | | `extra` | [String: Any]? | 扩展信息(可选) | ### HostMethodService - 宿主自定义方法 允许 H5 调用宿主自定义方法,实现业务扩展。 ```swift class MyHostMethodService: HostMethodService { func invokeAsync(methodName: String, params: [String: Any]?, callback: @escaping (_ code: String?, _ errMsg: String?, _ result: [String: Any]?) -> Void) { switch methodName { case "getUserInfo": callback("SUCCESS", nil, ["name": "张三"]) default: callback("NOT_FOUND", "方法不存在", nil) } } } ``` ### NavigationService - 接管 openPage(URL 路由) 宿主实现此协议即可**接管 openPage**:拿到 **URL + 配置**,自行决定如何打开(自有原生页 / 重定向 / 自有 WebView 等)。返回 `true` 表示已自行处理;返回 `false` 交回 SDK 默认容器打开(选择性拦截 + SDK 兜底)。 ```swift class MyNavigationService: NavigationService { func openPage(url: String, config: PageConfig) -> Bool { // 命中宿主路由 → 打开自己的原生页,返回 true 表示已接管 if let vc = MyRouter.matchNative(url) { MyRouter.push(vc) return true } // 其余交回 SDK 默认容器打开 return false } } ``` > 若你想**用 SDK 的 WebView 容器但自己控制展示**,在 `openPage` 里自取容器即可,无需走 SDK 默认页: > ```swift > let v = SuperSDKManager.shared.getPageController().createPageView() > v.load(url: url) > // v.onClosePageListener = { ... } 可响应 H5 的 closePage > // 把 v 放进你自己的 VC 展示,然后 return true > ``` **PageConfig:** | 属性 | 类型 | 说明 | |------|------|------| | `showTitleBar` | Bool | 是否显示标题栏 | | `showBackButton` | Bool | 是否显示返回按钮 | | `initialTitle` | String | 初始标题 | > **容器行为 · `target="_blank"` / `window.open`**:H5 中 `` 链接点击与 `window.open(url)` 会被容器自动识别,统一通过 `openPage` 在端上以**新页**打开(等价于 `appbridge.openPage`),并走上面的 `NavigationService`。H5 无需额外适配;与 Android 行为一致。 ### SuperPageView - 页面视图方法 `SuperPageView` 提供以下方法供宿主调用: | 方法 | 说明 | |------|------| | `load(url: String)` | 加载 URL | | `reload()` | 重新加载 | | `canGoBack() -> Bool` | 是否可以后退 | | `goBack()` | 后退一页 | | `handleBackPressed(exitAction: @escaping () -> Void)` | 处理返回键,优先级:前端拦截 > 页面历史回退 > exitAction | | `setSafeArea(statusBarHeight: Int, navigationBarHeight: Int)` | 设置安全区域(物理像素) | | `setImmersiveBottom(_ immersive: Bool)` | 底部是否沉浸(默认 `true`=内容延伸到底部安全区下,现有行为)。置 `false`(浏览器场景):WebView 内容缩短到底部安全区之上 + 页面 `env(safe-area-inset-bottom)=0` + UA `SafeAreaBottom=0`,第三方站点底栏紧贴系统栏、无多余白边。仅作用底部(顶部恒沉浸);应在注入安全区/首次 `load` 前调用 | | `sendBroadcast(eventKey: String, jsonData: String?)` | 向当前 WebView 发送广播 | | `onShow()` | 通知页面进入前台 | | `onHide()` | 通知页面进入后台 | | `onDestroy()` | 销毁页面 | ### HostNetworkExtensionService - 网络请求扩展(可选) 允许宿主为 **SDK 内部网络请求**(settings / 事件上报 / 归因 / 短链等)追加 query 参数与 request header。协议方法全部有默认实现,不实现即不生效,既有接入方无需改动。 ```swift class MyProvider: NSObject, SuperSDKProvider { private let networkExtension = MyNetworkExtension() func getHostNetworkExtensionService() -> HostNetworkExtensionService? { return networkExtension } } class MyNetworkExtension: HostNetworkExtensionService { func getExtraQueryParams(_ context: SuperRequestContext) -> [String: String]? { // 开启响应混淆:SDK 自身不会默认追加该参数 return ["suenc": "1"] } func getExtraHeaders(_ context: SuperRequestContext) -> [String: String]? { return ["X-Host-Trace": traceId()] } } ``` `SuperRequestContext` 提供 `url` / `urlString` / `path` / `host` / `method`,宿主可据此按请求条件化返回。 > **合并规则(SDK 侧强制,宿主无法绕过):** > 1. **SDK 自有 query / header 恒胜**——query 名精确匹配,header 名**大小写不敏感**匹配。例如 SDK 已设 `X-Super-API-ENV`,宿主返回 `x-super-api-env` 也不会覆盖它 > 2. `Authorization`、`Cookie` 等认证凭据 header **不接受本接口注入** > 3. 非法项被**单项跳过**,其余照常生效;日志只记 key 与拒绝原因,不记 value > 4. 实现抛出的异常一律被捕获,**不阻断 SDK 主流程** > 5. **每次请求实时读取**,SDK 不缓存返回值 > 6. 方法在发起请求的线程上**同步调用**,实现必须快速返回、不得阻塞 ### 响应混淆(suenc) 服务端在请求带 `?suenc=1` 时会用自定义 Base64 表编码 JSON 响应体并回 `X-Su-Enc: b64t1`。**SDK 内部请求会自动解码**(含非 2xx 的错误响应),宿主无需介入——只要按上文注入 `suenc=1` 即可。 > ⚠️ **只用于降低通用关键字扫描的有效性,不是加密**:不提供机密性、防篡改、防重放。 若宿主自己发起了 suenc 请求,可调用 SDK 的解码能力: ```swift // 查询本端支持的 codec,决定是否发送 suenc=1 let capabilities = SuperSDKManager.shared.getSuEncCapabilities() // ["supported": true, "codecs": ["b64t1"]] // codec 必须直接使用响应 X-Su-Enc header 的原始值,不接受自定义编码表 do { let json = try SuperSDKManager.shared.decodeSuEnc(encodedBody, codec: codec) } catch let error as SuEncError { // .unsupportedCodec / .decodeFailed / .invalidUTF8 } ``` > **注意事项:** > 1. 解码失败一律视为请求失败,**绝不回落读取明文**——那正是该协议要防的降级 > 2. 若请求带了 `suenc=1` 却收到无 `X-Su-Enc` 的响应,SDK 判定该线路失败并自动切换下一个 baseUrl > 3. 编码表在 SDK 发布后永不变更;新表一定使用新的 codec key,因此 `codec` 必须取自响应 header 而非硬编码 ### Service 总览 | Service | 说明 | 必要性 | |---------|------|--------| | `HostConfigService` | 宿主配置(渠道、版本、环境等) | 推荐实现 | | `EventService` | 事件上报回调 | 可选 | | `LoginService` | 登录服务 | 可选 | | `HostMethodService` | 宿主自定义方法调用 | 可选 | | `NavigationService` | 自定义导航服务 | 可选 | | `NativeBlockProvider` | Native Block 创建和生命周期 | 可选 | | `HostNetworkExtensionService` | SDK 内部请求的 query / header 扩展 | 可选 | --- ## JSBridge 前端通过 `window.appbridge` 调用原生能力,与 Android 保持一致。详见 [JSSDK 文档](https://www.superplatform.ai/jssdk.md)。 ```javascript // 获取环境信息 const env = await appbridge.getEnvAsync(); // 打开页面 appbridge.openPage('https://example.com', { title: '标题' }); // 调用宿主方法 const result = await appbridge.callHostMethod('methodName', { key: 'value' }); ``` --- ## 完整接入示例 ```swift // 1. AppDelegate 初始化 @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { try! SuperSDKManager.shared.initialize( spid: "ab12cd34", provider: MySDKProvider() ) return true } } // 2. Provider 实现 class MySDKProvider: SuperSDKProvider { private let hostConfig = MyHostConfigService() func getHostConfigService() -> HostConfigService? { hostConfig } func getLoginService() -> LoginService? { nil } func getEventService() -> EventService? { nil } func getHostMethodService() -> HostMethodService? { nil } func getNavigationService() -> NavigationService? { nil } func getNativeBlockService() -> NativeBlockProvider? { nil } } class MyHostConfigService: HostConfigService { var appChannel: String { "AppStore" } var appVersionCode: Int { 1 } var appVersionName: String { "1.0.0" } var sdkApiBaseUrl: String? { nil } // func generateDeviceId() -> String? { MyDeviceIdManager.deviceId } // 可选:宿主接管 deviceId 生成 } // 3. 使用 ViewController 方式打开页面 class HomeViewController: UIViewController { func openWebPage() { let vc = SuperSDKManager.shared.createPageViewController(url: "https://example.com") navigationController?.pushViewController(vc, animated: true) } } // 4. 使用 PageView 方式嵌入 class EmbedViewController: UIViewController, SuperPageDelegate { private var pageView: SuperPageView? override func viewDidLoad() { super.viewDidLoad() let pv = SuperSDKManager.shared.createPageView() pv.delegate = self view.addSubview(pv) pv.frame = view.bounds pv.autoresizingMask = [.flexibleWidth, .flexibleHeight] pv.load(url: "https://example.com") self.pageView = pv } func pageDidFinishLoading(_ page: SuperPageView) { print("页面加载完成") } func page(_ page: SuperPageView, didChangeTitle title: String?) { self.title = title } } ``` --- ## 注意事项 1. **初始化时机**:必须在使用任何 SDK 功能前调用 `SuperSDKManager.shared.initialize()` 2. **线程安全**:UI 操作在主线程执行,JSBridge 回调已自动切换到主线程 3. **内存管理**:WKScriptMessageHandler 的循环引用已在 SDK 内部处理 4. **ATS**:如需访问 HTTP 地址,需在 Info.plist 中配置 App Transport Security 5. **离线包**:SDK 内置离线包支持,加载 URL 时会自动匹配本地资源 6. **麦克风**:宿主 Info.plist **必填** `NSMicrophoneUsageDescription`;不要替换 SDK 的 `uiDelegate`