2016-10-10 68 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({ ... })