## uts for iOS

本文旨在帮助 iOS 开发者，快速上手 UTS。

需要阅读者具备 iOS 原生应用开发经验。

## 1 了解 UTS 插件是什么

UTS 插件是 uni-app 新型插件形式 [详情](/plugin/uts-plugin)

对于 iOS 开发者来说，我们需要了解的是：

1. 编译时：当我们在保存 UTS 源码文件时，IDE 会同步将其编译为对应的 swift 代码，并且会生成一个对应的插件 Framework 工程在编译出对应的`framework`依赖库
2. 运行时：在真机运行/云打包时，会将`framework`依赖库添加到打包工程生成最终的 ipa 包

## 2 掌握UTS语法

### 2.1  对于掌握 swift 语言者

因为 UTS 语法与 swift 较类似，建议快速阅读后，在实践中掌握 UTS 语法。[uts语法介绍](/uts/)。

### 2.2  对于仅掌握`objective-c`语言者

尽管开发 UTS 插件，并不要求一定掌握 swift，但是鉴于 UTS 目前在 iOS 平台上，会编译为 swift 源码，掌握 swift 语言，方便排查问题和复杂功能实现。

因此建议学习一下 swift 语法，推荐阅读

+ [swift 官方文档](https://docs.swift.org/swift-book/)
+ [swift 中文版](https://swiftgg.gitbook.io/swift/)

### 2.3 数据类型差异

UTS 和 swift 在数据类型上基本保持了一致，但是在部分场景下，还是会有差异，在此特别说明

原则上：

**数据类型以 UTS 内置的类型为准， 各原生平台都会对其自动适配。**

**当具体平台的 api 参数无法使用 UTS 类型兼容时，允许以对方明确要求的数据类型为准。**

-------------------------


#### 举例一： Int、Float、Double 和 Number

UTS 中不存在 Int、Float、Double 类型开发者在开发过程中应该使用  Number 类型覆盖 iOS 平台上使用 Int、Float、Double 的场景

但是当开发中需要重写系统方法或实现第三方依赖库的协议方法（delegate 方法）时，比如 API 明确要求参数为 Int 时，则需要写原生的类型 Int

下面以一个协议方法为例，需要实现一个三方依赖库中定义的协议方法

```swift
// swift
// 此协议定义在其他三方 SDK 中
protocol TestProtocol {
   func addTwoInts(_ a: Int, _ b: Int) -> Int
}
```

我们在 UTS 中需要实现上面三方库中的协议方法时，由于参数和返回值类型都要求是 Int 类型，为了适应这种情况，UTS 允许开发者使用原生平台的数据类型 Int，来满足原生 API 对数据类型的要求：

```ts
// UTS 中实现协议方法
class TestClass implements TestProtocol {
	addTwoInts(a: Int, b: Int): Int {
		return a + b
	}
}
```

注意：UTS 中使用 `implements` 关键字表示遵循某个协议，下文会有详细说明

## 3 iOS 原生环境配置

对于 iOS 项目来说，除了源码之外，还会涉及依赖，资源，工程配置等常见问题

本章节将会介绍，UTS插件开发环境中如何配置这些属性

注意：

+ 1 本章节内的实例代码均取自Hello UTS [项目地址](https://gitcode.net/dcloud/hello-uts)
+ 2 本章节设计的配置，均需自定义基座后才能生效

### 3.1 配置 Info.plist

当插件需要在原生工程 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
<?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 中

### 3.2 配置 entitlements
> HBuilder X 3.6.11+ 版本支持

当插件需要开启 capabilities 中的相关服务时，需要在插件的 app-ios 目录中创建 UTS.entitlements 文件

比如需要在 capabilities 中勾选 Access WiFi Information 项，对应的 UTS.entitlements 的配置示例：

```xml
<?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 配置文件中

### 3.3 依赖资源文件

如果您的插件需要依赖资源文件比如图片，音频等，可将资源文件放到插件目录下  `~/utssdk/app-ios/Resources/`路径中

云端打包时会将此目录下的所有文件添加到应用 main bundle 中，建议只保存 uts 插件内置的资源文件。

### 3.4 依赖三方库

uts 插件支持依赖三方库，目前支持 framework、xcframework、.a库

#### 3.4.1 framework依赖库说明

需要将依赖的framework或者xcframework文件存放到插件目录下  `~/utssdk/app-ios/Frameworks/`路径中

云端打包时会将此目录中所有的依赖库添加到工程中，建议只存放与插件相关的依赖库

以 hello uts 中的 uts-tencentgeolocation 腾讯定位插件为例，本插件需要依赖腾讯定位库 `TencentLBS.framework`，则将依赖库存放到 `~/uni_modules/uts-tencentgeolocation/utssdk/app-ios/Framework/TencentLBS.framework` 位置即可

#### 3.4.2 .a依赖库相关说明
##### 3.4.2.1 .a库存放的目录结构

<pre v-pre="" data-lang="">
	<code class="lang-" style="padding:0">
└─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文件夹，必须
</code>
</pre>

注意：
- 将.a库的所有文件存放在一个文件夹内，多个.a库就创建多个文件夹；
- 未对某个.a库文件夹下的文件做递归查找，请不要将.a或.h文件嵌套在多层文件夹内，以免发生错误；

##### 3.4.2.2 .a库的使用说明

- OC语言创建的.a库在使用时无需import,可直接使用；
- Swift语言创建的.a库在使用前需要在uts文件中import；
- HBuilder X目前暂不支持.a库相关代码的语法提示；

##### 3.4.2.3 .a库的使用示例

- OC语言创建的.a库使用示例：

```ts
// uts
const aResult = ToolA.toolAMethod();
const bResult = ToolB.toolBMethod();
const libResult = TestLib.testLib();

const res = {
	aResult: aResult,
	bResult: bResult,
	libResult: libResult
};
options.success?.(res);
```

- Swift语言创建的.a库使用示例：

```ts
// uts
import { Tool, Manager, TestLibraryExa } from 'TestLibraryExa';

Manager.testManager();
Tool.testTool()
let lib = TestLibraryExa();
lib.test()
console.log(lib.version);
```

#### 3.4.3 不包含 Modules 的 framework 使用说明@nomodules

有些第三方的 SDK 使用 OC 语言开发，且产物 .framework 文件里不包含 Moudules 文件夹。这就造成该 SDK 不支持 use module 模式，不能直接在 Swift 文件中导入。
这样的 SDK 不能直接被 uts 插件引用，需要做以下处理：

##### 有源码的情况

如果你有 SDK 的源码，那么有以下几种方法可以生成 Modules。
以名称为 `TestSDK` 的 framework 为例：

+ 通过创建和 SDK 的 target 同名的头文件方式：
	- 打开 XCode, 创建一个和 SDK target 同名的 `.h` 文件，如 `TestSDK.h`；
	- 将需要暴露的 public 文件 导入到 `TestSDK.h` 中，如 `#import <TestSDK/TestA.h>`;
	- 在 `target` -> `Build Phases` -> `Headers` 中将刚创建的 `TestSDK.h` 设置为 `public`;
	- 重新编译 SDK, 编译后可以看到 Modules 已经生成。

+ 通过自定义 Module Map 的方式：
	- 打开 XCode, 在 `TestSDK` SDK 源码目录下创建 ` module.map.modulemap ` 文件；
	- 在上述文件中键入下面代码中类似的内容，下述代码是以 `TestSDK` 为例，实践时需要改为自己的 SDK 和文件名；
	- 在 `target` -> `Build Settings` -> `Module Map File` 设置 `Module Map File` 为 `$(PROJECT_DIR)/TestSDK/module.map.modulemap`;
	- 重新编译 SDK, 编译后可以看到 Modules 已经生成。

```ts
framework module TestSDK {
    header "TestA.h"   //需要对外暴露的头文件，需要为 plubic 的文件

	header "TestB.h"  //需要对外暴露的头文件，需要为 plubic 的文件

    export *
}
```

##### 无源码的情况

如果使用的是第三方非开源的 SDK, 那么可以使用下面的方式来生成 Modules:
以 `TestSDK` 为例：

+ 在 `TestSDK.framework` 文件夹下创建 `Modules` 文件夹；
+ 在 `Modules` 文件夹下创建 `module.modulemap` 文件；
+ 在上述文件中键入下述的代码(其中的 .h 文件都要是 TestSDK.framework -> Headers 文件夹里的头文件)。
+ 至此 TestSDK 就可以直接放在 uts 插件中使用了。

> 注意：
> 实践时要将 `TestSDK` 改成你要操作的 SDK 名称，.h 文件也要改成你要暴露的头文件名字。

```ts
framework module TestSDK {
	// 下面的.h 文件都要是 TestSDK.framework -> Headers 文件夹下的头文件
    header "AClass.h"

    header "BClass.h"

    header "CClass.h"

    header "DClass.h"

    export *
}

```



## 4 iOS 平台内置库 DCloudUTSFoundation
> HBuilder X 3.6.11+ 版本支持

DCloudUTSFoundation 为框架内置库，所有 uts 插件都会依赖此基础库

DCloudUTSFoundation 会封装一些常用方法便于开发者直接调用

使用时需要在 uts 文件中先导入 UTSiOS 类，所有方法都通过 UTSiOS 类调用

```ts
// 从 DCloudUTSFoundation 依赖库中导入 UTSiOS 类
import { UTSiOS } from "DCloudUTSFoundation"
```

### 4.1 getCurrentViewController(): UIViewController
> HBuilder X 3.6.11+ 版本支持

获取当前 app 显示的 UIViewController 实例

以 hello uts 中的 uts-alert 为例：

示例文件在 hello uts 中的位置：

`~/uni_modules/uts-alert/utssdk/app-ios/index.uts`

```ts
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)
	})
}
```


#### 4.2 colorWithString(value: string): UIColor
> HBuilder X 3.6.11+ 版本支持

将字符串色值转换为 UIColor

格式支持

- 精简写法的十六进制 如：#f00
- 十六进制 如：#ff0000
- RGB 如：rgb(255, 0, 0)
- RGBA 如:rgba(255, 0, 0, 0.5)
- 色值关键字，如： red

示例

```ts
let bgColor = UTSiOS.colorWithString("#000000")
view.backgroundColor = bgColor
```

#### 4.3 getResourcePath(resourceName: string): string
> HBuilder X 3.6.11+ 版本支持

获取指定插件资源的运行期绝对路径

插架资源路径请传该资源在工程目录下的绝对路径

示例

```ts
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" */

```

持续更新中

## 5 swift 与 UTS 差异重点介绍 (持续更新)

通过上面的章节的阅读。

至此我们认为你已经掌握了UTS语法，掌握了基本的 swift 语法，掌握了 UTS 对于 iOS 资源的支持。

但是对于一个熟悉 iOS 开发的 swift 语言者来说，有很多常用的习惯发生了改变，我们会在这个章节特别指出，便于开发者加深认识。


### 5.1 语法差异

-------------------------------

#### 5.1.1  常量和变量

swift 中用 `let` 来声明常量，用 `var` 来声明变量

```swift
// swift
var str = "abc" // 声明一个字符串变量
let str1 = "abc" // 声明一个字符串常量
```

`uts`中用 `const` 来声明常量，用 `let` 来声明变量

```ts
// swift
let str = "abc" // 声明一个字符串变量
const str1 = "abc" // 声明一个字符串常量
```

#### 5.1.2 可选类型

swift 中的可选类型定义为 `类型?`

```swift
// swift
var user: String? = nil
```

 uts 中可选类型的定义为 `类型 | null`

```ts
// uts
let user: string | null = null
```

#### 5.1.3 调用构造方法

swift 中调用构造方法创建实例对象时不需要使用 `new` 关键字

```swift
var alert = UIAlertController()
```

uts 中调用构造方法实例化对象时需要在构造方法前加上 `new` 关键字

```ts
var alert = new UIAlertController()
```


#### 5.1.4 函数参数

在 swift 中参数名称使用 `:` 连接参数值，在 uts 中需要使用 `=` 连接

示例

```swift
// swift
var alert = UIAlertController(title: "提示", message: "提示内容", preferredStyle: .alert);
```


```ts
// uts 中写法
let alert = new UIAlertController(title="提示", message="提示内容", preferredStyle=UIAlertController.Style.alert)
```

#### 5.1.5 枚举值

枚举在 swift 中可以忽略枚举类型直接简写 `.枚举值` ，在 uts 中不支持简写，需要完整的写出 `枚举类型.枚举值`
上面的示例中 swift 中最后一个参数 preferredStyle 的值可以简写为

```swift
.alert
```

在 uts 中需要完整的写出

```ts
UIAlertController.Style.alert
```

枚举在 swift 中可以定义关联类型，可在关联值中传递信息，但是在 ts 中没有这种语法，所以目前 uts 还暂不支持此种用法。

```swift

// 定义带关联值的枚举
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 中时，可以将其定义成不包含关联值的枚举，然后使用合适的数据结构来表示关联值携带的信息。


#### 5.1.6 类继承

swift 中定义子类继承父类时需要在子类名称后加上父类名称，中间以冒号`:`分隔

```swift
// swift
class Son: Father {

}
```

uts 中需要使用`extends`关键字代替冒号`:`

```ts
// uts
class Son extends Father {

}
```

#### 5.1.7 遵循协议方法

swift 中要让自定义类型遵循某个协议，在定义类型时，需要在类型名称后加上协议名称，中间以冒号`:`分隔。遵循多个协议时，各协议之间用逗号`,`分隔：

```swift
class SomeClass: FirstProtocol, AnotherProtocol {

}
```

uts 中需要使用`implements`关键字代替冒号 `:`

```ts
class SomeClass implements FirstProtocol, AnotherProtocol {

}
```

#### 5.1.8 系统版本判断

swift 中系统版本判断的方法

```swift
// swift
if #available(iOS 10.0, *) {

}
```

在 uts 中不支持这种语法可使用下面方式代替

```ts
if (UTSiOS.available("iOS 10.0, *")) {

}
```

#### 5.1.9 闭包

swift 中闭包可以简写

```swift
// swift 中最后一个参数如果是闭包称作为尾随闭包，可以忽略参数标签类型等简写为下面的方式
let action = UIAlertAction(title: "确认", style: .default) { action in

}
```

uts 中不支持简写语法，需要完整的写出闭包函数

```ts
// uts 中 handler 对应的闭包函数必须写完整
let action = new UIAlertAction(title="确认", style=UIAlertAction.Style.default, handler=(action: UIAlertAction):void => {

})
```

原生中有些方法的闭包参数是逃逸闭包，此时就要在闭包前面添加 `@escaping` 标记：

```ts
// 在闭包参数前添加@escaping
function requestLocationPromise(@escaping completion: (res: boolean)=>void) {

}
```


#### 5.1.10 target-action 方法

uts 中调用原生中涉及 target-action 的方法时，比如给`UIButton`添加点击事件方法、注册通知中心事件方法时注意事项，

1. 接口要求的 selector 通过 Selector("方法名字符串") 的方法构建
2. 定义的回调方法需要添加 @objc 前缀

下面以监听截屏事件为例：

示例文件在 hello uts 中的位置：

`~/uni_modules/uts-screenshot-listener/utssdk/app-ios/index.uts`

```ts
// 注册监听截屏事件及回调方法
// 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)
}
```

#### 5.1.11 字典类型

swift 中的 Dictionary 类型，在 uts 中使用 Map 类型代替

```swift
// swift
var value: Dictionary<String,Any> = Dictionary()
value["name"] = "uts"
```

```ts
// uts
let map: Map<string,any> = new Map()
map.set("name","uts")
```
#### 5.1.12 参数标签的兼容问题
> HBuilder X 3.6.11+ 版本支持

当覆写系统方法，或实现三方SDK的协议方法时，一些方法可能会存在参数标签的情况

以 hello uts 中腾讯定位为例，监听位置变化时需要实现协议方法：

 `tencentLBSLocationManager(_ manager: TencentLBSLocationManager, didUpdate location: TencentLBSLocation)`

 此方法第二个参数存在 `didUpdate` 参数标签

原生 swift 中的实现为

```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") 来表示参数标签

```ts
// 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`

#### 5.1.12.1 无参数标签

只写参数名称的参数，编译后会在参数前默认增加 `_` 来忽略参数标签（如上面的示例，第一个参数 manager，这种方式能兼容绝大多数方法，尤其是Swift 调用 OC 方法），但是有些参数没有参数标签，默认添加 `_` 的行为会和原生方法定义不一致，这种情况需要定义一个空的参数标签来解决 `@argumentLabel("didUpdate")`

以高德定位 SDK 的代理方法为例：第三个参数 reGeocode 只有参数名称，没有参数标签

```swift
// swift
func amapLocationManager(_ manager: AMapLocationManager!, didUpdate location: CLLocation!, reGeocode: AMapLocationReGeocode!)
```

uts 实现此方法时，需要给 reGeocode 参数添加一个空的参数标签

```ts
// uts
amapLocationManager(manager : AMapLocationManager, @argumentLabel("didUpdate") location : CLLocation, @argumentLabel("") reGeocode : AMapLocationReGeocode) {

}
```

#### 5.1.13 异步方法 @async-method

swift 标记某个函数或者方法是异步的，你可以在它的声明中的参数列表后边加上 `async` 关键字

```swift
// 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` 关键字

```ts
// 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+ 版本才支持，低版本调用会报错**


#### 5.1.14 try catch @try

swift中try有以下三种方式:

以JSON反序列化为例

1. 使用try (注意：要和do {} catch {} 一起使用，捕获可能的异常)

```swift
// swift
	do{
		let dict = try JSONSerialization.jsonObject(with: d, options: [])
		print(dict)
	}catch{
	   // catch 中默认提供error信息, 当序列化不成功是, 返回error
		print(error)
	}

```

2. 使用try? 如果能发序列化成功，就返回成功的值，不能成功就返回nil

```swift
// swift
// 注意：dict是个可选值
	let dict = try? JSONSerialization.jsonObject(with: data, options: [])

```

3. 使用try! 强行try,如果不能反序列化成功，会造成应用闪退, 如果能序列化成功，就返回成功的值，注意该值是个可选值。

```swift
// swift
// 注意：dict是个可选值
	let dict = try! JSONSerialization.jsonObject(with: data, options: [])

```

为了满足Swift上述语法，UTS使用特殊语法来支持，以上三种写法分别对应为：

1. try

```ts
// uts
try {
	let dict = UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []))
}catch (e) {
	console.log(e)
}
```
2. try?

```ts
// uts
UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []), "?" )

