【问题标题】:Simultaneous accesses to 0x1c0a7f0f8, but modification requires exclusive access error on Xcode 9 beta 4同时访问 0x1c0a7f0f8,但修改需要 Xcode 9 beta 4 上的独占访问错误
【发布时间】:2018-01-07 00:38:23
【问题描述】:

我的项目同时使用了 Objective-C 和 Swift 代码。当用户登录时,它会调用一组用于用户首选项的 api,我有一个 DataCoordinator.swift 类来调度 API 操作,我从 UserDetailViewController.m 类进行调用以加载用户首选项。在我使用 Xcode 9 beta 4 将代码迁移到 Swift 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
}

//这就是我在viewcontrollers 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 中修复。感谢任何帮助

【问题讨论】:

  • 我在 Xcode 9 beta 5 上也看到了这一点。不是 beta 4 之前的问题,或者是 Xcode 8。仍在挖掘。
  • 在 Xcode 9 Beta 6 中仍然发生在我身上 :( 当将观察者添加到 MPVolumeViews 按钮 alpha 键路径并在访问 observeValue(forKeyPath:of:change:object:) 中的上下文时崩溃时会发生这种情况
  • 你知道这个运行时检查是在哪一行触发的吗?地址0x1c0a7f0f8的对象是什么?
  • 通用汽车也有这种情况吗??
  • @Sparga,好像触发了@line get { return preferenceFetchOperations.count }

标签: swift crash swift4 ios11 xcode9-beta


【解决方案1】:

我认为这个“错误”可能是 Swift 4 的“功能”,特别是他们称之为“对内存的独占访问”的东西。

观看此 WWDC 视频。在 50 分钟左右,长发演讲者解释了它。

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

如果您愿意忽略它,可以尝试在方案设置中关闭线程清理器。但是,调试器正试图告诉您一个微妙的线程问题,因此最好利用您的时间来尝试找出为什么在读取数组的同时写入了一些内容。

【讨论】:

  • 这有一些显着的微妙之处。首先,如果你在结构的属性上有一个 didSet 观察者,写访问仍然被强制为活动。其次,如果这是在类的属性上,不强制执行写访问,因为某些引用语义回避了这个问题。最后,如果突变发生通过协议类型,它取决于协议类型是否绑定class。否则,即使运行时类型毕竟是引用类型,它也会以struct 级别的限制运行。我不希望 ppl 遵循没有示例代码。
  • @BaseZen 我发现你的评论对我来说真的很有用?。我有一个问题,您是否有任何论文涵盖了您提到的这个主题:“......这取决于协议类型是否是类绑定的。如果不是,它的结构级别......”
  • 如果您收到此错误并且它源于默认协议实现中的 func 修改了另一个协议变量,只需通过使其继承自 AnyObject 来绑定协议类。这在我的特定场景中解决了它。
【解决方案2】:

在目标的构建设置下。从Swift Compiler - Code Generation 中为Exclusive Access to Memory 选择No Enforcement

【讨论】:

  • 请注意,这不会自动解决实际的数据竞争问题。然而,Thread Sanitizer 可能会发现误报——如果变异函数设置了自己的同步原语,这种情况很常见。
  • 很遗憾,在 Xcode 12 中,您只能选择 Compile-time Enforcement Only
【解决方案3】:

仅在 Swift 4 中以及在您的 KVO 设置中使用 .initial 选项时

如果您在 observeValue 方法中检查上下文,只需将上下文变量设为 静态。这个blog post 详细描述了这个错误。

【讨论】:

  • @PauloMattos:“使您的上下文变量静态化”这句话绝对是答案。如果您发现它错了,您可以对帖子投反对票。请参阅该元帖子 - meta.stackexchange.com/questions/225370/… - 以区分 NAA(非答案)和答案。
  • @Tsyvarev 是的,简短但看起来确实符合我们当前的准则。我删除了我的评论并感谢您的提醒;)
  • 谢谢!如果您的代码在上下文变量周围崩溃了 observeValue,这绝对是答案!
