Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simultaneous accesses to 0x1c0a7f0f8, but modification requires exclusive access error on Xcode 9 beta 4

my project uses both Objective-C and Swift code. When a user logs in, it calls a set of apis for user preference, I have a DataCoordinator.swift class which schedules the API operation and I make this calls from UserDetailViewController.m class to load user preferences. This use to work fine before I migrated my code to Swift 4 using Xcode 9 beta 4. Now when I login it crashes by giving me this error in my DataCoordinator class. Below is a sample of my DataCoordinator and Viewcontroller class.

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
}

//This is how I call it in my viewcontrollers viewDidLoad method

__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

     }

}

I'm not sure how to proceed on this, I saw a bug report at https://bugs.swift.org/browse/SR-5119 but it seems to be fixed in Xcode 9 beta 3. Any help is appreciated

like image 202
Francis F Avatar asked Jul 31 '17 12:07

Francis F


5 Answers

I think this 'bug' may be a Swift 4 "feature", specifically something they call "Exclusive access to Memory".

Check out this WWDC video. Around the 50-minute mark, the long-haired speaker explains it.

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

You could try turning the thread sanitizer off in your scheme settings if you're happy to ignore it. However, the debugger is trying to tell you about a subtle threading issue, so it's probably a better use of your time to figure out why you've got something writing to your array at the same time as it's being read from.


UPDATE 2022:

The link is broken, you can watch the video directly here: (Around the 53-minute mark)

https://devstreaming-cdn.apple.com/videos/wwdc/2017/402ynph39nk5sn4222/402/402_hd_whats_new_in_swift.mp4

like image 68
Mark Bridges Avatar answered Nov 16 '22 12:11

Mark Bridges


Under the target's Build Settings. Select No Enforcement for Exclusive Access to Memory from Swift Compiler - Code Generation

like image 44
geek1706 Avatar answered Nov 16 '22 12:11

geek1706


In Swift 5.0, this will be the default behavior when running your application in Release mode. Before 5.0 (Swift 4.2.1 as of today and lower) this behavior is only running when in Debug mode.

Your application will fail in release mode if you ignored this error.

Consider this example:

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

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

What is the value of count, when the print(count) line is printed? Well I don't know either and the compiler gives unpredicatable results when you run this code. This isn't allowed in Swift 4.0 in debug mode and in Swift 5.0 it crashes even in runtime.

Source: https://swift.org/blog/swift-5-exclusivity/

like image 13
J. Doe Avatar answered Nov 16 '22 14:11

J. Doe


Only in Swift 4 and when using .initial option for your KVO Settings

If you check your context in observeValue method, just make your context variable static. This blog post describes this bug in detail.

like image 12
Ralf Hundewadt Avatar answered Nov 16 '22 13:11

Ralf Hundewadt


Swift 5 here. I was calling a function from a property didSet and testing another property from the same object and I got this error.

I fixed it by calling my function from within another thread:

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

Basic fix but it works.

like image 7
Skoua Avatar answered Nov 16 '22 13:11

Skoua