2016-12-09 2 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는 또는 그것의 일부 속성은

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 screenshot

+0

@matt 아니, 나는 그렇게 생각하지 않는다. 그것은 할당 취소되는'MotionDataRecord'의 CoreMotion/Location 속성 인 것으로 보입니다. 나는 그들이 참조에 의해 전달되었다고 가정하고 있습니다. 그래서 아마 그 클래스들을'MotionDataRecord' 구조체 속성들로 설정하기보다는 그 데이터를위한 구조체를 직접 만들 필요가 있습니다. 좀 번거로운가 보군요.이 문제를 해결할 더 좋은 방법이 있습니까? – doovers

+0

@matt GitHub에 프로젝트의 버려진 버전을 추가했습니다 (끝 부분의 링크 참조). 확인해 주시면 대단히 감사하겠습니다. 나는 이걸 완전히 뒤엎았다! – doovers

+0

Objective-C 또는 Swift 개체에 정적 참조를 사용하지 마십시오. 오래 살아야하는 객체가 필요하다면, 애플 리케이션 델리게이트에 대한 심판을 걸어 둔다. 이것은 문제의 근본 원인입니다. – MoDJ

답변

2

좋아요, 여기서 일어날 일을 제안하기 위해 약간의 사고 실험을하려고 할 것입니다.

먼저 다음 사항에 유의하십시오

  • 귀하의 MotionDataRecord가 거의 참조 형의 인스턴스 속성으로 구성된 구조체이다. 이렇게하면 구조체가 참조 카운팅에 참여하게됩니다.

  • 다른 스레드에서이 구조체의 속성에 액세스하고 있습니다. 귀하의 locationManager:didUpdateLocations:은 메인 스레드에 motionDataRecord.location을 설정합니다. motionManager.startDeviceMotionUpdatesmotionDataRecord.deviceMotion을 배경 스레드 (queue)로 설정합니다.

  • struct 속성을 설정할 때마다 구조체를 변경합니다. 그러나 실제로 Swift에는 struct mutation과 같은 것이 없습니다. 구조체는 값 유형입니다. 실제로 일어나는 일은 전체 구조체가 복사되고 대체되는 것입니다 (좀비 로그에 initializeBufferWithCopyOfBuffer).

좋아요, 그렇다면 여러 개의 동시 스레드에서 들어오고 struct-full-of-references를 대체하십시오. 당신이 그렇게 할 때마다, 하나의 struct 복사본은 존재하지 않고 다른 복사본은 존재하게됩니다. 이것은 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 질문 전체가 별개의 질문이라고 말하고 싶습니다. (당신이 그 배터리를 _any_ 쓰레드에 올려두면 아무런 시간도 낭비하지 않을 것입니다). 그러나 당신의 첫 번째 질문에 관해서는, 나는 말할 기회가 없습니다. 스레딩은 힘들지만 위험합니다. Apple의 동시성 가이드를 읽고 매우 두려워하십시오. 그렇다면 모든 보호 장치를 사용하고 스레드를 _ 공유하는 공유 객체에 대한 액세스를 제한하는 것은 확실히 그 중 하나입니다. 어쩌면 나는 지나치게 보수적 인 태도를 취하고있다. – matt

+0

좋습니다. 나는 동시성 가이드를 확실히 읽을 것이다. 이것에 대한 모든 도움을 주셔서 감사합니다. 많이 배웠습니다. – doovers

관련 문제