21、iOS开发:UI测试、Core Motion与安全相关技术解析

iOS开发:UI测试、Core Motion与安全相关技术解析

1. UI测试相关操作

1.1 文本输入与验证

在进行UI测试时,我们可以模拟文本输入和删除操作。以下是一个示例代码:

let app = XCUIApplication()
let myText = app.textFields["myText"]
myText.tap()
let text1 = "Hello, World!"
myText.typeText(text1)
myText.typeText(XCUIKeyboardKeyDelete)
app.typeText(XCUIKeyboardKeyReturn)
XCTAssertEqual((myText.value as! String).characters.count, text1.characters.count - 1)

上述代码的操作步骤如下:
1. 获取应用实例。
2. 找到名为“myText”的文本框并点击。
3. 输入“Hello, World!”。
4. 删除一个字符。
5. 模拟按下回车键。
6. 验证文本框中的字符数量是否符合预期。

1.2 模拟滑动操作

在应用中,我们可能需要模拟各种UI组件的滑动操作。可以使用 XCUIElement 的以下方法:
- swipeUp()
- swipeDown()
- swipeRight()
- swipeLeft()

下面是一个模拟表格视图单元格滑动删除的示例:

let app = XCUIApplication()
let cells = app.cells
XCTAssertEqual(cells.count, 10)
app.cells.element(boundBy: 4).swipeLeft()
app.buttons["Delete"].tap()
XCTAssertEqual(cells.count, 9)

操作步骤如下:
1. 获取应用实例。
2. 检查表格视图中初始的单元格数量是否为10。
3. 找到第五个单元格并向左滑动。
4. 点击“Delete”按钮。
5. 验证单元格数量是否变为9。

1.3 模拟点击操作

在编写UI测试时,我们可以模拟各种点击方式。 XCUIElement 类提供了以下方法:
- tap()
- doubleTap()
- twoFingerTap()

以下是一个模拟双指点击视图的示例:

let app = XCUIApplication()
let view = app.descendants(matching: .other)["myView"]
XCTAssert(view.exists)
XCTAssert(view.value as! String == "untapped")
view.twoFingerTap()
XCTAssert(view.value as! String == "tapped")

操作步骤如下:
1. 获取应用实例。
2. 找到名为“myView”的视图并验证其是否存在。
3. 验证视图的初始值是否为“untapped”。
4. 进行双指点击操作。
5. 验证视图的值是否变为“tapped”。

2. Core Motion框架

2.1 关键术语

在Core Motion框架中,有两个关键术语需要了解:
- Cadence(节奏) :例如在骑自行车时,节奏传感器可以帮助我们了解踏板的转动次数。在跑步时,Apple Watch也包含节奏传感器。
- Pace(步速) :是移动时间与距离的比值,例如每米0.5秒的步速表示半秒移动1米。

2.2 查询节奏和步速信息

如果想从iOS设备的计步器获取节奏和步速信息,可以按照以下步骤操作:
1. 检查节奏和步速信息是否可用。
2. 调用 CMPedometer startUpdates(from:withHandler:) 函数。
3. 在处理程序块中,读取传入的可选 CMPedometerData 对象的 currentPace currentCadence 属性。

示例代码如下:

guard CMPedometer.isCadenceAvailable() && CMPedometer.isPaceAvailable() else {
    print("Pace and cadence data are not available")
    return
}
let oneWeekAgo = Date(timeIntervalSinceNow: -(7 * 24 * 60 * 60))
pedometer.startUpdates(from: oneWeekAgo) { data, error in
    guard let pData = data, error == nil else {
        return
    }
    if let pace = pData.currentPace {
        print("Pace = \(pace)")
    }
    if let cadence = pData.currentCadence {
        print("Cadence = \(cadence)")
    }
}
// 记得在查询结束后调用stopPedometerUpdates()停止计步器更新

2.3 记录和读取加速度计数据

如果想让iOS设备积累一段时间的加速度计数据,并一次性批量更新应用,可以按照以下步骤操作:
1. 调用 CMSensorRecorder isAccelerometerRecordingAvailable() 类函数,检查加速度计记录是否可用。
2. 实例化 CMSensorRecorder
3. 调用 recordAccelerometer(forDuration:) 函数,传入要记录的秒数。
4. 可以选择在后台线程等待数据。
5. 调用 accelerometerData(from:to:) 函数获取指定日期范围内的加速度计数据。
6. 读取每个 CMRecordedAccelerometerData 对象的值,包括 startDate timestamp acceleration

示例代码如下:

extension CMSensorDataList : Sequence {
    public func makeIterator() -> NSFastEnumerationIterator {
        return NSFastEnumerationIterator(self)
    }
}
lazy var recorder = CMSensorRecorder()
guard CMSensorRecorder.isAccelerometerRecordingAvailable() else {
    print("Accelerometer data recording is not available")
    return
}
let duration = 3.0
recorder.recordAccelerometer(forDuration: duration)
OperationQueue().addOperation { [unowned recorder] in
    Thread.sleep(forTimeInterval: duration)
    let now = Date()
    let past = now.addingTimeInterval(-(duration))
    guard let data = recorder.accelerometerData(from: past, to: now) else {
        return
    }
    print(data)
}

