Одновременный доступ к 0x1c0a7f0f8, но модификация требует монопольной ошибки доступа в Xcode 9 beta 4

мой проект использует код Objective-C и Swift. Когда пользователь входит в систему, он вызывает набор API для пользовательских настроек. У меня есть класс DataCoordinator.swift, который планирует работу API, и я делаю эти вызовы из класса UserDetailViewController.m для загрузки пользовательских настроек. Это использование работало нормально, прежде чем я перенес свой код на Swift 4 с помощью Xcode 9 beta 4. Теперь, когда я вхожу в систему, он падает, выдавая мне эту ошибку в моем классе DataCoordinator. Ниже приведен пример моего класса DataCoordinator и Viewcontroller.

DataCoordinator.swift

import UIKit

@objcMembers

class DataCoordinator: NSObject {

    //MARK:- Private
    fileprivate var user = myDataStore.sharedInstance().user
    fileprivate var preferenceFetchOperations = [FetchOperation]()

    fileprivate func scheduleFetchOperation(_ operation:FetchOperation, inFetchOperations operations:inout [FetchOperation]) {
        guard  operations.index(of: operation) == nil else { return }
        operations.append(operation)
    }

    fileprivate func completeFetchOperation(_ fetchOperation:FetchOperation, withError error:Error?, andCompletionHandler handler:@escaping FetchCompletionHandler) {

        func removeOperation(_ operation:FetchOperation, fromOperations operations:inout [FetchOperation]) {
            if operations.count > 0 {
                operations.remove(at: operations.index(of: fetchOperation)!)                 
              handler(error)
            }
        }

        if preferenceFetchOperations.contains(fetchOperation) {
            removeOperation(fetchOperation, fromOperations: &preferenceFetchOperations)
        }

    }

    fileprivate func schedulePreferencesFetchOperation(_ serviceName:String, fetch:@escaping FetchOperationBlock){
        let operation = FetchOperation(name: serviceName, fetch: fetch);
        scheduleFetchOperation(operation, inFetchOperations: &preferenceFetchOperations)
    }


    fileprivate func runOperationsIn(_ fetchOperations:inout [FetchOperation]) {
        for  var operation in fetchOperations {
            guard operation.isActivated == false else { continue }
            operation.isActivated = true
            operation.execute()
        }
    }


    //MARK:- Non-Private
    typealias FetchCompletionHandler = (_ error:Error?)->Void

    var numberOfPreferencesFetchCalls:Int {
        get { return preferenceFetchOperations.count }
    }


    // MARK: -
    func fetchPreferences(_ completionHandler:@escaping FetchCompletionHandler) -> Void {
        defer {
            runOperationsIn(&preferenceFetchOperations)
        }

        schedulePreferencesFetchOperation("com.fetchPreferences.type1") {[unowned self] (operation:FetchOperation) in
            WebServiceManager.getType1Detail(for: user) {[unowned self] (error) in
                self.completeFetchOperation(operation,  withError: error, andCompletionHandler: completionHandler)
            }

        }

        schedulePreferencesFetchOperation("com.fetchPreferences.type2") {[unowned self] (operation:FetchOperation) in
            WebServiceManager.getType2Detail(for: user) {[unowned self] (error) in
                self.completeFetchOperation(operation,  withError: error, andCompletionHandler: completionHandler)
            }

        }

        schedulePreferencesFetchOperation("com.fetchPreferences.type3") {[unowned self] (operation:FetchOperation) in
            WebServiceManager.getType3Detail(for: user) {[unowned self] (error) in
                self.completeFetchOperation(operation,  withError: error, andCompletionHandler: completionHandler)
            }

        }

        schedulePreferencesFetchOperation("com.fetchPreferences.type4") {[unowned self] (operation:FetchOperation) in
            WebServiceManager.getType4Detail(for: user) {[unowned self] (error) in
                self.completeFetchOperation(operation,  withError: error, andCompletionHandler: completionHandler)
            }

        }
    }

}


// MARK:- Fetch Operation Struct
private typealias FetchOperationBlock = (_ operation:FetchOperation)->Void

private struct FetchOperation:Hashable {
    fileprivate var runToken = 0
    fileprivate let fetchBlock:FetchOperationBlock

    let name:String!
    var isActivated:Bool {
        get {
            return runToken == 0 ? false : true
        }

        mutating set {
            if runToken == 0 && newValue == true {
                runToken = 1
            }
        }
    }

    fileprivate var hashValue: Int {
        get {
            return name.hashValue
        }
    }

    func execute() -> Void {
        fetchBlock(self)
    }

    init (name:String, fetch:@escaping FetchOperationBlock) {
        self.name = name
        self.fetchBlock = fetch
    }
}
private func ==(lhs: FetchOperation, rhs: FetchOperation) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

