2017-03-01 31 views
1

我經歷了很多有關CoreData的discutions和主題,但我一直都遇到同樣的問題。CoreData在串行隊列中讀取時發生崩潰

下面是上下文:我有一個應用程序必須對CoreData進行多次訪問。爲了簡化起見,我決定聲明專門用於訪問的串行線程(用於讀取的queue.sync,用於保存的queue.async)。我有一個嵌套三次的結構,並重新創建整個結構,我取subSubObject,然後子對象,最後對象

有時(如「對象」的1/5000娛樂)上擷取結果CoreData崩潰,不帶堆棧跟蹤,沒有崩潰日誌,只有 EXC_BAD_ACCESS(代碼1)

對象是不是原因,因爲所有的訪問都是在同一個線程這是一個完成的崩潰是怪異系列線程

如果有人能幫助我,我將非常感激!

下面的代碼的結構:

private let delegate:AppDelegate 
private let context:NSManagedObjectContext 
private let queue:DispatchQueue 

override init() { 
    self.delegate = (UIApplication.shared.delegate as! AppDelegate) 
    self.context = self.delegate.persistentContainer.viewContext 
    self.queue = DispatchQueue(label: "aLabel", qos: DispatchQoS.utility) 
    super.init() 
} 


(...) 

public func loadObject(withID ID: Int)->Object? { 

    var object:Object? = nil 

    self.queue.sync { 

     let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Name") 
     fetchRequest.predicate = NSPredicate(format: "id == %@", NSNumber(value: ID)) 

     do { 
      var data:[NSManagedObject] 

      // CRASH HERE ######################## 
      try data = context.fetch(fetchRequest) 
      // ################################### 

      if (data.first != nil) { 
       let subObjects:[Object] = loadSubObjects(forID: ID) 
       // Task creating "object" 
      } 
     } catch let error as NSError { 
      print("CoreData : \(error), \(error.userInfo)") 
     } 
    } 

    return object 
} 

private func loadSubObjects(forID ID: Int)->[Object] { 

    var objects:[Object] = nil 

    self.queue.sync { 

     let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Name") 
     fetchRequest.predicate = NSPredicate(format: "id == %@", NSNumber(value: ID)) 

     do { 
      var data:[NSManagedObject] 

      // OR HERE ########################### 
      try data = context.fetch(fetchRequest) 
      // ################################### 

      if (data.first != nil) { 
       let subSubObjects:[Object] = loadSubObjects(forID: ID) 
       // Task creating "objects" 
      } 
     } catch let error as NSError { 
      print("CoreData : \(error), \(error.userInfo)") 
     } 
    } 

    return objects 
} 

(etc...) 
+1

可能重複[如何在正確的隊列上初始化ManagedObjectContext?](http://stackoverflow.com/questions/41988409/how-to-init-managedobjectcontext-on-the-right-queue) – 2017-03-01 01:35:20

+0

檢查出我的在重複中回答。您不應該爲Core Data使用自定義線程。您可以將其添加到您的項目,以瞭解您將來何時違反線程:http://stackoverflow.com/questions/31391838/making-com-apple-coredata-concurrencydebug-1-work – 2017-03-01 01:35:39

回答

1

TL; DR:擺脫你的隊列,與操作隊列替換它。使用viewContext以同步方式寫入主線程上的提取。

有兩個問題。首先是managedObjectContexts不是線程安全的。您不能訪問上下文(不用於讀取或寫入),只能從設置爲使用的單個線程訪問。第二個問題是,你不應該同時多次寫入核心數據。同時寫入可能導致衝突和數據丟失。

崩潰是通過從不是主線程的線程訪問viewContext引起的。事實上,有一個隊列確保沒有別的東西在同一時間訪問核心數據並不能解決這個問題。當違反核心數據線程安全時,核心數據在任何時候都可能以失敗以任何方式。這意味着即使在正確的線程中的代碼中的某些點,它也可能很難診斷崩潰報告。

您有正確的想法,核心數據需要一個隊列才能在保存數據時正常工作,但是您的實現存在缺陷。核心數據的隊列將防止由於從不同的上下文同時將衝突的屬性寫入實體而導致的寫入衝突。使用NSPersistentContainer這是很容易設置

在覈心數據管理器創建一個NSOperationQueue

let persistentContainerQueue : OperationQueue = { 
    let queue = OperationQueue.init(); 
    queue.maxConcurrentOperationCount = 1; 
    return queue; 
}() 

,並盡一切使用此隊列的寫:

func enqueueCoreDataBlock(_ block: @escaping (NSManagedObjectContext) -> Swift.Void){ 
    persistentContainerQueue.addOperation { 
     let context = self.persistentContainer.newBackgroundContext(); 
     context.performAndWait { 
      block(context) 
      do{ 
       try context.save(); 
      } catch{ 
       //log error 
      } 
     } 
    } 
} 

對於書寫用enqueueCoreDataBlock:這將給你一個使用的上下文,並將執行隊列中的每個塊,所以你不會發生寫衝突。確保沒有managedObject離開這個塊 - 它們被附加到將在塊末尾銷燬的上下文。此外,您無法將managedObjects傳遞到此塊中 - 如果要更改viewContext對象,則必須使用objectID並在後臺上下文中獲取。爲了在viewContext上看到更改,您必須添加到核心數據設置persistentContainer.viewContext.automaticallyMergesChangesFromParent = true

對於閱讀,您應該使用主線程中的viewContext。正如你通常閱讀爲了向用戶顯示信息,你不會通過擁有不同的線程來獲得任何東西。主線程將不得不等待任何事件中的信息,因此只需在主線程上執行獲取就可以更快。千萬不要在viewContext上寫字。 viewContext不使用操作隊列,因此在其上寫入可能會產生寫入衝突。同樣,您應該將您創建的任何其他上下文(使用newBackgroundContext或使用performBackgroundTask)視爲只讀,因爲它們也會在寫入隊列之外。

起初我以爲NSPersistentContainerperformBackgroundTask有一個內部隊列,並且初始測試支持。經過更多測試後,我發現它也可能導致合併衝突。

+0

非常有用,一個大謝謝 !你應該寫一篇教程,因爲我發現沒有提到'viewContext'和'persistantContainer'之間的區別。我聽說過'線程安全'問題,但現在很明顯! 現在我有幾個問題,由於更新,我標記帖子爲「回答」無論如何:) Ps:所以,更新應該在「performBackgroundTask」,對吧? (Ps2:對不起英語,法國開發者在這裏x)) – Tom

+0

所有的寫作都應該使用'performBackgroundTask'來完成 –

+0

@JonRose大提取會導致ui凍結no? –

相關問題