```
3. try!

```ts
// uts
UTSiOS.try(JSONSerialization.jsonObject(with = data, options = []), "!" )

```

#### 5.1.15 swift 特有修饰符 @keyword

> HBuilder X 4.06+ 版本支持

`swift` 中有一些特有的属性、方法、类的修饰符如 `open`, `fileprivate`, `internal`, `weak`, `optional` 等，这些修饰符在 `ts` 中没有对应的替代者，但是在原生语法场景下又是必须的。
为了支持这些修饰符，我们提供了 `UTSiOS.keyword("xxx")` 的语法糖。你可以在符合 `swift` 语法要求的场景下，使用诸如 `UTSiOS.keyword("private")` 这样的语法来修饰对应的属性、方法、类等。
我们对具体的修饰符没有做特别的限制，`swfit` 中的合法修饰符都能通过这个语法糖来使用，但是请记住一个重要的前提：你所使用的修饰符，需要满足 `swift` 语法所要求的场景。

下面是一些示例：

```ts
// ts 中 private 修饰符不可出现在模块或命名空间元素上, 如果想将一个类 private，需要使用 @UTSiOS.keyword("private")
@UTSiOS.keyword("private")
class TestA  {
	// 如果需要使用 weak 修饰属性，来避免循环引用，需要使用 @UTSiOS.keyword("weak")
	@UTSiOS.keyword("weak")
	private delegate: TestProtocol | null = null
}

