2016-10-10 65 views
11

我試圖使用fetchedResultsController處理我的UITable中的結果。FetchedResultsController Swift 3 API誤用:嘗試序列化非擁有協調器上的存儲訪問

它最初在程序啓動時起作用。然後,當我切換回我的表格的庫存選項卡時(對於viewToAppear再次),這是它崩潰時。

我得到一個運行時崩潰錯誤,在我的viewWillAppear()方法的窗口中有表。

特別是它在這行let characters = name!.characters.map { String($0) }上的Inventory + CoredataProperties.swift文件上崩潰,但我懷疑這個錯誤是在別的地方,因爲這個工作原來是這樣的,所以爲什麼不現在在第二次重新加載?

這是功能。

override func viewWillAppear(_ animated: Bool) { 
     print("view appearing") 
     //When the view appears its important that the table is updated. 

     //Trigger Event on SearchBar in case returning from BarCode Scanner 
//  self.searchBar:SearchBar textDidChange:recentlySearchedWord; 
     //searchBar.performSelector(":textDidChange") 

     //Perform another fetch again to get correct data~ 
     do { 
      //fetchedResultsController. //this will force setter code to run again. 
      print("attempting fetch again, reset to use lazy init") 
      fetchedResultsController = setFetchedResultsController() //sets it again so its correct. 

      try fetchedResultsController.performFetch() 
     } catch { 
      print("An error occurred") 
     } 


     inventoryTable.reloadData()//this is important to update correctly for changes that might have been made 
    } 

錯誤發生在嘗試fetchedResultsController.performFetch()聲明。在發生實際崩潰之前,我收到了很多錯誤,說「API錯誤:嘗試序列化非擁有協調器上的存儲訪問(PSC = 0x170265300,存儲PSC = 0x0)。我一直在重構我的代碼以工作與新雨燕3個標準,我有一種感覺,我做錯了什麼事或者也可以是與如何獲取的成果控制工程變更。

任何幫助表示讚賞,可能是什麼原因?

如果您認爲我錯過了一個你需要查看的文件,只是讓我知道,我將它添加到下面的相關源代碼。

可能的相關的源碼BELOW:

InventoryController.swift(整個文件)

import UIKit 
import CoreData 
import Foundation 

class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate { 
    @available(iOS 2.0, *) 

    //Create fetchedResultsController to handle Inventory Core Data Operations 
    lazy var fetchedResultsController: NSFetchedResultsController<Inventory> = { 
     return self.setFetchedResultsController() 
    }() 

    //Reference to search text for filtering 
    var m_searchText = "" 

    func setFetchedResultsController() -> NSFetchedResultsController<Inventory>{ 

     let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 

     let inventoryFetchRequest : NSFetchRequest<Inventory> = Inventory.fetchRequest() 

     var primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)//by default assume name. 

     print("primarySortDescriptor...") 