需要注意的是,应该在非UI线程上枚举 accelerometerData(from:to:) 的结果,因为结果中可能包含数千个数据点。

2.4 操作流程总结

操作 步骤
查询节奏和步速信息 1. 检查可用性;2. 开始更新;3. 读取数据
记录和读取加速度计数据 1. 检查可用性;2. 实例化记录器;3. 记录数据;4. 等待并读取数据

2.5 操作流程mermaid图

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B{节奏和步速可用?}:::decision
    B -- 是 --> C(开始更新):::process
    C --> D(读取数据):::process
    B -- 否 --> E(输出不可用信息):::process
    F([开始]):::startend --> G{加速度计记录可用?}:::decision
    G -- 是 --> H(实例化记录器):::process
    H --> I(记录数据):::process
    I --> J(等待并读取数据):::process
    G -- 否 --> K(输出不可用信息):::process

3. 安全框架相关技术

3.1 用ATS保护网络连接

3.1.1 问题与解决方案概述

我们可能想控制网络连接所使用的HTTPS通道的细节,或者使用非安全通道(HTTP)。默认情况下,使用新Xcode编译并运行在最新iOS版本下的应用,会默认对所有网络流量使用HTTPS。但在某些后端不提供HTTPS版本的情况下,可能不得不使用HTTP。

可以通过在 info.plist 文件中配置 NSAppTransportSecurity 字典来控制。在该字典下的 NSExceptionDomains 字典中,可以列出不使用ATS的特定域名。

3.1.2 具体配置方法
  • 完全禁用ATS :在 NSExceptionDomains 键下插入 NSAllowsArbitraryLoads 键,并将其值设置为 true 。这样,HTTP连接就是HTTP,HTTPS连接就是HTTPS。示例 plist 文件如下:
<plist version="1.0">
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
  • 特定域名不使用ATS :在 NSExceptionDomains 键下指定域名,并将其数据类型设置为字典。在该字典中可以使用以下键:
    • NSExceptionAllowsInsecureHTTPLoads :设置为 true 时,允许在给定域名上进行HTTP加载。
    • NSIncludesSubdomains :设置为 true 时,将给定域名的所有子域名都作为ATS的例外。
    • NSRequiresCertificateTransparency :要求给定URL的SSL证书包含证书透明度信息。
    • NSExceptionMinimumTLSVersion :指定连接的最小TLS版本,值可以是 TLSv1.0 TLSv1.1 TLSv1.2

例如,要让 mydomain.com 不使用ATS,同时要求证书透明度,并将子域名也作为例外, plist 文件如下:

<plist version="1.0">
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>mydomain.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSRequiresCertificateTransparency</key>
<true/>
</dict>
</dict>
</dict>
</plist>

3.2 绑定钥匙串项到密码和Touch ID

3.2.1 问题与解决方案概述

我们可能想在钥匙串中创建一个安全项,只有当用户在设备上设置了密码并选择使用Touch ID时才能访问。

3.2.2 操作步骤
  1. 使用 SecAccessControlCreateWithFlags 函数创建访问控制标志。将 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly 作为保护参数, SecAccessControlCreateFlags.touchIDAny 作为标志参数。
  2. 在安全字典中添加 kSecUseAuthenticationUI 键,并将其值设置为 kSecUseAuthenticationUIAllow ,允许用户使用设备密码或Touch ID解锁安全钥匙。
  3. 在安全字典中添加 kSecAttrAccessControl 键,并将其值设置为之前调用 SecAccessControlCreateWithFlags 函数的返回值。
3.2.3 示例代码
guard let flags =
  SecAccessControlCreateWithFlags(
    kCFAllocatorDefault,
    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
    SecAccessControlCreateFlags.touchIDAny, nil) else{
      print("Could not create the access control flags")
      return
}
let password = "some string"
guard let data = password.data(using: String.Encoding.utf8) else{
  print("Could not get data from string")
  return
}
let service = "onlinePasswords"
let attrs = [
  kSecClass.str() : kSecClassGenericPassword.str(),
  kSecAttrService.str() : service,
  kSecValueData.str() : data,
  kSecUseAuthenticationUI.str() : kSecUseAuthenticationUIAllow.str(),
  kSecAttrAccessControl.str() : flags,
  ]
OperationQueue().addOperation{
  guard SecItemAdd(attrs, nil) == errSecSuccess else{
    print("Could not add the item to the keychain")
    return
  }

  print("Successfully added the item to keychain")
}

另外, SecAccessControlCreateFlags 中的 touchIDCurrentSet 值也很有用。如果使用该值,安全项仍需要Touch ID,但当当前注册的Touch ID手指集合发生变化时,该项将失效且无法读取。

3.3 安全打开URL

