uni-app iOS UTS扩展开发实战:Xcode环境配置、本地编译与真机调试全指南
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
本文基于 uni-app 开源仓库(
gh_mirrors/un/uni-app)中的官方文档与hello-uts示例工程整理编写。核心脉络来自 iOS UTS扩展开发,并参考 uts for iOS、UTS插件介绍、UTSiOS 内置对象、uts iOS调试 等文档及仓库示例源码进行纵深扩充。
导读
uni-app 生态中,UTS 插件(uni type script 插件)允许开发者用类 TypeScript 的强类型语法直接调用 iOS 原生 API 与三方 SDK,并编译为 Swift 代码运行。本指南聚焦iOS 平台 UTS 扩展开发:从 HBuilderX 3.6.9+ 起,你可以在本地修改 uts 插件的 iOS 平台代码,直接本地编译并真机运行到 iOS 设备,而无需再提交代码到云端制作自定义基座。读完本文,你将掌握 iOS UTS 插件的完整开发链路——Xcode 环境配置、插件目录与原生配置项、DCloudUTSFoundation内置库、Swift 与 UTS 的关键语法差异、真机调试与常见问题排查。
一、版本要求与能力总览
iOS 平台的 uts 插件本地开发能力自HBuilderX 3.6.9+版本开始提供,核心能力是:
- 本地编译:无需将代码上传云端,即可在本地将 uts 插件的 iOS 平台代码编译为 Swift;
- 真机运行:直接运行到 iOS 真机设备,快速验证插件效果。
这意味着插件开发周期大幅缩短:以往修改 iOS 原生代码后需要等待云端打包自定义基座,现在本地修改、本地编译、真机运行一气呵成。使用前提是必须配置 Xcode 环境(详见下文),并且必须安装「uts开发扩展 - iOS」插件。
版本注意:HBuilderX 3.6+ 支持在 uni-app 中使用 uts 插件,HBuilderX 3.9+ 支持在 uni-app x 中使用 uts 插件。本文所述的 iOS 本地编译与真机运行能力,面向 HBuilderX 3.6.9+。
二、安装 uts 扩展插件
当你把带有 uts 插件的项目运行到 iOS 真机设备时,HBuilderX 会自动检测并提示安装【uts开发扩展 - iOS】插件。该插件是 iOS 平台 uts 插件本地编译与真机运行的基础依赖,请务必安装,否则无法继续。
安装后,本地修改 uts 插件utssdk/app-ios目录下的代码,即可在本地编译并真机运行,无需再走云端制作自定义基座流程。
三、Xcode 环境配置
本地真机运行 uts 插件目前有以下硬性环境要求:
| 环境项 | 要求 |
|---|---|
| 操作系统 | macOS(必需) |
| Xcode | Xcode 15.2 或更高版本 |
| Command Line Tools | 与 Xcode 版本相同的 Xcode Command Line Tools |
3.1 安装 Xcode
可通过App Store安装,或前往 Apple 开发者官网下载。安装 Xcode 时通常会同步骤安装Xcode IDE、Xcode命令行工具和iOS模拟器。
新下载 Xcode 后,必须打开一次 Xcode,并确认命令行工具已正确配置。
3.2 检查 Xcode Command Line Tools
命令行工具中包含一些必须的工具(如git等)。启动 Xcode 后,在Xcode | Settings(或 Preferences)| Locations菜单中检查Command Line Tools是否已选择某个版本。
请确保在使用 uts 插件真机运行之前,本地环境已完成如上配置。若 Command Line Tools 未配置,真机运行编译 uts 插件时会直接报错。
3.3 Xcode 版本相关的踩坑提示
针对 Xcode 版本,仓库文档还给出两点重要提示(详见 uts for iOS 第 8 章):
- 高版本 Xcode 编译的 Swift 语言 Framework 动态库、静态库、
.a库在低版本 Xcode 上无法编译通过,存在 Swift 版本兼容性问题; - 若真机运行编译 uts 插件时报 swift 版本不兼容错误,先检查本地 Xcode 版本,确保本地 Xcode 版本大于或等于云端打包机使用的 Xcode 版本;
- 若报
XCode 版本应大于 13.2.1的错误,说明本地 Xcode 版本过低,直接升级到大于或等于打包机的版本即可(该提示中的 13.2.1 限制可忽略,后续版本会优化提示文案)。
四、iOS uts 插件的工作原理与目录结构
4.1 编译期与运行期行为
对 iOS 开发者而言,uts 插件有两个关键阶段(见 uts for iOS):
- 编译时:保存 UTS 源码文件时,IDE 会同步将其编译为对应的 Swift 代码,并生成一个对应的插件 Framework 工程,编译出对应的
framework依赖库; - 运行时:真机运行/云打包时,将
framework依赖库添加到打包工程,生成最终的 ipa 包。
也就是说:UTS 在 iOS 平台上最终编译为 Swift 源码。即使开发 UTS 插件不强制要求掌握 Swift,熟悉 Swift 语法对排查问题和实现复杂功能都很有帮助。
4.2 app-ios 目录结构
UTS 插件建议以 uni_modules 方式组织。在插件utssdk/app-ios目录下,存放 iOS 平台的原生配置与实现(详见 UTS插件介绍):
| 目录名/文件名 | 用途 |
|---|---|
Frameworks | 插件引用的三方 framework / xcframework 依赖库存放目录 |
Libs | 插件引用的三方.a依赖库存放目录(HBuilderX 3.7.2+ 支持) |
Resources | 需要合并到应用 Main Bundle 的资源文件目录(图片、音频等) |
EmbedResources | 合并到编译插件生成的动态库 Framework Bundle 中的资源目录(HBuilderX 5.08+) |
config.json | iOS 平台原生工程配置文件 |
index.uts | 主入口,interface.uts声明的能力在 iOS 平台下的实现 |
Info.plist | 需要添加到原生工程 Info.plist 的配置 |
PrivacyInfo.xcprivacy | 插件隐私清单文件 |
UTS.entitlements | 需要添加到原生工程 entitlements 的配置 |
hybrid.swift | iOS 混编的 swift 文件 |
interceptor.js | js 调用插件代码的拦截器 |
仓库中的真实示例见 hello-uts 的 uts-tencentgeolocation 插件目录,其中实际包含了Frameworks/TencentLBS.framework、config.json、index.uts、info.plist等文件,可以作为标准目录范本对照学习。
4.3 配置 Info.plist
当插件需要在原生工程 Info.plist 中添加配置项时,需在插件app-ios目录中创建Info.plist文件。其格式与配置规则与 iOS 工程一致,云端打包时配置信息会合并到原生工程的 Info.plist 中。
以 hello-uts 中腾讯定位插件的 info.plist 为例,它配置了腾讯定位 APIKey 及后台定位权限:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>TencentLBSAPIKey</key> <string>您申请的APIKey</string> <key>UIBackgroundModes</key> <array> <string>location</string> </array> </dict> </plist>在 uts 代码中,可通过Bundle.main.infoDictionary?["TencentLBSAPIKey"]读取该配置(见 腾讯定位插件 index.uts 中的configLocationManager()实现)。
4.4 配置 entitlements(UTS.entitlements)
HBuilderX 3.6.11+ 支持
当插件需要开启 capabilities 中的相关服务时,在app-ios目录中创建UTS.entitlements文件。例如勾选 Access WiFi Information 项,对应配置为:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.developer.networking.wifi-info</key> <true/> </dict> </plist>UTS.entitlements格式及配置规则与 iOS 工程一致,云端打包时会合并到原生工程的 entitlements 配置文件中。
4.5 依赖资源文件与三方库
- 资源文件:放到插件目录
~/utssdk/app-ios/Resources/,云端打包时该目录下所有文件会添加到应用 main bundle 中,建议只保存 uts 插件内置资源; - 三方 framework/xcframework:存放到
~/utssdk/app-ios/Frameworks/,云端打包时全部添加到工程,目前支持静态库和动态库; - 三方
.a库:存放到~/utssdk/app-ios/Libs/。注意.a库的所有文件(.a文件与对应.h/.swiftmodule)须放在同一个文件夹内,多个.a库创建多个文件夹,且不要把.a或.h嵌套在多层文件夹内(不会递归查找)。OC 创建的.a库使用时无需 import 可直接使用;Swift 创建的.a库使用前需在 uts 文件中 import;HBuilderX 目前暂不支持.a库相关代码的语法提示。
关于不包含 Modules 的 framework:部分 OC 开发的第三方 SDK 产物
.framework内不含 Modules 文件夹,不支持 use module 模式,不能直接在 Swift 文件中导入,也无法直接被 uts 插件引用。有源码时可在 Xcode 中创建与 SDK target 同名的.h头文件并设为 public,或通过module.map.modulemap自定义 Module Map 后重新编译;无源码时可在TestSDK.framework文件夹下手动创建Modules/module.modulemap文件声明需要暴露的头文件(详见 uts for iOS 3.4.3 节)。
4.6 config.json:iOS 平台原生配置
config.json用于配置依赖的系统库、最低系统版本等信息,仓库中的真实示例见 腾讯定位插件 config.json:
{ "frameworks": [ "libz.1.2.5.tbd" ] }完整字段说明(详见 UTS插件介绍 中 iOS 平台原生配置一节):
| 字段 | 说明 |
|---|---|
frameworks | 可选,依赖的系统库(有.framework、.tbd、.dylib类型) |
deploymentTarget | 可选,插件支持的最低 iOS 版本,默认12.0;应设为所有依赖三方库中最低支持版本号里的最高值 |
identifier | 可选,插件单独编译为动态库的 Bundle Identifier(HBuilderX 5.0+) |
validArchitectures | 可选,支持的 CPU 架构,默认arm64 |
dependencies-pods | 可选,需要依赖的 pod 库(HBuilderX 3.8.5+) |
dependencies-pod-resources | HBuilderX 5.25+,指定 pod 库资源打包后保存位置:app保存到主应用、framework保存到 uts 插件动态库、all同时保存(uni-app 项目默认all,uni-app x 项目默认framework) |
五、iOS 平台内置库 DCloudUTSFoundation 与 UTSiOS
HBuilderX 3.6.11+ 支持
DCloudUTSFoundation为框架内置库,所有 uts 插件都会依赖此基础库,封装了一些常用方法便于开发者直接调用。使用时需先在 uts 文件中导入UTSiOS类,所有方法都通过该类调用:
import { UTSiOS } from "DCloudUTSFoundation"完整的 UTSiOS 静态方法清单可查看 UTSiOS 内置对象文档,常用方法包括:
| 方法 | 说明 |
|---|---|
getCurrentViewController(): UIViewController | 获取当前 app 显示的 UIViewController 实例 |
colorWithString(value: string): UIColor | 将字符串色值转换为 UIColor(转换失败返回黑色) |
getResourcePath(resourceName: string): string | 获取指定插件资源的运行期绝对路径 |
convert2AbsFullPath(inputPath: string): string | 将文件的项目相对地址转换为运行期绝对地址 |
getKeyWindow(): UIWindow | 获取当前 app 的 keyWindow |
getAppId()/getAppName()/getAppVersion()/getAppVersionCode()/getAppWgtVersion() | 获取 AppId、应用名称、版本名称、版本号、资源版本号 |
getDataPath()/getDeviceId()/getModel()/getOsLanguage()/getSystemSetting() | 获取 dataPath、deviceId、设备型号、系统语言、系统设置 |
isSimulator(): boolean | 是否是模拟器 |
destroyInstance(obj: AnyObject): void | 销毁指定的原生实例对象(HBuilderX 4.25+,uni-app x) |
getPointer(...) | 表示 Swift 指针操作中的&符号 |
5.1 getCurrentViewController 实战:uts-alert 插件
仓库中的 uts-alert 插件 index.uts 是 UTSiOS 最典型的应用示例——弹出系统对话框:
import { UTSiOS } from "DCloudUTSFoundation" import { DispatchQueue } from 'Dispatch'; export function showAlert(title: string|null, message: string|null, result: (index: Number) => void) { // uts方法默认会在子线程中执行,涉及 UI 操作必须在主线程中运行 DispatchQueue.main.async(execute=():void => { let alert = new UIAlertController(title=title,message=message,preferredStyle=UIAlertController.Style.alert) let okAction = new UIAlertAction(title="确认", style=UIAlertAction.Style.default, handler=(action: UIAlertAction):void => { result(0) // 点击按钮的回调方法 }) let cancelAction = new UIAlertAction(title="取消", style=UIAlertAction.Style.cancel, handler=(action: UIAlertAction):void => { result(1) }) alert.addAction(okAction) alert.addAction(cancelAction) // 打开 alert 弹窗 UTSiOS.getCurrentViewController().present(alert, animated= true) }) }这段代码同时演示了两个关键点:
- 线程模型:uts 方法默认在子线程执行,涉及 UI 操作必须通过
DispatchQueue.main.async(execute=():void => { ... })切回主线程; - 构造与命名参数:UTS 中使用
new关键字创建实例,参数用=连接(见下一节语法差异)。
5.2 colorWithString 与 getResourcePath
// 字符串色值转 UIColor,支持 #f00、#ff0000、rgb(255,0,0)、rgba(255,0,0,0.5)、red 等格式 let bgColor = UTSiOS.colorWithString("#000000") view.backgroundColor = bgColor // 获取运行期绝对路径 const imagePath = UTSiOS.getResourcePath("/static/logo.png") const image = new UIImage(contentsOfFile = imagePath) /* imagePath 示例: "/var/mobile/Containers/Data/Application/FA7080BA-.../Documents/Pandora/apps/__UNI__FB95CAB/www/static/logo.png" */六、Swift 与 UTS 差异重点(面向 Swift 开发者)
对于熟悉 iOS 开发的 Swift 语言者,UTS 在语法上有不少"习惯性差异",以下是最容易踩坑的要点(完整清单见 uts for iOS 第 5 章)。
6.1 常量和变量
// swift var str = "abc" // 变量 let str1 = "abc" // 常量// uts let str = "abc" // 变量 const str1 = "abc" // 常量6.2 可选类型
// swift var user: String? = nil// uts let user: string | null = null6.3 构造方法、函数参数、枚举值
| 语法点 | Swift | UTS |
|---|---|---|
| 实例化 | UIAlertController() | new UIAlertController()(需new) |
| 参数连接 | title: "提示"(冒号) | title="提示"(等号) |
| 枚举 | .alert可简写 | UIAlertController.Style.alert必须写全 |
UTS 目前不支持带关联值的枚举(如enum Barcode { case upc(Int, Int, Int, Int) })。若三方库中此类枚举无法改动,可在 Swift 文件中调用并打包进 framework 供 uts 插件使用;若有源码,可改为不含关联值的枚举 + 合适的数据结构表示关联信息。
6.4 类继承与协议
- 继承:Swift 用冒号
class Son: Father,UTS 用extends; - 遵循协议:Swift 用冒号
class SomeClass: FirstProtocol,UTS 用implements,可同时 implements 多个协议。
6.5 系统版本判断
Swift 的if #available(iOS 10.0, *)在 UTS 中写作:
if (UTSiOS.available("iOS 10.0, *")) { }标记 class 或函数的最低系统版本:
@available(iOS 15.0, *) class Test { test1() {} } class Test1 { @UTSiOS.available("iOS 16.1, *") test2() {} }注意:当前 uts不支持对 class 属性设置系统版本号约束。存储属性会直接编译报错Stored properties cannot be marked potentially unavailable with '@available';计算属性虽然编译不报错但约束不生效(已知问题,后续版本会修复)。
6.6 闭包、@escaping 与 target-action
- UTS 不支持 Swift 的尾随闭包简写,
handler闭包必须写完整;原生逃逸闭包参数前需加@escaping; - 调用原生 target-action 方法(如给
UIButton添加点击事件、注册通知中心事件)时:selector 通过Selector("方法名字符串")构建,定义的回调方法需要添加@objc前缀。仓库中的 uts-screenshot-listener 插件 是监听截屏事件的完整示例:
const method = Selector("userDidTakeScreenshot") NotificationCenter.default.addObserver(this, selector = method, name = UIApplication.userDidTakeScreenshotNotification, object = null) @objc static userDidTakeScreenshot() { const obj = new UTSJSONObject() this.listener?.(obj) }6.7 字典、参数标签与异步方法
- Swift 的
Dictionary在 UTS 中用Map<string, any>代替(map.set("name","uts")); - 实现三方 SDK 的协议方法时,带参数标签的方法参数需用注解
@argumentLabel("didUpdate")表示;无参数标签的参数需传空字符串@argumentLabel(""),例如高德定位的reGeocode参数; - 异步方法:Swift 在参数列表后加
async关键字,UTS 在方法最前面加async关键字。使用async定义异步方法仅 iOS 13+ 支持,低版本调用会报错。
6.8 try / try? / try! 与指针操作
UTS 通过UTSiOS.try(...)支持 Swift 的三种 try 写法:
// try:与 do-catch 配合 try { let dict = UTSiOS.try(JSONSerialization.jsonObject(with = data, options = [])) } catch (e) { console.log(e) } // try?:失败返回 nil UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []), "?") // try!:失败会闪退 UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []), "!")指针操作:Swift 中&digest隐式转换得到UnsafePointer,UTS 中用UTSiOS.getPointer(digest)表示&符号,常用于CC_MD5等 C 接口调用。
6.9 Swift 特有修饰符与 @keyword
HBuilderX 4.06+ 支持
open、fileprivate、internal、weak、optional等 Swift 特有修饰符在 ts 中没有对应物,UTS 提供@UTSiOS.keyword("xxx")语法糖,在符合 Swift 语法要求的场景下使用:
// 将一个类设为 private @UTSiOS.keyword("private") class TestA { // 用 weak 修饰属性避免循环引用 @UTSiOS.keyword("weak") private delegate: TestProtocol | null = null }6.10 显式标注类型
uts 插件环境中无法默认推断类型,需要显式标注类型,例如uni.request<any>({ ... } as RequestOptions<any>)。
七、uts 插件开发最佳实践与常见问题
7.1 interface.uts 声明与 index.uts 实现
官方推荐的多端一致性最佳实践:在插件根目录interface.uts中统一声明对外暴露的 API 类型、参数类型、返回值类型、错误码类型(建议遵循 uni 错误规范,错误码以90开头),再在各平台index.uts中做具体实现。若不跨端(如只做 iOS 插件),也可以直接在分平台目录写index.uts(详见 UTS插件介绍)。
7.2 获取当前 UIViewController 与操作 UI 线程
- 获取 UIViewController:参考 hello-uts 中的 uts-alert 插件(即上文 5.1 节示例);
- 操作 UI 线程:
DispatchQueue.main.async(execute=():void => { ... }),参考 uts-toast 插件。
7.3 销毁原生对象实例(内存管理)
HBuilderX 4.25+ 支持
uts 插件中通过export导出给 js 用的 class,创建的实例会一直被保存在内存中,不主动销毁可能造成内存泄漏。解决方案是在类中实现destory()方法调用UTSiOS.destroyInstance(this),并在使用该对象的页面unmounted()时机调用:
// uts 插件中 export class Test { id: number name: string constructor(id: number, name: string) { this.id = id this.name = name } doSomething() { console.log("do something") } destory() { UTSiOS.destroyInstance(this) } }// uvue 页面 let test = new Test("1111", "name_11111") test.doSomething() this.test = test unmounted() { this.test.destory() }7.4 避免闭包循环引用
自定义 class 中若定义了闭包类型属性,而闭包内部又访问了 class 的其他属性或自身,就会形成循环引用导致内存泄漏。解决方式是在闭包体最开头添加"[weak self]"标记:
doSomething() { if (this.callback == null) { this.callback = (res: string) => { "[weak self]" // 标记后 this 变为可空 console.log(this?.name, res) // 需用可选链或非空断言 } } this.callback?.("like basketball") }判断标准:callback 是否被 this 持有,且闭包内是否访问了 this,两条都满足就需要加标记。使用标记后this变成可为空的值,访问属性和方法必须使用可选链或非空断言。
7.5 向 js 导出 class 的三个限制
需要向 js export 并在 uvue 页面中使用的 class 有以下明确限制(详见 uts for iOS 6.9 节):
- 不支持创建单例(Swift 的
static let shared写法在 uts class 中不可用;仅在 uts 内部使用、不向 js export 的 class 可在混编 swift 中实现单例); - 函数返回值不支持直接返回 class 类型,需为该 class 创建 interface 并返回 interface 类型(规范示例见 uts for iOS 6.7 节的
RequestTask例子:在interface.uts定义 interface → 在index.uts定义实现类 → export 函数返回 interface 类型); - 函数参数不支持自定义 class 类型。
八、iOS uts 调试
uts 插件在 iOS 上的调试能力与 HBuilderX 版本相关(详见 uts iOS调试):
| 调试能力 | 版本要求 |
|---|---|
| uni-app (x) uts 插件调试(iOS 17 以下) | HBuilderX 3.7.6+ |
| uni-app (x) uts 插件调试(iOS 17 以上) | HBuilderX 4.81+ |
| uni-app x 的 jscore 调试 | HBuilderX 4.31+ |
8.1 开启调试
uni-app (x) 项目运行到 iOS 且包含 uts 插件(或使用原生工程基座),运行成功后点击 HBuilderX 控制台的红色虫子图标,下拉菜单选择【开启uts调试(swift)】或【开启uts调试(jscore)】(此功能仅 Mac 支持)。首次开启 uts 调试(swift) 需要重新编译动态库,遇到确认弹窗请点击【确定】。
8.2 断点与调试视图
在要调试的 uts 文件代码行号上,鼠标右击或双击即可添加断点。开启调试后,HBuilderX 左侧显示调试视图,分为 5 部分:调试工具栏、变量窗口、监视窗口、调用堆栈窗口、断点窗口。调试快捷键:继续F8、下一步F10、进入F11、返回Shift+F11。可在变量窗口右键将变量添加到监视,或将鼠标悬停到变量上打开悬停窗口查看值。
8.3 注意事项
- 开启 uts 调试依赖 uts 调试插件,弹窗提示安装依赖插件时务必点击安装,否则无法调试;
- uts 调试(swift) 显示连接成功后可能需要等待十几秒方可使用;调试进程
codelldb会占用较大内存;调试模式下修改 uts 插件导致重装 App 可能失败;基座重装后需重新开启调试; - jscore 调试需在手机 "设置" > "Safari" > "高级" > "Web检查器" 中打开开关;服务开启成功后,修改编译为 jscore 的代码,热更新后会自动重连调试。
九、已知待解决问题
当前 HBuilderX 写 iOS uts 插件时部分语法提示仍有缺失(如构造方法只提示一个、缺失可选类型标识、参数标签无标记、不支持导入含子模块的原生模块、暂不支持.a库代码提示);类型兼容方面元组类型目前不支持。这些问题会在后续版本中优化(详见 uts for iOS 第 7 章)。
十、继续深入
- 完整的 iOS uts 插件教程:uts for iOS(含
.a库使用、无 Modules framework 处理、AppIntents/Shortcuts 支持、pod 依赖等进阶内容) - 插件整体架构与目录规范:UTS插件介绍、uni_modules
- UTSiOS 内置对象完整 API:UTSiOS
- iOS 调试指南:uts iOS调试
- 可运行示例:
examples/hello-uts工程中的uts-alert、uts-toast、uts-tencentgeolocation、uts-screenshot-listener等插件(源码位于examples/hello-uts/uni_modules/下),是学习 iOS uts 插件开发的最佳范本
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考