【解决方案4】:

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)
}

打印 print(count) 行时,count 的值是多少?好吧,我也不知道,当您运行此代码时,编译器会给出不可预测的结果。这在 Swift 4.0 的调试模式下是不允许的,而在 Swift 5.0 中,即使在运行时也会崩溃。

来源:https://swift.org/blog/swift-5-exclusivity/

【讨论】:

    【解决方案5】:

    这里是 Swift 5。 我从一个属性 didSet 调用一个函数,并从同一个对象测试另一个属性,我得到了这个错误。

    我通过在另一个线程中调用我的函数来修复它:

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

    基本修复,但有效。

    【讨论】:

    • 这适用于我的情况,因为我正在通过 didSet 更新属性以及在主线程上调用 setNeedsUpdate。因此更新全局线程上的属性对我有用。
    【解决方案6】:

    @Mark Bridges 和 @geek1706 的答案很好,但我想就此事加我 2 美分并举一个一般性的例子。

    如上所述,这是 Swift 4 SE-176 中的一项功能。

    当然,仍应允许实现检测并发冲突访问。一些程序员可能希望使用可选的线程安全强制机制,至少在某些构建配置中是这样。

    独占访问强制 vars 的每个写入突变在访问该变量时都必须是独占的。在多线程环境中,多个线程访问一个共享变量,并且一个或多个线程可以修改它。

    没有什么比得上一个很好的例子了: 如果我们尝试在多线程环境中使用 2 个对象之间的抽象(在协议类型上发生突变)来改变共享值并且 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,即使我们注入了实际的@987654329 @ (MyClass)。

    转义变量通常需要动态强制执行,而不是 静态执法。这是因为 Swift 无法推断何时 转义闭包将被调用,因此当变量将被调用时 访问。

    解决方案是将Abstraction协议绑定到class

    protocol Abstraction: class
    

    【讨论】:

    • 这正是我的情况。只是提醒一下:类已被弃用,我们应该使用 AnyObject 代替
    【解决方案7】:

    我要做的是将FetchOperation 更改为class 而不是struct

    【讨论】:

      【解决方案8】:

      在我的例子中,Swift 4 实际上发现了一种错误,直到我开始从多个地方调用一个函数时才注意到。我的函数传递了一个 inout 全局数组,它同时引用了该参数和全局名称。当我将函数更改为仅引用参数时,“同时访问”错误消失了。

      【讨论】:

        【解决方案9】:

        为我解决的问题是将lazy 添加到var

        【讨论】:

          【解决方案10】:

          numberOfSections 覆盖函数中返回零将导致此崩溃:

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

          简单的解决方案 - 上面函数中的return 1,然后collectionView(_:numberOfItemsInSection:) 函数中的return 0

          override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
          

          【讨论】:

            【解决方案11】:

            就我而言,我在构建项目的过程中更改了表格高度。当时我的设备已连接到网络。我删除了派生数据,它为我解决了这个问题。

            【讨论】:

              【解决方案12】:

              这发生在我身上,当我根据搜索查询修改谓词并重新加载表时,它可以重新进入(访问)相同的类对象代码。请检查我的堆栈跟踪,它设置了“searchPhrase”,并从 DB 工作人员再次触发表重新加载,这又再次返回到 DB 工作人员(用于项目计数。在 reloadData 之后)。

              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
              

              解决方案对我有用: 几毫秒或半秒后从 DB 工作者类调用“reloadData”。

              【讨论】:

                猜你喜欢
                • 2019-01-11
                • 2018-03-07
                • 1970-01-01
                • 2019-05-06
                • 1970-01-01
                • 2018-02-21
                • 2019-10-28
                • 2018-03-14
                • 2018-02-17
                相关资源
                最近更新 更多