2016-12-09 104 views
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或者它的一些特性是什么被莫名其妙地释放......

MotionDataRecordstruct

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 screenshot

+0

@matt不,我不这么认为。它似乎是正在解除分配的'MotionDataRecord'的CoreMotion/Location属性。我假设他们通过引用传递,所以也许我需要为该数据创建自己的结构,而不是将这些类设置为“MotionDataRecord”结构属性。这似乎有点麻烦,是否有更好的方法来解决这个问题? – doovers

+0

@matt我已经将项目的精简版本添加到GitHub中(请参阅问题结尾处的链接)。非常感谢,如果你能检查出来。我完全沉迷于这一个! – doovers

+0

不要将静态引用用于Objective-C或Swift对象。如果你需要有一个长寿命的对象,请将应用程序委托关闭,然后在完成时将其删除。这是你的问题的根源。 – MoDJ

回答

2

好吧,我将尝试做一些思考实验来提示可能发生的事情。

记住第一以下几点:

  • 你MotionDataRecord是由几乎全部的引用类型实例属性的结构体。这迫使结构参与引用计数。

  • 您正在不同线程上疯狂地访问此结构的属性。您的locationManager:didUpdateLocations:在主线程上设置motionDataRecord.location,而例如,您的motionManager.startDeviceMotionUpdates在后台线程上设置motionDataRecord.deviceMotionqueue)。

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

好的,所以在多个并发线程中,您将进入并替换您的struct-full-of-references。每当你这样做时,一个结构拷贝就会消失,另一个结构就会出现。它是一个结构完整的引用,所以这涉及引用计数。

因此,假设的过程是这样的:

  1. 使新的结构。

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

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

  4. 将新结构交换到位。

但是没有一个是原子。因此,这些步骤可能无序运行,彼此交错,因为(请记住)您有多个线程同时访问结构。所以设想一下,在另一个线程中,我们访问步骤3和步骤4之间的结构。特别是,在一个线程的步骤3和4之间,我们在另一个线程上执行步骤1和2。那时,旧的结构仍然存在,它引用了我们正在替换的指向垃圾的属性(因为它在第一个线程的第3步中被释放和释放)。我们试图在垃圾属性上做我们的副本。崩溃。所以,简而言之,我会建议(1)使MotionDataRecord成为一个类而不是一个结构体,并且(2)让你的线程变直(最起码,进入CMMotionManager回调的主线程之前你触摸MotionDataRecord)。

+0

很好的解释。现在,我开始对此更加了解。但我有几个问题。 1.如果我将'MotionDataRecord'作为一个类并写入一个函数来返回数据的值类型副本,我还需要确保对该类的所有访问都在同一个线程上?我试图让我的头脑为什么我不能在这个场景中设置来自不同线程的类道具。 2.如果我确实需要将'MotionDataRecord'更新发送到单个线程,那么我认为使用后台线程会更好,因为给定了100Hz下的操作数,例如? – doovers

+0

我想说,整个100Hz的问题是一个单独的问题(如果你把这个问题放在_任何线程上,你将很快耗尽用户的电池)。但是至于你的第一个问题,我会说:不要冒险,不要做任何假设。线程是_hard_,它是_dangerous_。阅读Apple的并发指南并且非常害怕。然后使用每一个安全措施,并且将_all_访问共享对象限制在_one_线程当然是其中之一。也许我过于保守,但这就是我。 – matt

+0

好吧,这是有道理的。我一定会阅读并发指南。感谢您对此的所有帮助,我学到了很多东西,非常感谢! – doovers