     if(g_appSettings[0].indextype=="numberfront"){ 
      primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true) 
     }else if(g_appSettings[0].indextype=="numberback"){ 
      primarySortDescriptor = NSSortDescriptor(key: "barcodeReverse", ascending: true) 
     }else if(g_appSettings[0].indextype=="numberfourth"){ 
      primarySortDescriptor = NSSortDescriptor(key: "barcodeFourth", ascending: true, selector: #selector(NSString.localizedCompare(_:))) 
     } 

     print("set primarySortDescriptor") 

     //let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true) 

     inventoryFetchRequest.sortDescriptors = [primarySortDescriptor] 

     print("set sort descriptors to fetch request") 

     var storefilter : Store? = nil 
     var predicate: NSPredicate 

     //Store should never be set to nil, the first store should always be selected by default. For fringe cases just in case ... support added so doesn't break 
     if(g_appSettings[0].selectedStore != nil){ 

      storefilter = g_appSettings[0].selectedStore 
      predicate = NSPredicate(format: "store = %@", storefilter!) //default predicate assuming store is selected 

      //However if search text is present then modify predicate 
      if(m_searchText != ""){ 
       predicate = NSPredicate(format: "store = %@ AND name contains[cd] %@ OR store = %@ AND barcode contains[cd] %@", storefilter!,m_searchText,storefilter!,m_searchText) 

      } 
      //This will ensure correct data relating to store is showing (and if any filters present, them as well) 

      inventoryFetchRequest.predicate = predicate 
     }else{ 
      if(m_searchText != ""){ 
       predicate = NSPredicate(format: "name contains[cd] %@ OR barcode contains[cd] %@",m_searchText,m_searchText) 
       inventoryFetchRequest.predicate = predicate 
       //This will ensure correct data relating to store is showing 
      } 
     } 

     //default assume letter section 
     var frc = NSFetchedResultsController(
      fetchRequest: inventoryFetchRequest, 
      managedObjectContext: moc, 
      sectionNameKeyPath: "lettersection", 
      cacheName: nil) 

     if(g_appSettings[0].indextype=="numberfront"){ 
      frc = NSFetchedResultsController(
       fetchRequest: inventoryFetchRequest, 
       managedObjectContext: moc, 
       sectionNameKeyPath: "numbersection", 
       cacheName: nil) 
     }else if(g_appSettings[0].indextype=="numberback"){ 
      frc = NSFetchedResultsController(
       fetchRequest: inventoryFetchRequest, 
       managedObjectContext: moc, 
       sectionNameKeyPath: "numberendsection", 
       cacheName: nil) 
     }else if(g_appSettings[0].indextype=="numberfourth"){ 
      frc = NSFetchedResultsController(
       fetchRequest: inventoryFetchRequest, 
       managedObjectContext: moc, 
       sectionNameKeyPath: "numberfourthsection", 
       cacheName: nil) 
     } 


     print("set the frc") 

     frc.delegate = self 

     return frc 
    } 

    @IBOutlet weak var searchBar: UISearchBar! 
    @IBOutlet weak var inventoryTable: UITableView! 

    // Start DEMO Related Code 
    var numberIndex = ["0","1","2","3","4","5","6","7","8","9"] 
    var letterIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] 

    var previousNumber = -1 //used so we show A,A, B,B, C,C etc for proper testing of sections 

    func createInventoryDummyData(number: Int) -> Inventory{ 
     let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 

     let tempInventory = NSEntityDescription.insertNewObject(forEntityName: "Inventory", into: moc) as! Inventory 
     if(number-1 == previousNumber){ 
      tempInventory.name = "\(letterIndex[number-2])-Test Item # \(number)" 
      previousNumber = -1//reset it again 
     }else{ 
      tempInventory.name = "\(letterIndex[number-1])-Test Item # \(number)" 
      previousNumber = number //set previous letter accordingly 
     } 
     tempInventory.barcode = "00000\(number+1)00\(number)" 

     //special exception to demo barcode reader 
     if(number==5){ 
      tempInventory.barcode = "0051111407592" 
     } 

     if(number==6){ 
      tempInventory.barcode = "0036000291452" 
     } 

     tempInventory.barcodeReverse = String(tempInventory.barcode!.characters.reversed()) 

     //Convert barcode into array of characters and take note if its size for indexing 
     let bcArraySize = Int(tempInventory.barcode!.characters.count) - 1//for correct indexing 
     var bcArray = tempInventory.barcode!.characters.map { String($0) } 

     print(bcArray) 
     print(bcArraySize) 

     //Take the digits from the 4th one at a time and convert to strings concatenating as you go. 
     let fourth = "\(bcArray[bcArraySize-3])"+"\(bcArray[bcArraySize-2])"+"\(bcArray[bcArraySize-1])"+"\(bcArray[bcArraySize])" 

     print(fourth) 

     //Finally convert that into a number again and set to barcodeFourth 
     tempInventory.barcodeFourth = fourth 

     print(tempInventory.barcodeFourth!) 

     //tempInventory.barcodeFourth = 
     //print(tempInventory.barcodeReverse) 


     tempInventory.currentCount = 0 
     tempInventory.id = number as NSNumber? 
     tempInventory.imageLargePath = "http://distribution.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png" 
     tempInventory.imageSmallPath = "http://distribution.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png" 
     tempInventory.addCount = 0 
     tempInventory.negativeCount = 0 
     tempInventory.newCount = 0 
     tempInventory.store_id = 1 //belongs to same store for now 

     //Select a random store to belong to 0 through 2 since array starts at 0 
     let lo = 0; 
     let hi = 2; 
     let aRandomInt = Int.random(range:lo...hi) 
     tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created. 

     return tempInventory 
    } 

    func createStoreDummyData(number:Int) -> Store{ 
     let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 

     let tempStore = NSEntityDescription.insertNewObject(forEntityName: "Store", into: moc) as! Store 

     tempStore.address = "100\(number) lane, Miami, FL" 
     tempStore.email = "store\(number)@centraltire.com" 
     tempStore.id = number as NSNumber? 
     tempStore.lat = 1.00000007 
     tempStore.lng = 1.00000008 
     tempStore.name = "Store #\(number)" 
     tempStore.phone = "123000000\(number)" 

     return tempStore 
    } 

    // End DEMO Related Code 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 

     print("InventoryController -> ViewDidLoad -> ... starting inits") 

