2016-12-09 154 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