```

```ts

@UTSiOS.keyword("fileprivate")
class TestB  {
	// 一个属性可以同时有多个修饰符，前提是所写的修饰符符合Swift语法
	@UTSiOS.keyword("weak")
	@UTSiOS.keyword("fileprivate")
	delegate: TestProtocol | null = null
}

```

#### 5.1.16 指针操作

在Swift操作指针，需要用到`&`操作符隐式转换得到`UnsafePointer`类型，UTS中提供了`UTSiOS.getPointer()`来表示`&`符号。

以获取字符串MD5为例，如下
```ts
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`


## 6  常见问题(持续更新)

### 6.1 如何在UTS环境中，获取当前 UIViewController 实例

参考 Hello UTS 项目中的 uts-alert 插件

路径:
> ~/uni_modules/uts-alert/utssdk/app-ios/index.uts


### 6.2 如何在UTS环境中，操作 UI 线程

```
DispatchQueue.main.async(execute=():void => {
	// 在主线程中可操作 UI
})
```

参考Hello UTS项目中的 uts-toast 插件

路径:
> ~/uni_modules/uts-toast/utssdk/app-ios/index.uts

### 6.3 插件中需要显示使用类型，不能省略

由于在uts插件环境，无法默认推断出类型，所以需要显示类型，以`uni.request()`为例：

```
		uni.request<any>({
			url: "http://xxx",
			method: "GET",
			success: (e : RequestSuccess<any>) => {
				
			},
			fail(e : RequestFail) {
				
			},
		} as RequestOptions<any>)
