55

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

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
Francis F
  • 3,157
  • 3
  • 41
  • 79
  • I'm seeing this as well on Xcode 9 beta 5. Not an issue pre-beta 4 or is Xcode 8. Still digging. – pho0 Aug 09 '17 at 17:10
  • Still happening to me in Xcode 9 Beta 6 :( it happens when a add an observer to an MPVolumeViews button alpha keypath and crashes when accessing the context in observeValue(forKeyPath:of:change:object:) – fruitcoder Aug 24 '17 at 09:31
  • Do you know at what line this runtime check is triggered? What is the object at address `0x1c0a7f0f8`? – Sparga Sep 01 '17 at 18:38
  • Is it happening in GM too?? – Rishab Sep 13 '17 at 10:29
  • @Sparga, it seems to trigger @ line get { return preferenceFetchOperations.count } – Francis F Sep 14 '17 at 07:00
  • @Rishab, yes, its happening in GM release too. In Swift 4 this is a feature to avoid simultaneous memory access, but I'm not sure how to proceed on fixing this. It doesn't tell any info on which methods are accessing simultaneously. – Francis F Sep 14 '17 at 07:04
  • Got this with a swap() call. Whoa. Where was that bin to throw that Swift in? – qwerty_so Mar 25 '18 at 10:13
  • I was having the same error but in my case I was trying to execute a UI operation on a background thread. As soon as I moved the UI operation to be called on the main thread, this crash stopped. DispatchQueue.main.async { // UI operation here } – C0D3 Sep 13 '20 at 23:13

15 Answers15

37

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

0xKayvan
  • 368
  • 1
  • 4
  • 18
Mark Bridges
  • 8,228
  • 4
  • 50
  • 65
  • 21
    This has some remarkable subtleties. First of all if you have a didSet observer on a property of a struct, *write access is still enforced as active*. Second, if this is on a property of a class, *write access is not enforced* due to some reference semantics that sidesteps the issue. Finally, if the mutation occurs *through* a protocol type, it *depends* if the protocol type is `class` bound or not. When not, it acts with the `struct` level of restriction even if the runtime type is a reference type after all. I don't expect ppl to follow w/o example code. – BaseZen Oct 26 '18 at 03:16
  • 3
    @BaseZen I found your comment really useful in my case . I have question, do you have any paper which covers this topic which you mentioned: "...it depends if the protocol type is class bound or not. When not, it acts with the struct level of ..." – Robert Jan 31 '19 at 11:43
  • 1
    If you are receiving this error and it stems from a func inside a default protocol implementation that modifies another protocol variable, simply make the protocol class bound by having it inherit from `AnyObject`. This solved it in my specific scenario. – luxo Dec 20 '19 at 21:06
20

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

geek1706
  • 980
  • 9
  • 16
  • 3
    Just be aware, that this does not automatically fix actual data race issues. Thread Sanitizer may however find false positives - which quite often is the case if the mutating functions have their own synchronisation primitive set up. – CouchDeveloper Apr 18 '18 at 10:47
  • 3
    Unfortunately, in Xcode 12, you can only select `Compile-time Enforcement Only`. – LinusGeffarth Oct 12 '20 at 08:45
15

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/

J. Doe
  • 12,159
  • 9
  • 60
  • 114
12

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.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Ralf Hundewadt
  • 1,078
  • 1
  • 13
  • 25
  • @PauloMattos: The phrase "make your context variable static" is definitely **an answer**. If you find it wrong, you may downvote the post. See that meta post - https://meta.stackexchange.com/questions/225370/your-answer-is-in-another-castle-when-is-an-answer-not-an-answer - for differentiate between NAA(Not-an-answer) and an answer. – Tsyvarev May 31 '18 at 12:53
  • @Tsyvarev Yep, short but looks like it really fits our current guidelines. I removed my comment and thanks for the heads up ;) – Paulo Mattos May 31 '18 at 14:10
  • Thanks! If your code is crashing observeValue around the context variable, this definitely is the answer! – tuttu47 Feb 14 '19 at 12:04
8

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.

Skoua
  • 3,373
  • 3
  • 38
  • 51
  • This worked in my case, as I was updating the property via the didSet as well as calling the setNeedsUpdate on the main thread. Thereby updating the property on the global thread worked for me. – Anshuman Singh Dec 07 '20 at 09:09
7

The answers by @Mark Bridges and @geek1706 are good answers but I would like to add my 2 cents about this matter and give a general example.

As stated above this is a feature in Swift 4 SE-176.

The implementation should still be permitted to detect concurrent conflicting accesses, of course. Some programmers may wish to use an opt-in thread-safe enforcement mechanism instead, at least in some build configurations.

The exclusive access enforces that every write mutation of vars must be exclusive when accessing that variable. In a multithread environment, multiple threads accessing a shared var and one or more can modify it.

There's nothing like a good example: If we try to mutate a shared value in a multi-threaded environment using an abstraction (mutation occurs on a protocol type) between 2 objects and the Exclusive Access to Memory is on, our app will crash.

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

Since we didn't state that the Abstraction protocol is class bound, during runtime, inside myMutatingFunc, the capture of self will be treated as struct even though we injected an actual class (MyClass).

Escaping variables generally require dynamic enforcement instead of static enforcement. This is because Swift cannot reason about when an escaping closure will be called and thus when the variable will be accessed.

The solution is to bound the Abstraction protocol to class:

protocol Abstraction: class
OhadM
  • 4,687
  • 1
  • 47
  • 57
  • That was exactly my case. Just a reminder though: class is deprecated, we should use AnyObject instead – Edudjr Feb 14 '22 at 09:45
4

What I would do is change FetchOperation to a class instead of struct.

kevinh1998
  • 66
  • 1
  • 1
  • 10
Tai Le
  • 8,530
  • 5
  • 41
  • 34
3

What fixed it for me, is adding lazy to the var

Amr
  • 2,160
  • 1
  • 15
  • 8
  • this is the solution that worked for me but i dont understand why, explanation please, here my code: `var b = 1 func increment(_ a: inout Int) { a += b print(a) }` – Saravanan V Jun 16 '22 at 17:25
2

In my case, Swift 4 actually uncovered a kind of bug that I wouldn't have noticed until I started calling a function from more than one place. My function was passed an inout global array and it was referencing both that parameter and the global name. When I changed the function to reference only the parameter, the "simultaneous access" error went away.

KenM
  • 623
  • 2
  • 8
  • 14
1

i have the same issue for this code:

    var b = 1
    
    func increment(_ a: inout Int) {
        a += b
        print(a)
    }

but making the var as lazy solves the issue, but i dont understand why. explanation please

0

Returning zero in the numberOfSections override function will cause this crash:

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

Simple solution - return 1 in the function above and then return 0 in the collectionView(_:numberOfItemsInSection:) function.

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
George
  • 25,988
  • 10
  • 79
  • 133
JonJ
  • 1,061
  • 6
  • 8
0

In my case, I changed the table height during process of build the project. At that time my device was connected with network. I deleted the derived data and it resolved the issue for me.

Raj Mohan
  • 957
  • 1
  • 6
  • 3
0

This was happening for me when, when I was modifying the predicate as per search query and also reloading the table so its sort of reentry (access) to the same class object code. Please check my stack trace, its setting the 'searchPhrase' and from DB worker again triggering the table reload which in-turn comes back to DB worker again (for Item count. after 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

Solution worked for me: Called 'reloadData' from DB worker class after few milli seconds or half a second.

infiniteLoop
  • 2,135
  • 1
  • 25
  • 29
0

I was facing the same issue with computed property in which I was using nested iterations over my data, i.e. compactMap, FlatMap and filter.

I had to change

.receive(on: RunLoop.main)

to

.receive(on: DispatchQueue.main)

in my bindings and crash resolved.

Muhammad Idris
  • 2,138
  • 1
  • 13
  • 9
0

I experienced this issue when passing an array variable by reference (&variable) to an inout function parameter to delete an object from the array. This array was also used to determine the number of rows in a table view in my controller. Removing the reference call (and repeating a small amount of code) fixed my issue.

Before, crashing:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if(section == 0) { return firstArray.count }
    return secondArray.count
}

...

private func deleteConfirmed(indexPath: IndexPath) {
    if(indexPath.section == 0) {
        deleteFromArray(&firstArray, indexPath: indexPath)
    } else {
        deleteFromArray(&secondArray, indexPath: indexPath)
    }
}

...

private func deleteFromArray(_ array: inout [ObjectType], indexPath: IndexPath) {
   // do things to array[indexPath.row]
}

After, fixed:

private func deleteConfirmed(indexPath: IndexPath) {
    if(indexPath.section == 0) {
        // do things to firstArray[indexPath.row]
    } else {
        // do the same things to secondArray[indexPath.row]
    }
}
Stonz2
  • 6,306
  • 4
  • 44
  • 64