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 操作步骤
-
使用
SecAccessControlCreateWithFlags函数创建访问控制标志。将kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly作为保护参数,SecAccessControlCreateFlags.touchIDAny作为标志参数。 -
在安全字典中添加
kSecUseAuthenticationUI键,并将其值设置为kSecUseAuthenticationUIAllow,允许用户使用设备密码或Touch ID解锁安全钥匙。 -
在安全字典中添加
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 操作步骤
-
在
plist文件中定义LSApplicationQueriesSchemes键为数组。 - 在该数组下,将想要应用能够打开的URL方案定义为字符串。
-
在应用中,调用共享应用的
canOpenUrl(_:)方法。 -
如果可以打开URL,使用共享应用的
open(_:options:completionHandler:)方法打开它。 - 如果无法打开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 操作步骤
-
使用
SecAccessControlCreateWithFlags创建访问控制标志。 -
实例化
LAContext类型的上下文对象。 -
将上下文的
touchIDAuthenticationAllowableReuseDuration属性设置为LATouchIDAuthenticationMaximumAllowableReuseDuration,使上下文仅在最大允许秒数后锁定。 -
调用上下文的
evaluateAccessControl(_:operation:localizedReason:)方法以获取对访问控制的访问权限。 -
如果获得访问权限,创建钥匙串请求字典并包含
kSecUseAuthenticationContext键,其值为上下文对象。 -
使用
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应用至关重要。
超级会员免费看
23

被折叠的 条评论
为什么被折叠?



