私のプロジェクトは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で修正されたようだ。どんな助けも大歓迎です
この「バグ」はSwift 4「機能」、具体的には「メモリへの排他的アクセス」と呼ばれるものだと思います。
このWWDCビデオをご覧ください。 50分のマークの周りで、長い髪のスピーカーがそれを説明します。
https://developer.Apple.com/videos/play/wwdc2017/402/?time=2
無視したい場合は、スキーム設定でスレッドサニタイザーをオフにしてみてください。ただし、デバッガーは微妙なスレッドの問題について通知しようとしているので、おそらく読み取り中にアレイに何かを書き込む理由があるかどうかを調べるのに時間をかける方が良いでしょう。
ターゲットのビルド設定の下。選択する No Enforcement
ために Exclusive Access to Memory
from Swift Compiler - Code Generation
.initial
オプションを使用する場合のみ]ObserveValueメソッドでコンテキストをチェックする場合は、コンテキスト変数staticを作成するだけです。この ブログ投稿 では、このバグについて詳しく説明しています。
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ではランタイムでもクラッシュします。
私の場合、Swift 4は実際、複数の場所から関数の呼び出しを開始するまで気付かなかったようなバグを発見しました。私の関数にはinoutグローバル配列が渡され、そのパラメーターとグローバル名の両方を参照していました。パラメーターのみを参照するように関数を変更すると、「同時アクセス」エラーはなくなりました。
@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
として扱われますただし、実際のclass
(MyClass
)を挿入しました。
一般に、エスケープ変数には静的な強制ではなく動的な強制が必要です。これは、Swiftはエスケープクロージャがいつ呼び出され、変数にアクセスされるかを判断できないためです。
解決策は、Abstraction
プロトコルをclass
にバインドすることです。
protocol Abstraction: class
私がすることは、FetchOperation
をclass
の代わりにstruct
に変更することです。
私の場合、プロジェクトのビルド中にテーブルの高さを変更しました。当時、私のデバイスはネットワークに接続されていました。派生データを削除し、問題を解決しました。
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
_