//  // Do any additional setup after loading the view, typically from a nib. 
//  print("InventoryController -> ViewDidLoad -> ... starting inits") 
// 
     //First check to see if we have entities already. There MUST be entities, even if its DEMO data. 
     let inventoryFetchRequest = NSFetchRequest<Inventory>(entityName: "Inventory") 
     //let storeFetchRequest = NSFetchRequest(entityName: "Store") 

     do { 
      let inventoryRecords = try moc.fetch(inventoryFetchRequest) 
      //Maybe sort descriptor here? But how to organize into sectioned array? 

      if(inventoryRecords.count<=0){ 

       g_demoMode = true 
       print("No entities found for inventory. Demo mode = True. Creating default entities & store...") 

       //Reset the Stores 
       g_storeList = [Store]() 

       var store : Store //define variable as Store type 

       for index in 1...3 { 
        store = createStoreDummyData(number: index) 
        g_storeList.append(store) 
       } 

       //save changes for inventory we added 
       do { 
        try moc.save() 
        print("saved to entity") 
       }catch{ 
        fatalError("Failure to save context: \(error)") 
       } 

       var entity : Inventory //define variable as Inventory type 

       for index in 1...52 { 
        let indexFloat = Float(index/2)+1 
        let realIndex = Int(round(indexFloat)) 
        entity = createInventoryDummyData(number: realIndex) 
        g_inventoryItems.append(entity) 
       } 

       //Save the changes 
       (UIApplication.shared.delegate as! AppDelegate).saveContext() 

       print("finished creating entities") 
      } 

     }catch{ 
      fatalError("bad things happened \(error)") 
     } 




//  //perform fetch we need to do. 
//  do { 
//   try fetchedResultsController.performFetch() 
//  } catch { 
//   print("An error occurred") 
//  } 

     print("InventoryController -> viewDidload -> ... finished inits!") 
    } 

    override func viewWillAppear(_ animated: Bool) { 
     print("view appearing") 
     //When the view appears its important that the table is updated. 

     //Trigger Event on SearchBar in case returning from BarCode Scanner 
//  self.searchBar:SearchBar textDidChange:recentlySearchedWord; 
     //searchBar.performSelector(":textDidChange") 

     //Perform another fetch again to get correct data~ 
     do { 
      //fetchedResultsController. //this will force setter code to run again. 
      print("attempting fetch again, reset to use lazy init") 
      fetchedResultsController = setFetchedResultsController() //sets it again so its correct. 

      try fetchedResultsController.performFetch() 
     } catch { 
      print("An error occurred") 
     } 


     inventoryTable.reloadData()//this is important to update correctly for changes that might have been made 
    } 

    // MARK: - Navigation 

    // In a storyboard-based application, you will often want to do a little preparation before navigation 
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 
     // Get the new view controller using segue.destinationViewController. 
     // Pass the selected object to the new view controller. 
     print("inventoryItemControllerPrepareForSegueCalled") 

     if segue.identifier == "inventoryInfoSegue" { 
      let vc = segue.destination as! InventoryItemController 
      vc.hidesBottomBarWhenPushed = true //hide the tab bar. This prevents crashing error from being on this page then syncing & returning. 
      if let cell = sender as? InventoryTableViewCell{ 
       vc.inventoryItem = cell.inventoryItem //sets the inventory item accordingly, passing its reference along. 
      }else{ 
       print("sender was something else") 
      } 
     } 

    } 

