我有以下用于收集设备运动数据的类:
class MotionManager: NSObject {
static let shared = MotionManager()
private override init() {}
// MARK: - Class Variables
private let motionManager = CMMotionManager()
fileprivate lazy var locationManager: CLLocationManager = {
var locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .fitness
locationManager.distanceFilter = 10.0
return locationManager
}()
private let queue: OperationQueue = {
let queue = OperationQueue()
queue.name = "MotionQueue"
queue.qualityOfService = .utility
return queue
}()
fileprivate var motionDataRecord = MotionDataRecord()
private var attitudeReferenceFrame: CMAttitudeReferenceFrame = .xTrueNorthZVertical
var interval: TimeInterval = 0.01
var startTime: TimeInterval?
// MARK: - Class Functions
func start() {
startTime = Date().timeIntervalSince1970
startDeviceMotion()
startAccelerometer()
startGyroscope()
startMagnetometer()
startCoreLocation()
}
func startCoreLocation() {
switch CLLocationManager.authorizationStatus() {
case .authorizedAlways:
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
case .notDetermined:
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .restricted, .denied:
break
}
}
func startAccelerometer() {
if motionManager.isAccelerometerAvailable {
motionManager.accelerometerUpdateInterval = interval
motionManager.startAccelerometerUpdates(to: queue) { (data, error) in
if error != nil {
log.error("Accelerometer Error: \(error!)")
}
guard let data = data else { return }
self.motionDataRecord.accelerometer = data
}
} else {
log.error("The accelerometer is not available")
}
}
func startGyroscope() {
if motionManager.isGyroAvailable {
motionManager.gyroUpdateInterval = interval
motionManager.startGyroUpdates(to: queue) { (data, error) in
if error != nil {
log.error("Gyroscope Error: \(error!)")
}
guard let data = data else { return }
self.motionDataRecord.gyro = data
}
} else {
log.error("The gyroscope is not available")
}
}
func startMagnetometer() {
if motionManager.isMagnetometerAvailable {
motionManager.magnetometerUpdateInterval = interval
motionManager.startMagnetometerUpdates(to: queue) { (data, error) in
if error != nil {
log.error("Magnetometer Error: \(error!)")
}
guard let data = data else { return }
self.motionDataRecord.magnetometer = data
}
} else {
log.error("The magnetometer is not available")
}
}
func startDeviceMotion() {
if motionManager.isDeviceMotionAvailable {
motionManager.deviceMotionUpdateInterval = interval
motionManager.startDeviceMotionUpdates(using: attitudeReferenceFrame, to: queue) { (data, error) in
if error != nil {
log.error("Device Motion Error: \(error!)")
}
guard let data = data else { return }
self.motionDataRecord.deviceMotion = data
self.motionDataRecord.timestamp = Date().timeIntervalSince1970
self.handleMotionUpdate()
}
} else {
log.error("Device motion is not available")
}
}
func stop() {
locationManager.stopUpdatingLocation()
locationManager.stopUpdatingHeading()
motionManager.stopAccelerometerUpdates()
motionManager.stopGyroUpdates()
motionManager.stopMagnetometerUpdates()
motionManager.stopDeviceMotionUpdates()
}
func handleMotionUpdate() {
print(motionDataRecord)
}
}
// MARK: - Location Manager Delegate
extension MotionManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedWhenInUse {
locationManager.startUpdatingLocation()
} else {
locationManager.stopUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
motionDataRecord.location = location
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
motionDataRecord.heading = newHeading
}
}
然而,我在运行一段时间后得到了EXC_BAD_ACCESS。我运行了僵尸乐器,看来handleMotionUpdate()
是错误的来电者。并且MotionDataRecord
或其某些属性是以某种方式解除分配...
MotionDataRecord
是struct
:
struct MotionDataRecord {
var timestamp: TimeInterval = 0
var location: CLLocation?
var heading: CLHeading?
var motionAttitudeReferenceFrame: CMAttitudeReferenceFrame = .xTrueNorthZVertical
var deviceMotion: CMDeviceMotion?
var altimeter: CMAltitudeData?
var accelerometer: CMAccelerometerData?
var gyro: CMGyroData?
var magnetometer: CMMagnetometerData?
}
任何想法在这里发生了什么?
修改:
已将项目的精简版添加到github here
修改
zombies instrument的屏幕截图:
答案 0 :(得分:2)
好的,我会尝试做一些思考实验,以提出可能会发生的事情。
首先请记住以下几点:
您的MotionDataRecord是一个几乎完全由引用类型实例属性组成的结构。这会强制结构参与引用计数。
您正在不同的线程上疯狂地访问此结构的属性。您的locationManager:didUpdateLocations:
在主要帖子上设置motionDataRecord.location
,例如您的motionManager.startDeviceMotionUpdates
在后台帖子motionDataRecord.deviceMotion
上设置queue
。
每次设置struct属性时,都会改变struct。但实际上Swift中没有结构变异这样的东西:结构是一种值类型。真正发生的是整个结构被复制和替换(僵尸日志中的initializeBufferWithCopyOfBuffer
)。
好的,所以你要进入多个同时进行的线程并替换你的struct-full-of-references。每次执行此操作时,一个结构副本将不存在,另一个结构副本将存在。它是一个完整的引用结构,所以这涉及引用计数。
所以假设这个过程是这样的:
制作新结构。
通过复制引用,将新结构的引用属性设置为旧结构的引用属性(我们要更改的属性除外)。这里有一些保留和释放,但它都可以平衡。
设置我们要替换的新结构的引用属性。这会保留新值并释放旧值。
将新结构交换到位。
但这些都不是 atomic 。因此,这些步骤可能无序运行,彼此之间交错,因为(记住)您有多个线程同时访问结构。因此,想象一下,在另一个线程上,我们访问步骤3和4之间的结构。特别是,在一个线程上的步骤3和4之间,我们在另一个线程上执行步骤1和2。在那一刻,旧的结构仍然存在,它引用了我们正在替换的属性指向垃圾(因为它在第一个线程的第3步中被释放和释放)。我们尝试在垃圾属性上进行复制。崩溃。
因此,简而言之,我建议(1)使MotionDataRecord成为一个类而不是结构,(2)让你的线程理顺(至少,在你之前进入CMMotionManager回调中的主线程)触摸MotionDataRecord)。