FetchedResultsController Swift 3 API滥用:尝试在非拥有协调器

时间:2016-10-10 11:42:17

标签: swift core-data swift3 nsfetchedresultscontroller ios10

我正在尝试使用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)。我一直在重构我的代码以使用新的swift 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

3 个答案:

答案 0 :(得分:4)

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

然后每当你在InventoryViewController中调用viewWillAppear()函数时,它都会保存上下文。

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

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

答案 1 :(得分:2)

我遇到了类似的问题,我转到了ios10中引入的新CoreData api。 这使用NSPersistentContainer类来创建堆栈并创建关联的上下文。 这消除了手动调用保存或命令创建获取结果控制器的需要。

好的博客文章:Roku Developer Documentation on Authentication and Linking

我的设置如下

创建商店NSPersistentContainer

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

或者您可以获得背景背景

{{1}}

答案 2 :(得分:1)

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