3.3.1 问题与解决方案概述

我们需要找出用户设备上的应用是否可以打开特定URL。

3.3.2 操作步骤
  1. plist 文件中定义 LSApplicationQueriesSchemes 键为数组。
  2. 在该数组下,将想要应用能够打开的URL方案定义为字符串。
  3. 在应用中,调用共享应用的 canOpenUrl(_:) 方法。
  4. 如果可以打开URL,使用共享应用的 open(_:options:completionHandler:) 方法打开它。
  5. 如果无法打开URL,尽可能为用户提供替代方案。
3.3.3 示例代码
guard let url = URL(string: "instagram://app"),
  UIApplication.shared.canOpenURL(url) else{
    return
}
UIApplication.shared.open(url){succeeded in
  if succeeded{
    print("Successfully opened Instagram")
  } else {
    print("Could not open Instagram")
  }
}

同时,需要在 plist 文件中告诉iOS想要打开以“instagram”开头的URL方案:

<plist version="1.0">
<array>
<string>instagram</string>
</array>
</plist>

3.4 用Touch ID和超时进行用户认证

3.4.1 问题与解决方案概述

我们想请求用户允许读取钥匙串中的安全内容,并设置一个超时时间,超时后将不再拥有访问权限。

3.4.2 操作步骤
  1. 使用 SecAccessControlCreateWithFlags 创建访问控制标志。
  2. 实例化 LAContext 类型的上下文对象。
  3. 将上下文的 touchIDAuthenticationAllowableReuseDuration 属性设置为 LATouchIDAuthenticationMaximumAllowableReuseDuration ,使上下文仅在最大允许秒数后锁定。
  4. 调用上下文的 evaluateAccessControl(_:operation:localizedReason:) 方法以获取对访问控制的访问权限。
  5. 如果获得访问权限,创建钥匙串请求字典并包含 kSecUseAuthenticationContext 键,其值为上下文对象。
  6. 使用 SecItemCopyMatching 函数和字典读取具有给定访问控制的安全对象。
3.4.3 示例代码
let context = LAContext()
let reason = "To unlock previously stored security phrase"
guard let flags =
  SecAccessControlCreateWithFlags(
    kCFAllocatorDefault,
    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
    SecAccessControlCreateFlags.touchIDAny, nil) else{
      print("Could not create the access control flags")
      return
}
context.touchIDAuthenticationAllowableReuseDuration =
LATouchIDAuthenticationMaximumAllowableReuseDuration
context.evaluateAccessControl(
  flags,
  operation: LAAccessControlOperation.useItem,
  localizedReason: reason) {[unowned context] succ, err in

    guard succ && err == nil else {
      print("Could not evaluate the access control")
      if let e = err {
        print("Error = \(e)")
      }
      return
    }

    print("Successfully evaluated the access control")

    let service = "onlinePasswords"

    let attrs = [
      kSecClass.str() : kSecClassGenericPassword.str(),
      kSecAttrService.str() : service,
      kSecUseAuthenticationUI.str() : kSecUseAuthenticationUIAllow.str(),
      kSecAttrAccessControl.str() : flags,
      kSecReturnData.str() : kCFBooleanTrue,
      kSecUseAuthenticationContext.str() : context,
      ] as NSDictionary

    // now attempt to use the attrs with SecItemCopyMatching

    print(attrs)

}

3.5 安全操作流程总结

操作 步骤
用ATS保护网络连接 1. 配置 info.plist ;2. 根据需求设置域名例外
绑定钥匙串项到密码和Touch ID 1. 创建访问控制标志;2. 准备数据;3. 添加到钥匙串
安全打开URL 1. 配置 plist ;2. 检查并打开URL
用Touch ID和超时进行用户认证 1. 创建访问控制标志;2. 实例化上下文;3. 评估访问控制;4. 读取安全对象

3.6 安全操作流程mermaid图

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B{ATS配置?}:::decision
    B -- 是 --> C(配置域名例外):::process
    B -- 否 --> D(默认使用HTTPS):::process
    E([开始]):::startend --> F{创建访问控制标志?}:::decision
    F -- 是 --> G(准备数据):::process
    G --> H(添加到钥匙串):::process
    F -- 否 --> I(输出创建失败信息):::process
    J([开始]):::startend --> K{配置plist URL方案?}:::decision
    K -- 是 --> L(检查并打开URL):::process
    K -- 否 --> M(输出无法检查信息):::process
    N([开始]):::startend --> O{创建访问控制标志?}:::decision
    O -- 是 --> P(实例化上下文):::process
    P --> Q(评估访问控制):::process
    Q --> R(读取安全对象):::process
    O -- 否 --> S(输出创建失败信息):::process

综上所述,在iOS开发中,我们可以通过UI测试确保应用的用户界面交互正常,利用Core Motion框架获取设备的运动数据,同时借助安全框架保护网络连接和用户数据的安全。掌握这些技术对于开发高质量、安全可靠的iOS应用至关重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值