FetchedResultsController Swift 3 API Misuse: попытка сериализовать доступ к хранилищу для не-владеющего координатора
Я пытаюсь использовать fetchedResultsController для обработки результатов в моем UITable.
Он работает первоначально, когда программа запускается. Затем, когда я переключаюсь обратно на вкладку инвентаря, где находится моя таблица (для viewToAppear снова), это происходит, когда он сбой.
Я получаю ошибку сбоя во время выполнения в моем viewWillAppear() методе окна, в котором есть таблица.
В частности, он сбой в файле Inventory + CoredataProperties.swift в этой строке let characters = name!.characters.map { String($0) }
, но я подозреваю, что ошибка находится где-то еще, поскольку это работает изначально, так почему бы не сейчас во второй перезагрузке?
Вот функция.
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
}
Ошибка возникает в операторе try fetchedResultsController.performFetch(). Я получаю много ошибок до фактического сбоя, говоря "Неправильное использование API: попытка сериализовать доступ к хранилищу для не-владеющего координатора (PSC = 0x170265300, хранить PSC = 0x0). рефакторинг моего кода для работы с новыми быстрыми 3 стандартами. У меня такое чувство, что я сделал что-то неправильно или, возможно, что-то изменилось с тем, как работает обработчик полученных результатов.
Любая помощь приветствуется в отношении того, что может быть причиной?
Если вы считаете, что мне не хватает файла, который вам нужно увидеть, просто дайте мне знать, и я добавлю его в соответствующий исходный код ниже.
ВОЗМОЖНЫЙ СООТВЕТСТВУЮЩИЙ КОД ИСТОЧНИКА НИЖЕ:
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 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 0000000123 ordering...(uses back number of barcode)
var numberendsection: String {
let characters = barcodeReverse!.characters.map { String($0) }
return (characters.first?.uppercased())!
}
//This is used for 0000000 -> 0123 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
}
Скриншоты ошибок
![введите описание изображения здесь]()
![введите описание изображения здесь]()
Ответы
Ответ 1
Я рассмотрел все ваши комментарии и содержание, размещенные здесь.
Вы не поделились одним файлом здесь, но проблема в том, что вы создаете недопустимые управляемые объекты в контексте.
И тогда всякий раз, когда вы вызываете функцию viewWillAppear() в InventoryViewController, она сохраняет контекст.
Наконец, он синхронизировал пустые записи в вашей базе данных.
Во время разбора этих недопустимых объектов он попытался разобрать значение nil, поэтому он разбился.
Никогда не устанавливайте значение по умолчанию для управляемых объектов, которые вы определяете как свойства.
Надеюсь, это прояснит вашу проблему.
Ответ 2
В случае, если это поможет кому-то еще, кто получает ошибку "Неправильное обращение с API: попытка сериализации доступа к хранилищу на ошибках без владельца" - я получал сообщение об ошибке, потому что я обратился к объекту в одиночном тоне, который не был уничтожен и все еще был используя старый NSManagedObjectContext после я reset NSPersistentStore и NSManagedObjectContext.
Ответ 3
У меня возникла аналогичная проблема, и я перешел на новую версию CoreData api, представленную в ios10.
Это использует класс 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({ ... })