// func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { 
//  //This scrolls to correct section based on title of what was pressed. 
//  return letterIndex.indexOf(title)! 
// } 

    func sectionIndexTitles(for tableView: UITableView) -> [String]? { 
     //This is smart and takes the first letter of known sections to create the Index Titles 
     return self.fetchedResultsController.sectionIndexTitles 
    } 

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 

     if let sections = fetchedResultsController.sections { 
      let currentSection = sections[section] 
      return currentSection.numberOfObjects 
     } 

     return 0 
    } 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     let cell = tableView.dequeueReusableCell(withIdentifier: "InventoryTableCell", for: indexPath as IndexPath) as! InventoryTableViewCell 

     print("IndexPath=") 
     print(indexPath) 

     let inventory : Inventory = fetchedResultsController.object(at: indexPath as IndexPath) 
     cell.inventoryItem = inventory 

     cell.drawCell() //uses passed inventoryItem to draw it's self accordingly. 

     return cell 
    } 


    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 

     if let sections = fetchedResultsController.sections { 
      let currentSection = sections[section] 
      return currentSection.name 
     } 

     return nil 
    } 

    func numberOfSections(in tableView: UITableView) -> Int { 
     if let sections = fetchedResultsController.sections { 
      return sections.count 
     } 

     return 0 
    } 

    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { 
     //dispatch_async(dispatch_get_main_queue()) { 
     //[unowned self] in 
     print("didSelectRowAtIndexPath")//does not recognize first time pressed item for some reason? 
     let selectedCell = self.tableView(tableView, cellForRowAt: indexPath) as? InventoryTableViewCell 

     self.performSegue(withIdentifier: "inventoryInfoSegue", sender: selectedCell) 
     //} 
    } 


    @IBAction func BarcodeScanBarItemAction(sender: UIBarButtonItem) { 
     print("test of baritem") 
    } 
    @IBAction func SetStoreBarItemAction(sender: UIBarButtonItem) { 
     print("change store interface") 
    } 

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { 
     self.barcodeTextDidChange(searchText: searchText) 
    } 

    func barcodeTextDidChange(searchText: String){ 
     print("text is changing") 
     //Code to change NSFetchRequest Here~ & Reload Table 

     m_searchText = searchText //sets the local variable to this class so the setFetchedResultsController() will update accordingly 

     //Perform another fetch again to get correct data~ 
     do { 
      //fetchedResultsController. //this will force setter code to run again. 
      print("attempting fetch again, reset to use lazy init") 
      fetchedResultsController = setFetchedResultsController() //sets it again so its correct. 
      try fetchedResultsController.performFetch() 
     } catch { 
      print("An error occurred") 
     } 

     inventoryTable.reloadData()//refreshes the data~ 

    } 

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { 
     print("ended by cancel") 
     searchBar.text = "" 

     m_searchText = "" //set the search text accordingly back to nothing. 

     //Perform another fetch again to get correct data~ 
     do { 
      //fetchedResultsController. //this will force setter code to run again. 
      print("attempting fetch again, reset to use lazy init") 
      fetchedResultsController = setFetchedResultsController() //sets it again so its correct. 

      try fetchedResultsController.performFetch() 
     } catch { 
      print("An error occurred") 
     } 

     inventoryTable.reloadData()//refreshes the data~ 

     searchBar.resignFirstResponder() 
    } 

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { 
     print("ended by search") 
     searchBar.resignFirstResponder() 
    } 

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { 
     print("ended by end editing") 
     searchBar.resignFirstResponder() 
    } 

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { 
     print("DidBeginEditing") 
     //searchBar.keyboardType = UIKeyboardType.NamePhonePad 
    } 


    @IBAction func unwindBackToInventory(segue: UIStoryboardSegue) { 
     print("unwind attempt") 

     let barcode = (segue.source as? ScannerViewController)?.barcode 
     searchBar.text = barcode! 

     barcodeTextDidChange(searchText: searchBar.text!)//force it to re-run function manually. 

     print("barcode="+barcode!) 

     inventoryTable.reloadData()//reload the data to be safe. 

    } 

} 

//Extention to INT to create random number in range. 
extension Int 
{ 
    static func random(range: ClosedRange<Int>) -> Int 
    { 
     var offset = 0 

     if range.lowerBound < 0 // allow negative ranges 
     { 
      offset = abs(range.lowerBound) 
     } 

     let mini = UInt32(range.lowerBound + offset) 
     let maxi = UInt32(range.upperBound + offset) 

     return Int(mini + arc4random_uniform(maxi - mini)) - offset 
    } 
} 

globals.swift

import Foundation 
import CoreData 

//Array of Inventory & Store Core Data Managed Objects 
var g_inventoryItems = [Inventory]() 
var g_storeList = [Store]() 
var g_appSettings = [AppSettings]() 
var g_demoMode = false 

庫存+ CoreDataProperties.swift

import Foundation 
import CoreData 

extension Inventory { 

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Inventory> { 
     return NSFetchRequest<Inventory>(entityName: "Inventory"); 
    } 

