web-dev-qa-db-ja.com

0x1c0a7f0f8への同時アクセスですが、変更にはXcode 9ベータ4での排他的アクセスエラーが必要です

私のプロジェクトはObjective-CとSwiftコードの両方を使用します。ユーザーがログインすると、ユーザー設定のために一連のAPIを呼び出します。API操作をスケジュールするDataCoordinator.Swiftクラスがあり、 UserDetailViewController.mクラスからこの呼び出しを行って、ユーザー設定を読み込みます。これを使用して、コードをSwift 4にXcode 9ベータ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ベータ3で修正されたようだ。どんな助けも大歓迎です

22
Sonu P

この「バグ」はSwift 4「機能」、具体的には「メモリへの排他的アクセス」と呼ばれるものだと思います。

このWWDCビデオをご覧ください。 50分のマークの周りで、長い髪のスピーカーがそれを説明します。

https://developer.Apple.com/videos/play/wwdc2017/402/?time=2

無視したい場合は、スキーム設定でスレッドサニタイザーをオフにしてみてください。ただし、デバッガーは微妙なスレッドの問題について通知しようとしているので、おそらく読み取り中にアレイに何かを書き込む理由があるかどうかを調べるのに時間をかける方が良いでしょう。

21
Mark Bridges

ターゲットのビルド設定の下。選択する No Enforcement ために Exclusive Access to Memory from Swift Compiler - Code Generation 

13
geek1706

Swift 4で、KVO設定に.initialオプションを使用する場合のみ]

ObserveValueメソッドでコンテキストをチェックする場合は、コンテキスト変数staticを作成するだけです。この ブログ投稿 では、このバグについて詳しく説明しています。

10
Ralf Hundewadt

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/

6
J. Doe

私の場合、Swift 4は実際、複数の場所から関数の呼び出しを開始するまで気付かなかったようなバグを発見しました。私の関数にはinoutグローバル配列が渡され、そのパラメーターとグローバル名の両方を参照していました。パラメーターのみを参照するように関数を変更すると、「同時アクセス」エラーはなくなりました。

2
KenM

@Mark Bridgesと@ geek1706による回答は良い回答ですが、この問題について2セントを追加し、一般的な例を挙げたいと思います。

上記のように、これはSwift 4 SE-176 の機能です。

もちろん、実装は同時に競合するアクセスを検出することを許可されるべきです。少なくとも一部のビルド構成では、代わりにオプトインスレッドセーフ強制メカニズムを使用したいプログラマーもいます。

排他的アクセスは、その変数にアクセスするときに、varsのすべての書き込み変更が排他的でなければならないことを強制します。マルチスレッド環境では、共有変数と1つ以上の共有変数にアクセスする複数のスレッドがそれを変更できます。

良い例のようなものはありません。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として扱われますただし、実際のclassMyClass)を挿入しました。

一般に、エスケープ変数には静的な強制ではなく動的な強制が必要です。これは、Swiftはエスケープクロージャがいつ呼び出され、変数にアクセスされるかを判断できないためです。

解決策は、Abstractionプロトコルをclassにバインドすることです。

protocol Abstraction: class
1
OhadM

私がすることは、FetchOperationclassの代わりにstructに変更することです。

0
Tai Le

私の場合、プロジェクトのビルド中にテーブルの高さを変更しました。当時、私のデバイスはネットワークに接続されていました。派生データを削除し、問題を解決しました。

0
Raj Mohan

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
_
0
JonJ