Что является причиной зомби в следующем коде
У меня есть следующий класс для сбора данных о движении устройства:
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 здесь
Редактировать:
Скриншот инструмента зомби:
1 ответ
Хорошо, я попытаюсь провести небольшой мысленный эксперимент, чтобы предположить, что здесь может происходить.
Имейте в виду в первую очередь следующие моменты:
Ваш MotionDataRecord - это структура, состоящая почти полностью из свойств экземпляра ссылочного типа. Это заставляет структуру участвовать в подсчете ссылок.
Вы дико обращаетесь к свойствам этой структуры в разных потоках. Ваш
locationManager:didUpdateLocations:
наборыmotionDataRecord.location
в основной теме, в то время как, например, вашmotionManager.startDeviceMotionUpdates
наборыmotionDataRecord.deviceMotion
в фоновом потоке (queue
).Каждый раз, когда вы устанавливаете свойство структуры, вы изменяете структуру. Но на самом деле в Swift нет такой вещи, как структурная мутация: структура является типом значения. Что действительно происходит, так это то, что вся структура копируется и заменяется (
initializeBufferWithCopyOfBuffer
в журнале зомби).
Итак, в нескольких одновременных потоках вы входите и заменяете свои struct-full-of-links. Каждый раз, когда вы делаете это, одна копия структуры исчезает, а другая появляется. Это структура, полная ссылок, так что это включает в себя подсчет ссылок.
Итак, предположим, что процесс выглядит так:
Создайте новую структуру.
Установите свойства ссылки новой структуры на свойства ссылки старой структуры (кроме той, которую мы меняем), скопировав ссылки. Здесь есть некоторые сохранения и освобождения, но все это уравновешивается.
Установите свойство ссылки новой структуры, которое мы заменяем. Это сохраняет новое значение и освобождает старое значение.
Поменяйте местами новую структуру.
Но ничего из этого не является атомным. Таким образом, эти шаги могут выполняться не по порядку, чередуясь друг с другом, потому что (помните), у вас есть несколько потоков, обращающихся к структуре одновременно. Итак, представьте, что в другом потоке мы обращаемся к структуре между шагами и 3 и 4. В частности, между шагами 3 и 4 в одном потоке мы выполняем шаги 1 и 2 в другом потоке. В этот момент старая структура все еще на месте, и ее ссылка на заменяемое нами свойство указывает на мусор (поскольку он был освобожден и освобожден на шаге 3 в первом потоке). Мы пытаемся сделать нашу копию на свойстве мусора. Краш.
Итак, в двух словах, я хотел бы предложить (1) сделать MotionDataRecord классом вместо структуры, и (2) выправить поток в потоке (по крайней мере, попасть в основной поток в обратных вызовах CMMotionManager, прежде чем вы коснетесь MotionDataRecord).