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

Swift 同时访问0x1c0a7f0f8,但修改需要在Xcode 9 beta 4上出现独占访问错误,swift,crash,swift4,ios11,xcode9-beta,Swift,Crash,Swift4,Ios11,Xcode9 Beta,我的项目使用Objective-C和Swift代码。当用户登录时,它会为用户首选项调用一组API,我有一个DataCoordinator.swift类来调度API操作,我从UserDetailViewController.m类进行调用以加载用户首选项。在我使用Xcode 9 beta 4将代码迁移到Swift 4之前,这个用法可以正常工作。现在,当我登录时,在DataCoordinator类中给我这个错误,它崩溃了。下面是我的DataCoordinator和Viewcontroller类的示例

我的项目使用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

     }

}

我不确定如何继续,我在上看到了一个bug报告,但它似乎在Xcode 9 beta 3中得到了修复。非常感谢您的帮助

我认为这个“bug”可能是Swift 4的一个“功能”,特别是他们称之为“独占内存访问”的功能

看看这个WWDC视频。在50分钟左右,长发演讲者解释了这一点


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

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

仅在Swift 4中以及使用
.initial
选项进行KVO设置时
如果在observeValue方法中检查上下文,只需将上下文变量设置为静态。这将详细描述此错误。

在目标的生成设置下。从
Swift编译器-代码生成

numberOfSections
覆盖功能中返回零将导致此崩溃:

override func numberOfSections(in collectionView: UICollectionView) -> Int {
    // This causes a crash!    
    return 0
}
简单解决方案-
在上面的函数中返回1
,然后在
collectionView(\uuU2:numberOfItemsInSection:)
函数中返回0

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

我要做的是将
FetchOperation
更改为
class
,而不是Swift 5.0中的
struct

,这将是在发布模式下运行应用程序时的默认行为。在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)
}
打印(计数)行时,计数的值是多少?我也不知道,当你运行这段代码时,编译器会给出不可预测的结果。这在调试模式下的Swift 4.0中是不允许的,在Swift 5.0中甚至在运行时也会崩溃


资料来源:

由@Mark Bridges和@geek1706给出的答案都是很好的答案,但我想在这件事上加上我的2美分,并给出一个一般性的例子

如上所述,这是Swift 4中的一项功能

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

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

没有什么比这更好的例子了: 如果我们试图在多线程环境中使用两个对象之间的抽象(在协议类型上发生变异)来变异共享值,并且
独占内存访问
处于启用状态,我们的应用程序将崩溃

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()
由于我们没有声明
抽象
协议是
绑定的,因此在运行时,在
myMutatingFunc
内部,
自身
的捕获将被视为
结构
,即使我们注入了实际的
类(
MyClass

转义变量通常需要动态强制,而不是 静态强制执行。这是因为斯威夫特无法解释 将调用转义闭包,从而在何时调用变量 访问

解决方案是将
抽象
协议绑定到

protocol Abstraction: class

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

当我按照搜索查询修改谓词并重新加载表时,这种情况发生在我身上,使其重新进入(访问)相同的类对象代码。请检查我的堆栈跟踪,其设置为“searchPhrase”,并再次从DB worker触发重新加载表,然后再返回DB worker(用于项目计数。重新加载数据后)

解决方案对我有效: 在几毫秒或半秒后从DB worker类调用“reloadData”。

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

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

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

基本的修复,但它是有效的。

对我来说,修复它的是在
var
中添加
lazy
,我在Xcode 9 beta 5上也看到了这一点。不是beta 4之前的问题,也不是Xcode 8。在Xcode 9 Beta 6中仍然发生在我身上:(它发生在向MPVolumeViews按钮alpha键路径添加观察者时,并在访问observeValue(forKeyPat)中的上下文时崩溃
DispatchQueue.global(qos: .userInitiated).async {
    // do something
}