// Вот как я называю это в моем методе viewDidLoad в контроллерах представлений

__weak UserDetailViewController *weakSelf = self;
[self.dataCoordinator fetchPreferences:^(NSError * _Nullable error) {
                if (error == nil) {
                    [weakSelf didFetchPrefrences];
                }
                else {
                    // handle error
                }
            }];

//completion response
- (void)didFetchPrefrences {

    //when api calls complete load data
    if (self.dataCoordinator.numberOfPreferencesFetchCalls == 0) {

        //Load details

     }

}

Я не знаю, что делать дальше, я видел отчет об ошибке по адресу https://bugs.swift.org/browse/SR-5119, но, похоже, это исправлено в Xcode 9 beta 3. Любая помощь приветствуется


person Francis F    schedule 31.07.2017    source источник
comment
Я также вижу это на Xcode 9 beta 5. Не проблема до бета-версии 4 или Xcode 8. Все еще копаю.   -  person pho0    schedule 09.08.2017
comment
Все еще происходит со мной в Xcode 9 Beta 6 :( это происходит, когда я добавляю наблюдателя к альфа-пути кнопки MPVolumeViews и происходит сбой при доступе к контексту вObservValue(forKeyPath:of:change:object:)   -  person fruitcoder    schedule 24.08.2017
comment
Вы знаете, в какой строке запускается эта проверка во время выполнения? Что за объект по адресу 0x1c0a7f0f8?   -  person Sparga    schedule 01.09.2017
comment
Это происходит и в GM??   -  person Rishab    schedule 13.09.2017
comment
@Sparga, похоже, это вызывает @line get {return preferenceFetchOperations.count}   -  person Francis F    schedule 14.09.2017
comment
@Rishab, да, это происходит и в выпуске GM. В Swift 4 это функция, позволяющая избежать одновременного доступа к памяти, но я не уверен, как это исправить. Он не сообщает никакой информации о том, к каким методам осуществляется одновременный доступ.   -  person Francis F    schedule 14.09.2017
comment
Получил это с вызовом swap(). Вау. Где была эта урна, чтобы бросить этот Свифт?   -  person qwerty_so    schedule 25.03.2018
comment
У меня была такая же ошибка, но в моем случае я пытался выполнить операцию пользовательского интерфейса в фоновом потоке. Как только я переместил операцию пользовательского интерфейса для вызова в основном потоке, этот сбой прекратился. DispatchQueue.main.async { // здесь операция пользовательского интерфейса }   -  person C0D3    schedule 14.09.2020


Ответы (12)


Я думаю, что эта «ошибка» может быть «функцией» Swift 4, в частности, тем, что они называют «эксклюзивным доступом к памяти».

Посмотрите это видео WWDC. Примерно на 50-й минуте длинноволосый спикер объясняет это.

https://developer.apple.com/videos/play/wwdc2017/402/?time=233

Вы можете попробовать отключить дезинфицирующее средство потока в настройках схемы, если вы готовы его игнорировать. Однако отладчик пытается сообщить вам о тонкой проблеме с многопоточностью, поэтому, вероятно, лучше использовать ваше время, чтобы попытаться выяснить, почему у вас есть что-то, записывающее в ваш массив в то же время, когда оно читается.

person Mark Bridges    schedule 05.09.2017
comment
В этом есть несколько замечательных тонкостей. Прежде всего, если у вас есть наблюдатель didSet для свойства структуры, доступ для записи по-прежнему применяется как активный. Во-вторых, если это относится к свойству класса, доступ для записи не применяется из-за некоторой семантики ссылок, которая позволяет обойти проблему. Наконец, если мутация происходит посредством типа протокола, это зависит от того, является ли тип протокола class связанным или нет. В противном случае он действует с уровнем ограничений struct, даже если тип времени выполнения в конце концов является ссылочным типом. Я не ожидаю, что люди будут следовать без примера кода. - person BaseZen; 26.10.2018
comment
@BaseZen Я нашел ваш комментарий очень полезным в моем случае ????. У меня есть вопрос, есть ли у вас документ, посвященный этой теме, которую вы упомянули: ... это зависит от того, привязан ли тип протокола к классу или нет. Если нет, он действует со структурным уровнем ... - person Robert; 31.01.2019
comment
Если вы получаете эту ошибку, и она связана с функцией внутри реализации протокола по умолчанию, которая изменяет другую переменную протокола, просто сделайте класс протокола связанным, унаследовав его от AnyObject. Это решило это в моем конкретном сценарии. - person luxo; 21.12.2019

В настройках сборки цели. Выберите No Enforcement для Exclusive Access to Memory из Swift Compiler - Code Generation

person geek1706    schedule 13.01.2018
comment
Просто имейте в виду, что это не устраняет автоматически фактические проблемы гонки данных. Тем не менее, Thread Sanitizer может обнаруживать ложные срабатывания, что довольно часто бывает, если изменяющие функции имеют свои собственные настройки примитива синхронизации. - person CouchDeveloper; 18.04.2018
comment
К сожалению, в Xcode 12 вы можете выбрать только Compile-time Enforcement Only. - person LinusGeffarth; 12.10.2020

Только в Swift 4 и при использовании опции .initial для настроек KVO.

Если вы проверяете свой контекст в методеObservValue, просто сделайте переменную контекста статической. Эта сообщение в блоге подробно описывает эту ошибку.

person Ralf Hundewadt    schedule 22.11.2017
comment
@PauloMattos: Фраза «сделать переменную контекста статической» определенно является ответом. Если вы сочтете это неправильным, вы можете проголосовать за пост. См. этот метапост - meta.stackexchange.com/questions/225370/ - для различения NAA (не-ответ) и ответа. - person Tsyvarev; 31.05.2018
comment
@Tsyvarev Да, коротко, но похоже, что это действительно соответствует нашим текущим рекомендациям. Я удалил свой комментарий и спасибо за голову;) - person Paulo Mattos; 31.05.2018
comment
Спасибо! Если ваш код дает сбой в методеObservValue вокруг переменной контекста, это определенно является ответом! - person tuttu47; 14.02.2019

В Swift 5.0 это будет поведением по умолчанию при запуске приложения в режиме выпуска. До 5.0 (Swift 4.2.1 на сегодняшний день и ниже) это поведение работает только в режиме отладки.

Ваше приложение не сможет работать в режиме выпуска, если вы проигнорируете эту ошибку.

Рассмотрим этот пример:

func modifyTwice(_ value: inout Int, by modifier: (inout Int) -> ()) {
  modifier(&value)
  modifier(&value)
}

func testCount() {
  var count = 1
  modifyTwice(&count) { $0 += count }
  print(count)
}

Каково значение count, когда печатается строка print(count)? Ну, я тоже не знаю, и компилятор дает непредсказуемые результаты, когда вы запускаете этот код. Это не разрешено в Swift 4.0 в режиме отладки, а в Swift 5.0 происходит сбой даже во время выполнения.

Источник: https://swift.org/blog/swift-5-exclusivity/

person J. Doe    schedule 08.02.2019

Ответы @Mark Bridges и @geek1706 - хорошие ответы, но я хотел бы добавить свои 2 цента по этому поводу и привести общий пример.

Как указано выше, это функция Swift 4 SE-176.

Разумеется, реализации должно быть разрешено обнаруживать одновременный конфликтный доступ. Некоторые программисты могут захотеть использовать вместо этого механизм обеспечения безопасности потоков, по крайней мере, в некоторых конфигурациях сборки.

Эксклюзивный доступ требует, чтобы каждая мутация записи vars была эксклюзивной при доступе к этой переменной. В многопоточной среде несколько потоков обращаются к общей переменной, и один или несколько могут ее изменять.

Нет ничего лучше хорошего примера: если мы попытаемся изменить общее значение в многопоточной среде, используя абстракцию (мутация происходит в типе протокола) между двумя объектами и включенным Exclusive Access to Memory, наше приложение рухнет.

protocol Abstraction {
  var sharedProperty: String {get set}
}

class MyClass: Abstraction {
  var sharedProperty: String

  init(sharedProperty: String) {
     self.sharedProperty = sharedProperty
  }

  func myMutatingFunc() {
     // Invoking this method from a background thread
     sharedProperty = "I've been changed"
  }
}


class MainClass {
   let myClass: Abstraction

   init(myClass: Abstraction) {
     self.myClass = myClass
   }

   func foobar() {
      DispatchQueue.global(qos: .background).async {
         self.myClass.myMutatingFunc()
      }
   }
}

let myClass = MyClass(sharedProperty: "Hello")
let mainClass = MainClass(myClass: myClass)
// This will crash
mainClass.foobar()

Поскольку мы не указали, что протокол Abstraction связан с class, во время выполнения внутри myMutatingFunc захват self будет рассматриваться как struct, даже если мы ввели фактический class (MyClass).

Экранирующие переменные обычно требуют динамического применения вместо статического. Это связано с тем, что Swift не может рассуждать о том, когда будет вызвано экранирующее закрытие и, следовательно, когда будет осуществлен доступ к переменной.

Решение состоит в том, чтобы связать протокол Abstraction с class:

protocol Abstraction: class
person OhadM    schedule 20.06.2019

Свифт 5 здесь. Я вызывал функцию из свойства didSet и тестировал другое свойство из того же объекта, и я получил эту ошибку.

Я исправил это, вызвав свою функцию из другого потока:

DispatchQueue.global(qos: .userInitiated).async {
    // do something
}

Базовое исправление, но оно работает.

person Skoua    schedule 07.06.2020
comment
В моем случае это сработало, так как я обновлял свойство через didSet, а также вызывал setNeedsUpdate в основном потоке. Таким образом, у меня сработало обновление свойства в глобальном потоке. - person Anshuman Singh; 07.12.2020

Я бы заменил FetchOperation на class вместо struct.

person Tai Le    schedule 19.01.2019

В моем случае Swift 4 на самом деле обнаружил ошибку, которую я бы не заметил, пока не начал вызывать функцию из более чем одного места. Моей функции был передан глобальный массив inout, и она ссылалась как на этот параметр, так и на глобальное имя. Когда я изменил функцию, чтобы ссылаться только на параметр, ошибка «одновременный доступ» исчезла.

person KenM    schedule 10.10.2017

Что исправило это для меня, так это добавление lazy к var

person Amr    schedule 14.01.2021

Возврат нуля в функции переопределения numberOfSections вызовет этот сбой:

override func numberOfSections(in collectionView: UICollectionView) -> Int {
    // This causes a crash!    
    return 0
}

Простое решение — return 1 в функции выше, а затем return 0 в функции collectionView(_:numberOfItemsInSection:).

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
person JonJ    schedule 04.09.2018

В моем случае я изменил высоту стола в процессе сборки проекта. В то время мое устройство было подключено к сети. Я удалил производные данные, и это решило проблему для меня.

person Raj Mohan    schedule 15.09.2019

Это происходило со мной, когда я модифицировал предикат в соответствии с поисковым запросом, а также перезагружал таблицу, чтобы ее повторный вход (доступ) к тому же объектному коду класса. Пожалуйста, проверьте мою трассировку стека, ее настройку «searchPhrase» и от рабочего БД снова запускает перезагрузку таблицы, которая, в свою очередь, снова возвращается к рабочему БД (для количества элементов после перезагрузки данных).

0    libswiftCore.dylib                 0x000000010a325590 swift_beginAccess + 568
1    Groupe                             0x00000001063d5cf0 SCPickUsersInteractor.dbWorker.getter + 59
2    Groupe                             0x00000001063d5830 SCPickUsersInteractor.count.getter + 58
3    Groupe                             0x00000001063d8550 protocol witness for SCPickUsersInteractorProtocol.count.getter in conformance SCPickUsersInteractor + 14
4    Groupe                             0x0000000105a340d0 SCPickUsersViewController.tableView(_:numberOfRowsInSection:) + 278
5    Groupe                             0x0000000105a34280 @objc SCPickUsersViewController.tableView(_:numberOfRowsInSection:) + 76
6    UIKitCore                          0x00007fff482a3537 -[UITableView _numberOfRowsInSection:] + 62
7    UIKitCore                          0x00007fff482b3c42 -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 1938
8    UIKitCore                          0x00007fff482b85cd -[UITableViewRowData numberOfRows] + 67
9    UIKitCore                          0x00007fff4827362d -[UITableView noteNumberOfRowsChanged] + 117
10   UIKitCore                          0x00007fff48271b8b -[UITableView reloadData] + 1426
11   Groupe                             0x0000000105a35890 SCPickUsersViewController.reloadTableView() + 152
12   Groupe                             0x0000000105a37060 protocol witness for SCListUpdatesProtocol.reloadTableView() in conformance SCPickUsersViewController + 9
13   Groupe                             0x00000001068a14f0 SCPickUsersPresenter.reloadTableView() + 158
14   Groupe                             0x00000001068a2350 protocol witness for SCListUpdatesProtocol.reloadTableView() in conformance SCPickUsersPresenter + 17
15   Groupe                             0x0000000105a8bc90 SCPickUsersDBWorker.searchPhrase.didset + 911
16   Groupe                             0x0000000105a8c0e0 SCPickUsersDBWorker.searchPhrase.setter + 356
17   Groupe                             0x0000000105a8ffb0 protocol witness for SCFRCProtocol.searchPhrase.setter in conformance SCPickUsersDBWorker + 37
18   Groupe                             0x00000001063d6500 SCPickUsersInteractor.searchPhrase.setter + 274
19   Groupe                             0x00000001063d8630 protocol witness for SCPickUsersInteractorProtocol.searchPhrase.setter in conformance SCPickUsersInteractor + 17
20   Groupe                             0x0000000105a34eb0 SCPickUsersViewController.searchBar(_:textDidChange:) + 322
21   Groupe                             0x0000000105a35020 @objc SCPickUsersViewController.searchBar(_:textDidChange:) + 105

Решение сработало для меня: вызвал «reloadData» из рабочего класса БД через несколько миллисекунд или полсекунды.

person infiniteLoop    schedule 09.01.2020