    @NSManaged var addCount: NSNumber? 
    @NSManaged var barcode: String? 
    @NSManaged var barcodeReverse: String? 
    @NSManaged var barcodeFourth: String? 
    @NSManaged var currentCount: NSNumber? 
    @NSManaged var id: NSNumber? 
    @NSManaged var imageLargePath: String? 
    @NSManaged var imageSmallPath: String? 
    @NSManaged var name: String? 
    @NSManaged var negativeCount: NSNumber? 
    @NSManaged var newCount: NSNumber? 
    @NSManaged var store_id: NSNumber? 
    @NSManaged var store: Store? 

    //This is used for A,B,C ordering... 
    var lettersection: String { 
     let characters = name!.characters.map { String($0) } 
     return (characters.first?.uppercased())! 
    } 

    //This is used for 1,2,3 ordering... (using front of barcode and using barcodeReverse) 
    var numbersection: String { 
     let characters = barcode!.characters.map { String($0) } 
     return (characters.first?.uppercased())! 
    } 

    //This is used for 000000ordering...(uses back number of barcode) 
    var numberendsection: String { 
     let characters = barcodeReverse!.characters.map { String($0) } 
     return (characters.first?.uppercased())! 
    } 

    //This is used for 0000000 ->ordering...(uses back 4th number of barcode) 
    var numberfourthsection: String { 
     let characters = barcodeFourth!.characters.map { String($0) } 
     //print("characters") 
     //print(characters) 
     return (characters.first?.uppercased())! 
    } 

} 

Inventory.Swift

import Foundation 
import CoreData 


class Inventory: NSManagedObject { 

// Insert code here to add functionality to your managed object subclass 

} 

的屏幕截圖錯誤

enter image description here

enter image description here

+0

任何人有任何想法或事情,我可以嘗試? –

+0

最近更改NSFetchedResultsController引用庫存,但仍然是相同的問題。例如,NSFetchedResultsController

+0

在這裏發現了一些東西,我將嘗試(https://forums.developer.apple.com/thread/60503)。它指的是在嘗試獲取之前保存上下文。在這一點上,我願意嘗試任何事情。 –

回答

4

我已經審覈了您張貼在這裏的所有意見和內容。 您尚未在此共享一個文件,但問題在於您在上下文中創建了無效的託管對象。

然後,無論何時在InventoryViewController中調用viewWillAppear()函數,它都會保存上下文。

最後,它將空記錄同步到數據庫中。 在解析這些無效對象時,它試圖解析nil值,因此崩潰。

請不要爲您定義爲屬性的託管對象設置默認值。 我希望這會澄清你的問題。

+0

真棒是的,解決了這個問題。在表格繪製單元格中,它一次又一次地將其創建到上下文中,以創建重複部分。我所要做的就是在沒有上下文的情況下創建一個正常的可選引用。 –

+0

我很高興它正確地解決了你的問題:) – softninja

1

如果這有助於任何獲得「API誤用:嘗試序列化非擁有協調器上的存儲訪問」錯誤的人 - 我得到的錯誤是因爲我訪問了一個沒有被銷燬的單例中的對象,在重置NSPersistentStore和NSManagedObjectContext之後仍然使用舊的NSManagedObjectContext。

1

我遇到了類似的問題,我轉移到ios10中引入的新CoreData api。 這使用NSPersistentContainer類來創建堆棧並創建關聯的上下文。 這消除了手動調用保存或命令創建提取結果控制器的需要。

好的博客文章閱讀:https://useyourloaf.com/blog/easier-core-data-setup-with-persistent-containers/

我的設置是一個如下

創建存儲NSPersistentContainer

let persistentContainer = NSPersistentContainer(name: "ModelFileName"); 

配置設置

let url = NSPersistentContainer.defaultDirectoryURL() 
let path = url.appendingPathComponent(persistentContainer.name); 
description.shouldAddStoreAsynchronously = true; //write to disk should happen on background thread 
self.persistentContainer.persistentStoreDescriptions = [description]; 

裝載存儲

persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in 
     if let error = error { 
       fatalError("Unresolved error \(error), \(error.localizedDescription)") 
     } 

    //configure context for main view to automatically merge changes 
    persistentContainer.viewContext.automaticallyMergesChangesFromParent = true; 
}); 
在視圖控制器

您可以通過調用

persistentContainer.viewContext 

,如果你需要更改訪問視圖上下文你可以調用

persistentContainer.performBackgroundTask({ (context) in ... }); 

,或者你可以得到一個背景情況下

let context = persistentContainer.newBackgroundContext() 
context.perform({ ... })