0

我有以下用于收集设备运动数据的类:

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

编辑:

僵尸仪器截图:

僵尸仪器截图

4

1 回答 1

2

好的,我将尝试做一些思想实验来建议这里可能发生的事情。

首先要记住以下几点:

  • 您的 MotionDataRecord 是一个几乎完全由引用类型实例属性组成的结构。这会强制结构参与引用计数。

  • 你在不同的线程上疯狂地访问这个结构的属性。您在主线程上locationManager:didUpdateLocations:设置,而例如您在后台线程上设置( )。motionDataRecord.locationmotionManager.startDeviceMotionUpdatesmotionDataRecord.deviceMotionqueue

  • 每次设置结构属性时,都会改变结构。但在 Swift 中实际上并没有结构突变之类的东西:结构是一种值类型。真正发生的是整个结构被复制和替换(initializeBufferWithCopyOfBuffer在僵尸日志中)。

好的,所以在您进入并替换您的 struct-full-of-references 的多个同时线程上。每次您这样做时,一个结构副本不复存在,而另一个结构副本存在。这是一个完全引用的结构,所以这涉及到引用计数。

所以假设这个过程是这样的:

  1. 制作新的结构。

  2. 通过复制引用将新结构的引用属性设置为旧结构的引用属性(我们正在更改的除外)。这里有一些保留和释放,但一切都平衡了。

  3. 设置我们要替换的新结构的引用属性。这会保留新值并释放旧值。

  4. 将新结构交换到位。

但这些都不是原子的。因此,这些步骤可能会乱序运行,彼此交错,因为(请记住)您有多个线程同时访问该结构。所以想象一下,在另一个线程上,我们访问步骤 3 和 4 之间的结构。特别是,在一个线程上的步骤 3 和 4 之间,我们在另一个线程上执行步骤 1 和 2。在那一刻,旧结构仍然存在,它对我们要替换的指向垃圾的属性的引用(因为它在第 3 步中的第一个线程上被释放和释放)。我们尝试在垃圾属性上进行复制。碰撞。

因此,简而言之,我建议 (1) 将 MotionDataRecord 设为类而不是结构,以及 (2) 理顺您的线程(至少,在您触摸 MotionDataRecord 之前进入 CMMotionManager 回调中的主线程)。

于 2016-12-11T16:11:12.677 回答