同时访问0x1c0a7f0f8,但修改需要Xcode 9 beta 4上的独占访问错误

问题描述 投票:20回答:8

我的项目使用Objective-C和Swift代码。当用户登录时,它会为用户首选项调用一组apis,我有一个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中得到修复。任何帮助表示赞赏

swift crash swift4 ios11 xcode9-beta
8个回答
21
投票

我认为这个'bug'可能是一个Swift 4'功能',特别是他们称之为'独家访问内存'。

看看这个WWDC视频。大约50分钟后,这位长发演讲者解释道。

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

如果您乐意忽略它,可以尝试在方案设置中关闭线程消毒剂。但是,调试器试图告诉你一个微妙的线程问题,所以它可能更好地利用你的时间来弄清楚为什么你在读取数据的同时写入了你的数组。


13
投票

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


9
投票

仅适用于Swift 4以及使用.initial选项进行KVO设置时

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


6
投票

在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的值是多少?好吧,我也不知道,编译器在运行此代码时会产生不可预测的结果。在调试模式下Swift 4.0中不允许这样做,在Swift 5.0中它甚至在运行时崩溃。

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


2
投票

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


0
投票

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

0
投票

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


0
投票

@Mark Bridges和@ geek1706的答案都是很好的答案,但我想在这个问题上加上我的2美分并给出一个例子。

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

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

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

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

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

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

protocol Abstraction: class
© www.soinside.com 2019 - 2024. All rights reserved.