FetchedResultsController Swift 3 API滥用:尝试在非拥有协调器上序列化存储访问

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FetchedResultsController Swift 3 API滥用:尝试在非拥有协调器上序列化存储访问相关的知识,希望对你有一定的参考价值。

我正在尝试使用fetchedResultsController来处理我的UITable中的结果。

它最初在程序启动时起作用。然后,当我切换回我的表所在的库存选项卡(对于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
    }

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'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 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

}

错误的屏幕截图

enter image description here

enter image description here

答案

我已经审核了您在此处发布的所有评论和内容。您尚未在此处共享一个文件,但问题是您正在上下文中创建无效的托管对象。

然后,只要在InventoryViewController中调用viewWillAppear()函数,它就会保存上下文。

最后,它将空记录同步到您的数据库中。在解析那些无效对象时,它试图解析nil值,因此崩溃了。

请勿为您定义为属性的托管对象设置默认值。我希望这能澄清你的问题。

另一答案

我遇到了类似的问题,我转向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({ ... })
另一答案

如果这有助于其他任何人获得“API滥用:尝试在非拥有协调器上序列化存储访问”错误 - 我收到错误,因为我访问了一个尚未销毁的单例中的对象并仍在使用旧的重置NSPersistentStore和NSManagedObjectContext后的NSManagedObjectContext。

以上是关于FetchedResultsController Swift 3 API滥用:尝试在非拥有协调器上序列化存储访问的主要内容,如果未能解决你的问题,请参考以下文章

FetchedResultsController 中没有部分

如何将对象从 fetchedResultsController 到 Plist?

fetchedResultsController 对象的表视图部分

fetchedResultsController 和 Integer

将 fetchedResultsController 更改为 protected from private

将 fetchedResultsController 与 swift3 一起使用