简体中文
本文旨在帮助 iOS 开发者,快速上手 UTS。
需要阅读者具备 iOS 原生应用开发经验。
UTS 插件是 uni-app 新型插件形式 详情
对于 iOS 开发者来说,我们需要了解的是:
framework
依赖库framework
依赖库添加到打包工程生成最终的 ipa 包因为 UTS 语法与 swift 较类似,建议快速阅读后,在实践中掌握 UTS 语法。uts语法介绍。
objective-c
语言者尽管开发 UTS 插件,并不要求一定掌握 swift,但是鉴于 UTS 目前在 iOS 平台上,会编译为 swift 源码,掌握 swift 语言,方便排查问题和复杂功能实现。
因此建议学习一下 swift 语法,推荐阅读
UTS 和 swift 在数据类型上基本保持了一致,但是在部分场景下,还是会有差异,在此特别说明
原则上:
数据类型以 UTS 内置的类型为准, 各原生平台都会对其自动适配。
当具体平台的 api 参数无法使用 UTS 类型兼容时,允许以对方明确要求的数据类型为准。
UTS 中不存在 Int、Float、Double 类型开发者在开发过程中应该使用 Number 类型覆盖 iOS 平台上使用 Int、Float、Double 的场景
但是当开发中需要重写系统方法或实现第三方依赖库的协议方法(delegate 方法)时,比如 API 明确要求参数为 Int 时,则需要写原生的类型 Int
下面以一个协议方法为例,需要实现一个三方依赖库中定义的协议方法
// swift
// 此协议定义在其他三方 SDK 中
protocol TestProtocol {
func addTwoInts(_ a: Int, _ b: Int) -> Int
}
我们在 UTS 中需要实现上面三方库中的协议方法时,由于参数和返回值类型都要求是 Int 类型,为了适应这种情况,UTS 允许开发者使用原生平台的数据类型 Int,来满足原生 API 对数据类型的要求:
// UTS 中实现协议方法
class TestClass implements TestProtocol {
addTwoInts(a: Int, b: Int): Int {
return a + b
}
}
注意:UTS 中使用 implements
关键字表示遵循某个协议,下文会有详细说明
对于 iOS 项目来说,除了源码之外,还会涉及依赖,资源,工程配置等常见问题
本章节将会介绍,UTS插件开发环境中如何配置这些属性
注意:
当插件需要在原生工程 Info.plist 中添加配置项时,需要在插件的 app-ios 目录中创建 Info.plist 文件
以 hello uts 中的 uts-tencentgeolocation 腾讯定位插件中的配置文件为例:
示例文件在 hello uts 中的位置:
~/uni_modules/uts-tencentgeolocation/utssdk/app-ios/Info.plist
此示例表示需要在 Info.plist 中配置 APIKey 及开启后台定位权限
Info.plist 示例:
<?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>
Info.plist 格式及配置规则与 iOS 工程中是一致的,云端打包时会将配置信息合并到原生工程的 Info.plist 中
HBuilder X 3.6.11+ 版本支持
当插件需要开启 capabilities 中的相关服务时,需要在插件的 app-ios 目录中创建 UTS.entitlements 文件
比如需要在 capabilities 中勾选 Access WiFi Information 项,对应的 UTS.entitlements 的配置示例:
<?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 配置文件中
如果您的插件需要依赖资源文件比如图片,音频等,可将资源文件放到插件目录下 ~/utssdk/app-ios/Resources/
路径中
云端打包时会将此目录下的所有文件添加到应用 main bundle 中,建议只保存 uts 插件内置的资源文件。
uts 插件支持依赖三方库,目前支持 framework、xcframework、.a库
需要将依赖的framework或者xcframework文件存放到插件目录下 ~/utssdk/app-ios/Frameworks/
路径中
云端打包时会将此目录中所有的依赖库添加到工程中,建议只存放与插件相关的依赖库
以 hello uts 中的 uts-tencentgeolocation 腾讯定位插件为例,本插件需要依赖腾讯定位库 TencentLBS.framework
,则将依赖库存放到 ~/uni_modules/uts-tencentgeolocation/utssdk/app-ios/Framework/TencentLBS.framework
位置即可
└─Libs // .a库存放目录
├─MyStaticLibA //A静态库(该库所有文件放在此文件夹内,OC库)
│ ├─libMyStaticLib.a //.a文件,必须
│ ├─MyStaticLib.h //A.a库对应的头文件,必须
│ ├─MyClassA.h //需要暴露的头文件A,可选
│ └─MyClassB.h //需要暴露的头文件B,可选
└─TestSwiftLibrary //B静态库(该库所有文件放在此文件夹内,Swift库)
├─libTestSwiftLibrary.a //.a文件,必须
└─TestSwiftLibrary.swiftmodule //.swiftmodule文件夹,必须
注意:
// uts
const aResult = ToolA.toolAMethod();
const bResult = ToolB.toolBMethod();
const libResult = TestLib.testLib();
const res = {
aResult: aResult,
bResult: bResult,
libResult: libResult
};
options.success?.(res);
// uts
import { Tool, Manager, TestLibraryExa } from 'TestLibraryExa';
Manager.testManager();
Tool.testTool()
let lib = TestLibraryExa();
lib.test()
console.log(lib.version);
有些第三方的 SDK 使用 OC 语言开发,且产物 .framework 文件里不包含 Moudules 文件夹。这就造成该 SDK 不支持 use module 模式,不能直接在 Swift 文件中导入。 这样的 SDK 不能直接被 uts 插件引用,需要做以下处理:
如果你有 SDK 的源码,那么有以下几种方法可以生成 Modules。
以名称为 TestSDK
的 framework 为例:
通过创建和 SDK 的 target 同名的头文件方式:
.h
文件,如 TestSDK.h
;TestSDK.h
中,如 #import <TestSDK/TestA.h>
;target
-> Build Phases
-> Headers
中将刚创建的 TestSDK.h
设置为 public
;通过自定义 Module Map 的方式:
TestSDK
SDK 源码目录下创建 module.map.modulemap
文件;TestSDK
为例,实践时需要改为自己的 SDK 和文件名;target
-> Build Settings
-> Module Map File
设置 Module Map File
为 $(PROJECT_DIR)/TestSDK/module.map.modulemap
;framework module TestSDK {
header "TestA.h" //需要对外暴露的头文件,需要为 plubic 的文件
header "TestB.h" //需要对外暴露的头文件,需要为 plubic 的文件
export *
}
如果使用的是第三方非开源的 SDK, 那么可以使用下面的方式来生成 Modules:
以 TestSDK
为例:
TestSDK.framework
文件夹下创建 Modules
文件夹;Modules
文件夹下创建 module.modulemap
文件;注意: 实践时要将
TestSDK
改成你要操作的 SDK 名称,.h 文件也要改成你要暴露的头文件名字。
framework module TestSDK {
// 下面的.h 文件都要是 TestSDK.framework -> Headers 文件夹下的头文件
header "AClass.h"
header "BClass.h"
header "CClass.h"
header "DClass.h"
export *
}
HBuilder X 3.6.11+ 版本支持
DCloudUTSFoundation 为框架内置库,所有 uts 插件都会依赖此基础库
DCloudUTSFoundation 会封装一些常用方法便于开发者直接调用
使用时需要在 uts 文件中先导入 UTSiOS 类,所有方法都通过 UTSiOS 类调用
// 从 DCloudUTSFoundation 依赖库中导入 UTSiOS 类
import { UTSiOS } from "DCloudUTSFoundation"
HBuilder X 3.6.11+ 版本支持
获取当前 app 显示的 UIViewController 实例
以 hello uts 中的 uts-alert 为例:
示例文件在 hello uts 中的位置:
~/uni_modules/uts-alert/utssdk/app-ios/index.uts
export function showAlert(title: string|null, message: string|null, result: (index: Number) => void) {
// uts方法默认会在子线程中执行,涉及 UI 操作必须在主线程中运行,通过 DispatchQueue.main.async 方法可将代码在主线程中运行
DispatchQueue.main.async(execute=():void => {
// 初始化 UIAlertController 实例对象 alert
let alert = new UIAlertController(title=title,message=message,preferredStyle=UIAlertController.Style.alert)
// 创建 UIAlertAction 按钮
let okAction = new UIAlertAction(title="确认", style=UIAlertAction.Style.default, handler=(action: UIAlertAction):void => {
// 点击按钮的回调方法
result(0)
})
// 创建 UIAlertAction 按钮
let cancelAction = new UIAlertAction(title="取消", style=UIAlertAction.Style.cancel, handler=(action: UIAlertAction):void => {
// 点击按钮的回调方法
result(1)
})
// 将 UIAlertAction 添加到 alert 上
alert.addAction(okAction)
alert.addAction(cancelAction)
// 打开 alert 弹窗
UTSiOS.getCurrentViewController().present(alert, animated= true)
})
}
HBuilder X 3.6.11+ 版本支持
将字符串色值转换为 UIColor
格式支持
示例
let bgColor = UTSiOS.colorWithString("#000000")
view.backgroundColor = bgColor
HBuilder X 3.6.11+ 版本支持
获取指定插件资源的运行期绝对路径
插架资源路径请传该资源在工程目录下的绝对路径
示例
const imagePath = UTSiOS.getResourcePath("/static/logo.png")
console.log(imagePath)
const image = new UIImage(contentsOfFile = imagePath)
/* imagePath: "/var/mobile/Containers/Data/Application/FA7080BA-3EF7-4C4E-B7C5-0332539B2964/Documents/Pandora/apps/__UNI__FB95CAB/www/static/logo.png" */
持续更新中
通过上面的章节的阅读。
至此我们认为你已经掌握了UTS语法,掌握了基本的 swift 语法,掌握了 UTS 对于 iOS 资源的支持。
但是对于一个熟悉 iOS 开发的 swift 语言者来说,有很多常用的习惯发生了改变,我们会在这个章节特别指出,便于开发者加深认识。
swift 中用 let
来声明常量,用 var
来声明变量
// swift
var str = "abc" // 声明一个字符串变量
let str1 = "abc" // 声明一个字符串常量
uts
中用 const
来声明常量,用 let
来声明变量
// swift
let str = "abc" // 声明一个字符串变量
const str1 = "abc" // 声明一个字符串常量
swift 中的可选类型定义为 类型?
// swift
var user: String? = nil
uts 中可选类型的定义为 类型 | null
// uts
let user: string | null = null
swift 中调用构造方法创建实例对象时不需要使用 new
关键字
var alert = UIAlertController()
uts 中调用构造方法实例化对象时需要在构造方法前加上 new
关键字
var alert = new UIAlertController()
在 swift 中参数名称使用 :
连接参数值,在 uts 中需要使用 =
连接
示例
// swift
var alert = UIAlertController(title: "提示", message: "提示内容", preferredStyle: .alert);
// uts 中写法
let alert = new UIAlertController(title="提示", message="提示内容", preferredStyle=UIAlertController.Style.alert)
枚举在 swift 中可以忽略枚举类型直接简写 .枚举值
,在 uts 中不支持简写,需要完整的写出 枚举类型.枚举值
上面的示例中 swift 中最后一个参数 preferredStyle 的值可以简写为
.alert
在 uts 中需要完整的写出
UIAlertController.Style.alert
枚举在 swift 中可以定义关联类型,可在关联值中传递信息,但是在 ts 中没有这种语法,所以目前 uts 还暂不支持此种用法。
// 定义带关联值的枚举
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
// 定义枚举值
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
// 匹配
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
如果遇到上述类型的枚举,且在三方库中无法改动的,可以在 Swift 文件中进行调用,然后把 该 Swift 文件打包在 framework 中供 uts 插件使用; 如果上述类型的枚举定义在有源码的 swift 中时,可以将其定义成不包含关联值的枚举,然后使用合适的数据结构来表示关联值携带的信息。
swift 中定义子类继承父类时需要在子类名称后加上父类名称,中间以冒号:
分隔
// swift
class Son: Father {
}
uts 中需要使用extends
关键字代替冒号:
// uts
class Son extends Father {
}
swift 中要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号:
分隔。遵循多个协议时,各协议之间用逗号,
分隔:
class SomeClass: FirstProtocol, AnotherProtocol {
}
uts 中需要使用implements
关键字代替冒号 :
class SomeClass implements FirstProtocol, AnotherProtocol {
}
swift 中系统版本判断的方法
// swift
if #available(iOS 10.0, *) {
}
在 uts 中不支持这种语法可使用下面方式代替
if (UTSiOS.available("iOS 10.0, *")) {
}
swift 中闭包可以简写
// swift 中最后一个参数如果是闭包称作为尾随闭包,可以忽略参数标签类型等简写为下面的方式
let action = UIAlertAction(title: "确认", style: .default) { action in
}
uts 中不支持简写语法,需要完整的写出闭包函数
// uts 中 handler 对应的闭包函数必须写完整
let action = new UIAlertAction(title="确认", style=UIAlertAction.Style.default, handler=(action: UIAlertAction):void => {
})
原生中有些方法的闭包参数是逃逸闭包,此时就要在闭包前面添加 @escaping
标记:
// 在闭包参数前添加@escaping
function requestLocationPromise(@escaping completion: (res: boolean)=>void) {
}
uts 中调用原生中涉及 target-action 的方法时,比如给UIButton
添加点击事件方法、注册通知中心事件方法时注意事项,
下面以监听截屏事件为例:
示例文件在 hello uts 中的位置:
~/uni_modules/uts-screenshot-listener/utssdk/app-ios/index.uts
// 注册监听截屏事件及回调方法
// target-action 回调方法需要通过 Selector("方法名") 构建
const method = Selector("userDidTakeScreenshot")
NotificationCenter.default.addObserver(this, selector = method, name = UIApplication.userDidTakeScreenshotNotification, object = null)
// 捕获截屏回调的方法
// target-action 的方法前需要添加 @objc 前缀
@objc static userDidTakeScreenshot() {
const obj = new UTSJSONObject()
// 回调
this.listener?.(obj)
}
swift 中的 Dictionary 类型,在 uts 中使用 Map 类型代替
// swift
var value: Dictionary<String,Any> = Dictionary()
value["name"] = "uts"
// uts
let map: Map<string,any> = new Map()
map.set("name","uts")
HBuilder X 3.6.11+ 版本支持
当覆写系统方法,或实现三方SDK的协议方法时,一些方法可能会存在参数标签的情况
以 hello uts 中腾讯定位为例,监听位置变化时需要实现协议方法:
tencentLBSLocationManager(_ manager: TencentLBSLocationManager, didUpdate location: TencentLBSLocation)
此方法第二个参数存在 didUpdate
参数标签
原生 swift 中的实现为
// swift
func tencentLBSLocationManager(_ manager: TencentLBSLocationManager, didUpdate location: TencentLBSLocation) {
var response = LocationResponse();
response.name = location.name;
response.address = location.address;
response.latitude = NSNumber(location.location.coordinate.latitude);
response.longitude = NSNumber(location.location.coordinate.longitude);
self.locationOptions?.success(response);
}
uts 中需要用注解语法 @argumentLabel("didUpdate") 来表示参数标签
// uts
// 实现位置更新的 delegate 方法
tencentLBSLocationManager(manager: TencentLBSLocationManager, @argumentLabel("didUpdate") location: TencentLBSLocation) {
let response = new LocationResponse();
response.name = location.name;
response.address = location.address;
response.latitude = Number(location.location.coordinate.latitude);
response.longitude = Number(location.location.coordinate.longitude);
this.locationOptions?.success(response)
}
示例文件在 hello uts 中的位置:
~/uni_modules/uts-tencentgeolocation/utssdk/app-ios/index.uts
只写参数名称的参数,编译后会在参数前默认增加 _
来忽略参数标签(如上面的示例,第一个参数 manager,这种方式能兼容绝大多数方法,尤其是Swift 调用 OC 方法),但是有些参数没有参数标签,默认添加 _
的行为会和原生方法定义不一致,这种情况需要定义一个空的参数标签来解决 @argumentLabel("didUpdate")
以高德定位 SDK 的代理方法为例:第三个参数 reGeocode 只有参数名称,没有参数标签
// swift
func amapLocationManager(_ manager: AMapLocationManager!, didUpdate location: CLLocation!, reGeocode: AMapLocationReGeocode!)
uts 实现此方法时,需要给 reGeocode 参数添加一个空的参数标签
// uts
amapLocationManager(manager : AMapLocationManager, @argumentLabel("didUpdate") location : CLLocation, @argumentLabel("") reGeocode : AMapLocationReGeocode) {
}
swift 标记某个函数或者方法是异步的,你可以在它的声明中的参数列表后边加上 async
关键字
// swift
@available(iOS 13.0.0, *)
func testAsync(_ opts: AsyncOptions) async -> UTSJSONObject {
if (opts.type == "success") {
opts.success("success");
}
else {
opts.fail("fail");
}
opts.complete("complete");
return UTSJSONObject([
"name": "testAsync"
]);
}
uts 中定义异步方法是在方法最前面加上 async
关键字
// uts
async function testAsync(opts: AsyncOptions) {
if (opts.type == "success") {
opts.success("success");
} else {
opts.fail("fail");
}
opts.complete("complete");
return { name: "testAsync" };
}
需要注意:使用 async 定义异步方法只有 iOS 13+ 版本才支持,低版本调用会报错
swift中try有以下三种方式:
以JSON反序列化为例
// swift
do{
let dict = try JSONSerialization.jsonObject(with: d, options: [])
print(dict)
}catch{
// catch 中默认提供error信息, 当序列化不成功是, 返回error
print(error)
}
// swift
// 注意:dict是个可选值
let dict = try? JSONSerialization.jsonObject(with: data, options: [])
// swift
// 注意:dict是个可选值
let dict = try! JSONSerialization.jsonObject(with: data, options: [])
为了满足Swift上述语法,UTS使用特殊语法来支持,以上三种写法分别对应为:
// uts
try {
let dict = UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []))
}catch (e) {
console.log(e)
}
// uts
UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []), "?" )
// uts
UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []), "!" )
HBuilder X 4.06+ 版本支持
swift
中有一些特有的属性、方法、类的修饰符如 open
, fileprivate
, internal
, weak
, optional
等,这些修饰符在 ts
中没有对应的替代者,但是在原生语法场景下又是必须的。
为了支持这些修饰符,我们提供了 UTSiOS.keyword("xxx")
的语法糖。你可以在符合 swift
语法要求的场景下,使用诸如 UTSiOS.keyword("private")
这样的语法来修饰对应的属性、方法、类等。
我们对具体的修饰符没有做特别的限制,swfit
中的合法修饰符都能通过这个语法糖来使用,但是请记住一个重要的前提:你所使用的修饰符,需要满足 swift
语法所要求的场景。
下面是一些示例:
// ts 中 private 修饰符不可出现在模块或命名空间元素上, 如果想将一个类 private,需要使用 @UTSiOS.keyword("private")
@UTSiOS.keyword("private")
class TestA {
// 如果需要使用 weak 修饰属性,来避免循环引用,需要使用 @UTSiOS.keyword("weak")
@UTSiOS.keyword("weak")
private delegate: TestProtocol | null = null
}
@UTSiOS.keyword("fileprivate")
class TestB {
// 一个属性可以同时有多个修饰符,前提是所写的修饰符符合Swift语法
@UTSiOS.keyword("weak")
@UTSiOS.keyword("fileprivate")
delegate: TestProtocol | null = null
}
在Swift操作指针,需要用到&
操作符隐式转换得到UnsafePointer
类型,UTS中提供了UTSiOS.getPointer()
来表示&
符号。
以获取字符串MD5为例,如下
private convertToMD5(param : string) : string {
const strData = param.data(using = String.Encoding.utf8)!
let digest = new Array<UInt8>(repeating = 0, count = new Int(CC_MD5_DIGEST_LENGTH))
strData.withUnsafeBytes((body : UnsafeRawBufferPointer) => {
CC_MD5(body.baseAddress, new UInt32(strData.count), UTSiOS.getPointer(digest))
})
let md5String = ""
digest.forEach((byte : UInt8) => {
md5String += new String(format = "%02x", new UInt8(byte))
})
return md5String
}
其中UTSiOS.getPointer(digest)
编译后会变成&digest
参考 Hello UTS 项目中的 uts-alert 插件
路径:
~/uni_modules/uts-alert/utssdk/app-ios/index.uts
DispatchQueue.main.async(execute=():void => {
// 在主线程中可操作 UI
})
参考Hello UTS项目中的 uts-toast 插件
路径:
~/uni_modules/uts-toast/utssdk/app-ios/index.uts
由于在uts插件环境,无法默认推断出类型,所以需要显示类型,以uni.request()
为例:
uni.request<any>({
url: "http://xxx",
method: "GET",
success: (e : RequestSuccess<any>) => {
},
fail(e : RequestFail) {
},
} as RequestOptions<any>)
在 uts 插件中通过 export
导出给 js
用的 class
, 创建出的 class 实例会被一直保存在内存中,如果不主动销毁,可能会造成
内存泄漏的问题。我们在 UTSiOS 类型上提供了 destroyInstance()
的静态方法来实现销毁原生对象的功能。开发者需要在使用这个对象的页面的
unmounted()
时机将对象销毁。
HBuilder X 4.25+ 版本支持
具体使用示例如下:
在 uts 插件中定义 class Test,并将其 export
:
// uts 插件中 export class
export class Test {
id : number;
name : string;
constructor(id : number, name : string) {
this.id = id;
this.name = name;
}
doSomething() {
console.log("do something");
}
// 实现 destory 方法
destory() {
UTSiOS.destroyInstance(this)
}
}
在 uvue
页面中使用:
// 创建 test 对象,并调用方法
let test = new Test("1111", "name_11111");
test.doSomething();
this.test = test;
在 uvue
页面 unmounted
时销毁对象:
//
unmounted() {
this.test.destory()
}
在 uts 插件或者组件中,如果自定义的 class
中定义了闭包类型的属性,而闭包内部又使用了 class
的其他属性或者 class
自身,
就会造成对象循序引用,导致内存泄漏。为避免产生循环引用我们需要在闭包内使用 "[weak self]"
标记。
具体示例如下:
export class Test {
id: number
name: string
callback: ((res: string) => void) | null = null
constructor(id: number, name: string) {
this.id = id
this.name = name
}
doSomething() {
console.log("do something");
if (this.callback == null) {
this.callback = (res: string) => {
"[weak self]"
console.log(this?.name, res)
}
}
this.callback?.("like basketball")
}
destory() {
UTSiOS.destroyInstance(this)
}
}
上述示例中,自定义类 Test
中持有一个 callback
的闭包属性,而 callback
闭包实现中有引用了 this.name
,这就导致了循环引用。
因此在 callback
闭包体的最开头部分使用了 "[weak self]"
标记,避免产生循环引用。
特别注意:
使用
"[weak self]"
标记以后,this 就变成了可为空的值,访问标记后的this
的任何属性和方法都要使用可选链或者做非空断言。
判断是否需要加
"[weak self]"
标记的标准是:callback 是否被 this 持有,闭包内是否访问了 this,如果满足这两条就需要加。
HBuilderX 目前写iOS uts 插件时部分语法提示会有缺失、参数类型不准确的问题,例如:
这些问题会在后续版本中优化
特别注意 如果在使用真机运行编译uts插件时报 swift 版本不兼容的错误,请先检查自己的 XCode 版本,确保安装XCode 版本应大于或者等于打包机的 XCode版本; 如果在使用真机运行编译uts插件时报 XCode 版本应大于 13.2.1的错误,这是说明本地安装的 XCode版本过低,请忽略 13.2.1这个版本限制,直接将本地 XCode升级到大于或者等于打包机的版本,后续我们会优化提示。