web-dev-qa-db-fra.com

Mauvaise utilisation de l'API Swift 3 de FetchedResultsController: tentative de sérialisation de l'accès au magasin sur un coordinateur non propriétaire

Je tente d'utiliser un fetchedResultsController pour gérer les résultats dans mon UITable.

Cela fonctionne initialement lorsque le programme démarre. Ensuite, lorsque je reviens à l'onglet d'inventaire où se trouve ma table (pour la vue à afficher à nouveau), c'est à ce moment-là qu'il se bloque.

Je reçois une erreur d'erreur d'exécution avec ma méthode viewWillAppear () de la fenêtre contenant la table.

En particulier, il se bloque sur le fichier Inventory + CoredataProperties.Swift sur cette ligne let characters = name!.characters.map { String($0) }, mais je soupçonne que l'erreur est ailleurs car cela fonctionne initialement, alors pourquoi ne pas maintenant sur le 2e rechargement?

Voici la fonction.

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
    }

L'erreur se produit dans l'instruction try fetchedResultsController.performFetch (). Je reçois beaucoup d'erreurs avant que le crash réel se produise, indiquant "API Misuse: tentative de sérialisation de l'accès au magasin sur un coordinateur non propriétaire (PSC = 0x170265300, magasin PSC = 0x0). pour travailler avec les nouvelles normes Swift 3, j'ai le sentiment d'avoir fait quelque chose de mal ou peut-être que quelque chose a changé avec le fonctionnement du contrôleur de résultats récupérés.

Toute aide est appréciée quant à ce qui pourrait être la cause?

Si vous pensez qu'il me manque un fichier que vous devez voir, faites le moi savoir et je l'ajouterai au code source correspondant ci-dessous.

CODE SOURCE PERTINENT POSSIBLE CI-DESSOUS:

InventoryController.Swift (fichier entier)

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

Inventory + 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

}

Captures d'écran des erreurs

 enter image description here

 enter image description here

13
Joseph Astrahan

J'ai examiné tous vos commentaires et contenus publiés ici . Vous n'avez partagé aucun fichier ici, mais le problème est en train de se produire, vous créez des objets gérés non valides dans le contexte.

Et ensuite, chaque fois que vous appelez la fonction viewWillAppear () dans InventoryViewController, le contexte est enregistré.

Enfin, il a synchronisé des enregistrements vides dans votre base de données . Lors de l'analyse de ces objets non valides, il a essayé d'analyser la valeur nil.

Veuillez ne jamais définir la valeur par défaut pour les objets gérés que vous définissez comme propriétés ..__ J'espère que cela clarifiera votre problème.

4
softninja

Je rencontrais un problème similaire et je suis passé à la nouvelle API CoreData introduite dans ios10 . Elle utilise la classe NSPersistentContainer pour créer la pile et créer les contextes associés ..__ du contrôleur de résultats de récupération.

Bon article de blog à lire: https://useyourloaf.com/blog/easier-core-data-setup-with-persistent-containers/

Ma configuration est la suivante

créer un magasin NSPersistentContainer

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

configurer les paramètres

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];

charger le magasin

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;
});

dans le contrôleur de vue, vous pouvez accéder au contexte de la vue en appelant

persistentContainer.viewContext

si vous devez apporter des modifications, vous pouvez appeler

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

ou vous pouvez obtenir un contexte de fond

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

Dans le cas où cela aiderait toute autre personne ayant l'erreur "Utilisation abusive de l'API: tentative de sérialisation de l'accès au magasin sur un coordinateur non propriétaire", j'ai eu l'erreur parce que j'ai accédé à un objet dans un singleton qui n'avait pas été détruit et qui utilisait toujours l'ancien. NSManagedObjectContext après la réinitialisation de NSPersistentStore et NSManagedObjectContext.

1
SAHM