Core Motion与持久存储技术解析
1. Core Motion使用要点
1.1 Core Motion使用注意事项
在使用Core Motion时,有以下几点需要注意:
- 应用应仅创建一个
CMMotionManager
实例。
- Core Motion需要开启各种传感器,如磁力计和陀螺仪,这会增加电池消耗。因此,尽量避免使用不必要的传感器,并在不需要更新时及时停止。
- 应用在后台运行时可以合法使用Core Motion,但需要有其他后台运行的理由,因为
Info.plist
中没有Core Motion的
UIBackgroundModes
设置。例如,若使用了Core Location,可借此同时使用Core Motion。
- 若应用不在后台运行,当应用进入后台时,应明确告知运动管理器停止生成更新。
1.2 Core Motion其他数据类型
除了
CMDeviceMotion
,Core Motion框架还能获取以下四种其他类型的数据:
| 数据类型 | 功能 | 使用步骤 |
| ---- | ---- | ---- |
|
CMMotionActivityManager
| 部分设备有运动协处理器芯片,能检测、分析和记录设备运动,即使设备休眠且耗电量极低。可分析用户携带或佩戴设备时的活动情况,但不涉及位置确定。 | 1. 维护一个
CMMotionActivityManager
实例,通常作为实例属性。
2. 调用
isActivityAvailable
方法检查设备是否有运动协处理器。
3. 有两种查询方式:
- 实时更新:调用
startActivityUpdates(to:withHandler:)
,回调函数会定期调用,不需要更新时调用
stopActivityUpdates
。
- 历史数据:调用
queryActivityStarting(from:to:to:withHandler:)
查询记录的数据,可在运动活动管理器已提供更新时查询历史数据。 |
|
CMPedometer
| 计步器,通过设备的前后运动推断步数,还可接收用户活动开始或停止的事件。在某些Core Location不可靠的情况下仍能可靠工作。 | 1. 维护一个
CMPedometer
实例,通常作为实例属性。
2. 使用前调用
isStepCountingAvailable
方法检查是否支持计步。
3. 不同设备有不同功能,如推断步幅大小、计算距离、估算是否爬楼梯、获取瞬时节奏和步速等。
4. 数据查询方式与运动活动数据相同:
- 实时更新:调用
startUpdates(from:withHandler:)
。
- 历史数据:调用
queryPedometerData(from:to:withHandler:)
,数据以
CMPedometerData
对象形式返回。
5. 调用
startEventUpdates(handler:)
可接收用户运动变化的通知,处理函数接收
CMPedometerEvent
对象,类型为
.pause
或
.resume
。 |
|
CMAltimeter
| 部分设备有高度计(本质是气压计),主要用于提醒用户活动期间相对高度的变化,而非绝对高度。 | 1. 维护一个
CMAltimeter
实例,通常作为实例属性。
2. 使用前调用
isRelativeAltitudeAvailable
方法检查是否支持相对高度测量。
3. 调用
startRelativeAltitudeUpdates(to:withHandler:)
开始接收
CMAltitudeData
对象,关键指标是
relativeAltitude
属性,以米为单位,初始值为0,后续对象提供相对于初始值的测量值。 |
|
CMSensorRecorder
| 部分设备可在后台记录加速度计的输出。 | 1. 使用前调用
isAccelerometerRecordingAvailable
方法检查是否支持加速度计记录。
2. 实例化
CMSensorRecorder
并调用
recordAccelerometer(forDuration:)
开始记录,系统每秒记录50次,无论应用是否在前台或运行,达到指定时长后自动停止。
3. 检索数据时,再次实例化
CMSensorRecorder
并调用
accelerometerData(from:to:)
,返回
CMSensorDataList
对象。
4. 为
CMSensorDataList
添加扩展使其符合
Sequence
协议:
swift<br>extension CMSensorDataList: Sequence {<br> public typealias Iterator = NSFastEnumerationIterator<br> public func makeIterator() -> NSFastEnumerationIterator {<br> return NSFastEnumerationIterator(self)<br> }<br>}<br>
5. 遍历
CMSensorDataList
获取
CMRecordedAccelerometerData
实例:
swift<br>let rec = CMSensorRecorder() // and d1 and d2 are Dates<br>if let list = rec.accelerometerData(from: d1, to: d2) {<br> for datum in list {<br> if let accdatum = datum as? CMRecordedAccelerometerData {<br> let accel = accdatum.acceleration<br> let t = accdatum.timestamp<br> // do something with data here<br> }<br> }<br>}<br>
|
1.3 用户授权
获取以上四种数据都需要用户授权,且用户后续可在设置应用中撤销授权。
Info.plist
必须包含“Privacy - Motion Usage Description”键(
NSMotionUsageDescription
)的条目,解释使用目的。奇怪的是,没有
requestAuthorization
方法。以前,很难提前知道是否获得授权,需尝试查询数据并查看是否出错。从iOS 11开始,可直接询问类的
authorizationStatus
,若状态为
.notDetermined
,仍需“触发”类以弹出授权对话框。
以下是检查授权的示例代码:
func checkAuthorization(andThen f:(()->())? = nil) {
let status = CMMotionActivityManager.authorizationStatus()
switch status {
case .notDetermined: // bring up dialog
let now = Date()
self.actman.queryActivityStarting(from: now, to:now, to:.main) {
_,err in
print("asked for authorization")
if err == nil {
f?()
}
}
case .authorized: f?()
case .restricted: break // do nothing
case .denied: break // could beg for authorization here
}
}
1.4 历史运动活动数据查询示例
以下是查询过去24小时历史运动活动管理器数据的示例:
let now = Date()
let yester = now - (60*60*24)
self.actman.queryActivityStarting(
from: yester, to: now, to: self.queue) { arr, err in
guard var acts = arr else {return}
// ...
}
1.5 数据处理
CMMotionActivity
对象有
startDate
、
confidence
(
.low
、
.medium
或
.high
)和一系列布尔属性,用于分类活动。为了简化数据,可对
CMMotionActivity
进行扩展,添加一个实用方法将布尔属性总结为字符串:
extension CMMotionActivity {
private func tf(_ b:Bool) -> String {
return b ? "t" : "f"
}
func overallAct() -> String {
let s = tf(self.stationary)
let w = tf(self.walking)
let r = tf(self.running)
let a = tf(self.automotive)
let c = tf(self.cycling)
let u = tf(self.unknown)
return "\(s) \(w) \(r) \(a) \(c) \(u)"
}
}
然后,可去除无明确活动、置信度低或活动与前一个相同的
CMMotionActivity
对象:
let blank = "f f f f f f"
acts = acts.filter {act in act.overallAct() != blank}
acts = acts.filter {act in act.confidence == .high}
for i in (1..<acts.count).reversed() {
if acts[i].overallAct() == acts[i-1].overallAct() {
acts.remove(at:i)
}
}
DispatchQueue.main.async {
self.data = acts
}
2. 持久存储
2.1 沙盒
设备的整体内容对应用不可见,应用有自己的沙盒,这是设备持久存储的有限区域。每个应用只能看到自己的沙盒,可防止窥探或影响其他应用的文件,同时自身文件也受到保护。若用户删除应用,沙盒及其中的数据将被删除;否则,数据应可靠持久。
2.2 标准目录
推荐使用文件URL(
URL
实例)来引用文件或目录,也可使用文件路径(字符串),必要时可在两者间转换。沙盒包含一些标准目录,可通过
FileManager
实例的
url(for:in:appropriateFor:create:)
方法获取标准目录的URL。
以下是获取文档目录URL的示例:
do {
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
// use docsurl here
} catch {
// deal with error here
}
对于要保存和读取的文件和文件夹,文档目录是个不错的选择,但如果应用支持文件共享,用户可通过iTunes查看和修改文档目录,因此可能不适合存放用户不应查看和更改的内容。应用支持目录是个很好的替代方案,iOS中每个应用在沙盒中有自己的私有应用支持目录,可直接安全地存放文件。该目录可能最初不存在,但可在获取时创建:
do {
let fm = FileManager.default
let suppurl = try fm.url(for:.applicationSupportDirectory,
in: .userDomainMask, appropriateFor: nil, create: true)
// use suppurl here
} catch {
// deal with error here
}
可将愿意接受丢失的临时文件写入缓存目录(
.cachesDirectory
)或临时目录(
FileManager
的
temporaryDirectory
)。若将临时文件写入应用支持文件夹,默认可通过iTunes或iCloud备份,可通过设置文件属性排除备份:
var myFileURL = // file URL
var rv = URLResourceValues()
rv.isExcludedFromBackup = true
try myFileURL.setResourceValues(rv)
2.3 检查沙盒
开发应用时,可查看沙盒以确保文件按预期保存。在模拟器中,可通过打印应用文档目录的路径,复制到Finder的“前往文件夹”对话框中查看。在设备上,连接设备后,选择“Window → Devices and Simulators”,切换到“Devices”标签,选择设备和应用,点击齿轮图标,可选择“Show Container”查看沙盒层次结构,或选择“Download Container”将沙盒复制到计算机,以
.xcappdata
包形式存在,可在Finder中打开。
2.4 基本文件操作
2.4.1 创建文件夹
以下是在文档目录中创建
MyFolder
文件夹的示例:
let foldername = "MyFolder"
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let myfolder = docsurl.appendingPathComponent(foldername)
try fm.createDirectory(at:myfolder, withIntermediateDirectories: true)
2.4.2 查看目录内容
可通过
contentsOfDirectory
方法获取目录的内容数组,该数组列出目录的直接内容的完整URL,是浅层的。若要深度遍历目录内容,可使用目录枚举器(
FileManager.DirectoryEnumerator
),这在内存使用上更高效,因为每次只返回一个文件引用。
以下是深度遍历文档目录查找
.txt
文件的示例:
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let dir = fm.enumerator(at:docsurl, includingPropertiesForKeys: nil)!
for case let f as URL in dir where f.pathExtension == "txt" {
print(f.lastPathComponent) // file1.txt, file2.txt
}
目录枚举器还允许跳过特定子目录,使遍历更高效。可查阅
FileManager
类文档和Apple的文件系统编程指南了解更多文件操作。
2.5 保存和读取文件
2.5.1 避免存储绝对文件URL
沙盒目录的绝对URL在应用的单次运行中会持续存在,但长期来看是不稳定的,不同运行时可能不同。因此,不能将应用的绝对文件URL或路径字符串存储到任何持久存储中,否则下次应用启动时会出错。若必须存储文件和文件夹信息,应存储其名称(或相对URL、部分路径)以及所在沙盒目录的信息,每次需要时重新生成URL。
2.5.2 可直接转换的类
NSString
、
NSData
、
NSArray
和
NSDictionary
这四个Cocoa类提供了从文件URL创建实例和将实例保存到文件URL的方法,可在文件和类实例间有效转换。
以下是生成文本文件和属性列表文件的示例:
// 生成文本文件
try "howdy".write(to: myfolder.appendingPathComponent("file1.txt"),
atomically: true, encoding:.utf8)
// 生成属性列表文件
let arr = ["Manny", "Moe", "Jack"]
let temp = FileManager.default.temporaryDirectory
let f = temp.appendingPathComponent("pep.plist")
try (arr as NSArray).write(to: f)
2.5.3 序列化对象到文件
若要保存其他类型的对象到文件,可将其序列化为
NSData
对象(Swift的
Data
)。序列化有两种方法:旧的Cocoa方式(
NSCoding
)和新的Swift方式(
Codable
)。
NSCoding
NSCoding
协议在Cocoa的Foundation框架中定义。若对象的类采用
NSCoding
,可通过
NSKeyedArchiver
和
NSKeyedUnarchiver
将对象转换为
NSData
并转换回来。这意味着类要实现
encode(with:)
方法来归档对象,实现
init(coder:)
方法来解档对象。
以下是一个简单的
Person
类采用
NSCoding
协议的示例:
class Person: NSObject, NSSecureCoding {
static var supportsSecureCoding: Bool { return true }
var firstName : String
var lastName : String
override var description : String {
return self.firstName + " " + self.lastName
}
init(firstName:String, lastName:String) {
self.firstName = firstName
self.lastName = lastName
super.init()
}
func encode(with coder: NSCoder) {
// do not call super in this case
coder.encode(self.lastName, forKey: "last")
coder.encode(self.firstName, forKey: "first")
}
required init(coder: NSCoder) {
self.lastName = coder.decodeObject(
of: NSString.self, forKey:"last")! as String
self.firstName = coder.decodeObject(
of: NSString.self, forKey:"first")! as String
// do not call super init(coder:) in this case
super.init()
}
}
// 保存Person实例
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let moi = Person(firstName: "Matt", lastName: "Neuburg")
let moidata = try NSKeyedArchiver.archivedData(
withRootObject: moi, requiringSecureCoding: true)
let moifile = docsurl.appendingPathComponent("moi.txt")
try moidata.write(to: moifile, options: .atomic)
// 检索保存的Person实例
let persondata = try Data(contentsOf: moifile)
let person = try NSKeyedUnarchiver.unarchivedObject(
ofClass: Person.self, from: persondata)!
print(person) // "Matt Neuburg"
Codable
Codable
协议在Swift 4中引入,是
Encodable
和
Decodable
协议的组合。对象只要符合
Encodable
就可序列化(归档),符合
Decodable
就可从序列化形式恢复(解档),通常对象会同时符合两者,即采用
Codable
协议。有三种序列化模式:属性列表、JSON和
NSCoder
。
以下是将
Person
类重写为采用
Codable
协议的示例:
class Person: NSObject, Codable {
var firstName : String
var lastName : String
override var description : String {
return self.firstName + " " + self.lastName
}
init(firstName:String, lastName:String) {
self.firstName = firstName
self.lastName = lastName
super.init()
}
}
// 保存Person实例
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let moi = Person(firstName: "Matt", lastName: "Neuburg")
let moidata = try PropertyListEncoder().encode(moi)
let moifile = docsurl.appendingPathComponent("moi.txt")
try moidata.write(to: moifile, options: .atomic)
// 检索保存的Person实例
let persondata = try Data(contentsOf: moifile)
let person = try PropertyListDecoder().decode(Person.self, from: persondata)
print(person) // "Matt Neuburg"
保存可编码的
Person
对象数组的方法与保存单个对象相同。
Codable
的实现可能更复杂,特别是当编码数据的格式不由自己控制时,如通过服务器规定的JSON API进行通信。
虽然有了
Codable
,但仍可能需要使用
NSCoding
,因为Cocoa是用Objective - C编写的,其可编码对象类型采用
NSCoding
而非
Codable
。不过,可通过
NSCoder
的
encodeEncodable(_:forKey:)
和
decodeDecodable(_:forKey:)
方法结合Swift的
Codable
和Cocoa的
NSCoding
。
以下是一个视图控制器参与视图控制器状态保存和恢复的示例:
class Pep: UIViewController {
let boy : String
// ...
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with:coder)
coder.encode(self.boy, forKey:"boy")
}
}
// 结合Codable和NSCoding
class Pep: UIViewController {
let boy : String
let prop : MyStruct // adopts Codable
// ...
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with:coder)
coder.encode(self.boy, forKey: "boy")
let arch = coder as! NSKeyedArchiver
try! arch.encodeEncodable(self.prop, forKey: "prop")
}
}
decodeRestorableState(with:)
的实现类似,将编码器转换为
NSKeyedUnarchiver
并调用
decodeDecodable(_:forKey:)
提取编码的结构体。
3. 序列化模式对比
3.1 序列化模式总结
| 序列化模式 | 适用场景 | 编码方法 | 解码方法 |
|---|---|---|---|
| 属性列表 | 数据结构简单,适合存储基本类型数据 |
PropertyListEncoder().encode(_:)
|
PropertyListDecoder().decode(_:from:)
|
| JSON | 用于网络数据传输,与服务器交互 |
JSONEncoder().encode(_:)
|
JSONDecoder().decode(_:from:)
|
| NSCoder |
处理Cocoa对象,如
UIColor
等
|
NSKeyedArchiver.encodeEncodable(_:forKey:)
|
NSKeyedUnarchiver.decodeDecodable(_:forKey:)
|
3.2 选择建议
在选择序列化模式时,可参考以下几点:
-
简单数据存储
:若数据为基本类型,如字符串、数组、字典等,且不需要与外部服务器交互,属性列表是不错的选择,其实现简单,与
NSKeyedArchiver
底层原理相似。
-
网络通信
:当需要与服务器进行数据交互时,JSON是首选,因为JSON是一种通用的数据交换格式,大多数服务器API都支持。
-
Cocoa对象处理
:对于Cocoa框架中的对象,如
UIColor
、
UIImage
等,由于它们采用
NSCoding
协议,需使用
NSKeyedArchiver
和
NSKeyedUnarchiver
进行编码和解码。
3.3 示例对比
以下是使用不同序列化模式保存和读取
Person
对象的示例:
属性列表模式
// 保存
let moi = Person(firstName: "Matt", lastName: "Neuburg")
let moidata = try PropertyListEncoder().encode(moi)
let moifile = docsurl.appendingPathComponent("moi.plist")
try moidata.write(to: moifile, options: .atomic)
// 读取
let persondata = try Data(contentsOf: moifile)
let person = try PropertyListDecoder().decode(Person.self, from: persondata)
print(person) // "Matt Neuburg"
JSON模式
// 保存
let jsonData = try JSONEncoder().encode(moi)
let jsonFile = docsurl.appendingPathComponent("moi.json")
try jsonData.write(to: jsonFile, options: .atomic)
// 读取
let jsonPersondata = try Data(contentsOf: jsonFile)
let jsonPerson = try JSONDecoder().decode(Person.self, from: jsonPersondata)
print(jsonPerson) // "Matt Neuburg"
NSCoder模式
// 保存
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encodeEncodable(moi, forKey: "person")
let archivedData = archiver.encodedData
let archivedFile = docsurl.appendingPathComponent("moi.archived")
try archivedData.write(to: archivedFile, options: .atomic)
// 读取
let archivedPersondata = try Data(contentsOf: archivedFile)
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: archivedPersondata)
let archivedPerson = try unarchiver.decodeDecodable(Person.self, forKey: "person")
print(archivedPerson) // "Matt Neuburg"
4. 数据处理流程总结
4.1 Core Motion数据处理流程
graph LR
A[初始化CMMotionManager] --> B[检查授权]
B -->|已授权| C[选择数据类型]
B -->|未授权| D[请求授权]
D --> B
C -->|CMMotionActivityManager| E[实时更新或查询历史数据]
C -->|CMPedometer| F[检查功能可用性并查询数据]
C -->|CMAltimeter| G[检查高度测量可用性并获取数据]
C -->|CMSensorRecorder| H[检查记录可用性并记录/检索数据]
E --> I[处理数据]
F --> I
G --> I
H --> I
4.2 持久存储数据处理流程
graph LR
A[确定存储需求] --> B[选择存储目录]
B -->|Documents目录| C[创建文件或文件夹]
B -->|Application Support目录| C
B -->|缓存或临时目录| C
C --> D[选择数据类型]
D -->|NSString/NSData/NSArray/NSDictionary| E[直接保存/读取]
D -->|其他类型对象| F[序列化对象]
F -->|NSCoding| G[使用NSKeyedArchiver/NSKeyedUnarchiver]
F -->|Codable| H[选择序列化模式]
H -->|属性列表| I[使用PropertyListEncoder/PropertyListDecoder]
H -->|JSON| J[使用JSONEncoder/JSONDecoder]
H -->|NSCoder| K[使用NSKeyedArchiver/NSKeyedUnarchiver结合Codable]
E --> L[完成存储/读取]
G --> L
I --> L
J --> L
K --> L
5. 注意事项总结
5.1 Core Motion注意事项
-
仅创建一个
CMMotionManager实例,避免资源浪费。 - 及时停止不必要的传感器更新,以减少电池消耗。
-
确保在
Info.plist中添加“Privacy - Motion Usage Description”键,解释使用目的,获取用户授权。
5.2 持久存储注意事项
- 避免存储绝对文件URL,防止应用重启后URL失效。
- 根据数据的重要性和使用场景选择合适的存储目录。
- 对于不同类型的对象,选择合适的序列化方法进行存储和读取。
通过以上对Core Motion和持久存储技术的详细解析,我们了解了如何使用Core Motion框架获取设备的运动数据,以及如何将数据持久存储在设备上。在实际开发中,可根据具体需求选择合适的技术和方法,确保应用的性能和数据的安全性。
超级会员免费看
1376

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