```


### 6.4 如何销毁原生对象实例

在 uts 插件中通过 `export` 导出给 `js` 用的 `class`, 创建出的 class 实例会被一直保存在内存中，如果不主动销毁，可能会造成
内存泄漏的问题。我们在 UTSiOS 类型上提供了 `destroyInstance()` 的静态方法来实现销毁原生对象的功能。开发者需要在使用这个对象的页面的
`unmounted() ` 时机将对象销毁。

> HBuilder X 4.25+ 版本支持 

具体使用示例如下：

在 uts 插件中定义 class Test，并将其 `export`：


```ts
// 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` 页面中使用：


```ts
// 创建 test 对象，并调用方法
let test = new Test("1111", "name_11111");
test.doSomething();
this.test = test;
```

在 `uvue` 页面 `unmounted` 时销毁对象：


```ts
// 
unmounted() {
  this.test.destory()
}
```



### 6.5 如何避免闭包可能造成的循环引用

在 uts 插件或者组件中，如果自定义的 `class` 中定义了闭包类型的属性，而闭包内部又使用了 `class` 的其他属性或者 `class` 自身，
就会造成对象循序引用，导致内存泄漏。为避免产生循环引用我们需要在闭包内使用 `"[weak self]"` 标记。

具体示例如下：


```ts
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，如果满足这两条就需要加。


## 7  已知待解决问题(持续更新)

### 7.1 语法提示问题

HBuilderX 目前写iOS uts 插件时部分语法提示会有缺失、参数类型不准确的问题，例如：

- 类的构造方法目前只会提示一个，实际上可能会存在多个；
- 缺失可选类型标识；
- 参数标签没有标记无法知道是否需要忽略参数标签；
- 不支持导入包含有子模块的原生模块；
- 暂不支持.a依赖库的代码提示；

这些问题会在后续版本中优化

### 7.2 类型兼容问题

- 元组类型目前不支持

## 8 有关Swift语言创建的Framework和.a的Swift版本兼容性问题

- 由于高版本XCode编译的Swift语言Framework动态库、静态库、.a库在低版本XCode上无法编译通过，因此存在Swift版本兼容性问题;
- 目前打包机使用的XCode版本号是15.2，对应的Swift版本是5.9.2;
- uts 插件开发者在编译Swift相关Framework和.a库时请选择和打包机相同或者更低版本的XCode;
- uts 插件开发者在选择比打包机更低版本XCode编译Swift库时请在Target->buildSettings设置Buid Libraries for Distribution 为Yes；
- uts 插件使用者所依赖的 xcode 版本号应大于或者等于打包机的 XCode 版本号。

> 特别注意
> 如果在使用真机运行编译uts插件时报 swift 版本不兼容的错误，请先检查自己的 XCode 版本，确保安装XCode 版本应大于或者等于打包机的 XCode版本；
> 如果在使用真机运行编译uts插件时报 XCode 版本应大于 13.2.1的错误，这是说明本地安装的 XCode版本过低，请忽略 13.2.1这个版本限制，直接将本地 XCode升级到大于或者等于打包机的版本，后续我们会优化提示。
