kasperonline
kasperonline

Reputation: 21

Core Data NSInvalidArgument Exception

I am following this tutorial https://medium.com/@jamesrochabrun/parsing-json-response-and-save-it-in-coredata-step-by-step-fb58fc6ce16f

But before even reaching the stage of fetching , I ran the app as the tutorial suggests to get the filePath where data is stored

My app crashes and I get the error

'NSInvalidArgumentException', reason: '+entityForName: nil is not a

legal NSManagedObjectContext parameter searching for entity name 
'NewsObject''

I looked around and found this iOS: Swift: Core Data: Error: +entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name

and Core Data Reading Data

They both suggest making sure manageObjectContext is not nil. However I don't how to implement that/ make sure it's not nil

I am a complete beginner in CoreData and just started with that tutorial

Here's my Code


 private func createNewsEntityFrom(dictionary: [String: Any]) -> NSManagedObject? {
        let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
        if let newsEntity = NSEntityDescription.insertNewObject(forEntityName: "NewsObject", into: context) as? NewsObject {
            newsEntity.newsAuthor = dictionary["author"] as? String ?? "default"
            newsEntity.newsTitle = dictionary["title"] as? String ?? "default"
            let images = dictionary["image"] as? [String: AnyObject]
            newsEntity.newsImageURL = images?["link"] as? String ?? "default"
            return newsEntity
        }
        return nil
    }

    private func saveInCoreDataWith(array: [[String: Any]]) {
        for dict in array {
            _ = self.createNewsEntityFrom(dictionary: dict)
        }
        do {
            try CoreDataStack.sharedInstance.persistentContainer.viewContext.save()
        } catch let error {
            print(error)
        }
    }
  let url = "someURL"
            Alamofire.request(url, method: .get , headers: headers).responseJSON { response in
                switch response.result {
                case .success:
                    let json = response.result.value as! [String:Any]
                    let data = json["data"] as! [[String : Any]]
                    self.saveInCoreDataWith(array: data)
                    self.nextToken = json["nextPageToken"] as? String ?? "empty"
                    print("Token = "+self.nextToken!)
                    for dic in data{
                        self.news.append(News(dictionary: dic))
                        print(self.news.count)

                    }
                    DispatchQueue.main.async {
                        loadingIndicator.stopAnimating()
                        self.tableView.reloadData()
                    }


                case .failure: break
                }

My CoreDataStack class



import UIKit
import Foundation
import CoreData

class CoreDataStack: NSObject {

    static let sharedInstance = CoreDataStack()
    private override init() {}

    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
         */
        let container = NSPersistentContainer(name: "TapIn")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

}

extension CoreDataStack {

    func applicationDocumentsDirectory() {
        // The directory the application uses to store the Core Data store file. This code uses a directory named "yo.BlogReaderApp" in the application's documents directory.
        if let url = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).last {
            print(url.absoluteString)
        }
    }
}

EDIT : Here's the struct News I am using to hold the data received and perform all sorts of functionality on them

//Model to hold our news
   struct News {
    var image : String
    var title : String
    var publisherIcon : String
    var publisher : String
    var author : String
    var time : Int
    var id : String
    var bookmarked : Bool
    var liked : Bool

    init(dictionary : [String:Any])
    {
        let image = dictionary["image"] as? [String:Any]
        self.image = image!["link"] as! String
        self.title = dictionary["title"] as? String ?? ""
        self.publisherIcon = dictionary["shortenedLogo"] as? String ?? ""
        self.publisher =  dictionary["publisher"] as? String ?? ""
        self.author = dictionary["author"] as? String ?? ""
        self.time = dictionary["timeToRead"] as? Int ?? 0
        self.id = dictionary["_id"] as? String ?? ""
        self.bookmarked = dictionary["bookmarked"] as? Bool ?? false
        self.liked = dictionary["liked"] as? Bool ?? false
    }

}

And in the main VC var news = [News]()

Upvotes: 0

Views: 358

Answers (1)

vadian
vadian

Reputation: 285039

Add this lazy instantiated property in the singleton to get the non-optional context

lazy var managedObjectContext : NSManagedObjectContext = {
    return self.persistentContainer.viewContext
}()

And use the modern API to insert an object

 private func createNewsEntityFrom(dictionary: [String: Any]) -> NewsObject {
    let context = CoreDataStack.sharedInstance.managedObjectContext
    let newsEntity = NewsObject(context: context)
    newsEntity.newsAuthor = dictionary["author"] as? String ?? "default"
    newsEntity.newsTitle = dictionary["title"] as? String ?? "default"
    let images = dictionary["image"] as? [String: Any]
    newsEntity.newsImageURL = images?["link"] as? String ?? "default"
    return newsEntity
 }

And according to the Naming Guidelines it's highly recommended to name entity and attributes less redundant for example

 private func createNews(from dictionary: [String: Any]) -> News {
    let context = CoreDataStack.sharedInstance.managedObjectContext
    let news = News(context: context)
    news.author = dictionary["author"] as? String ?? "default"
    news.title = dictionary["title"] as? String ?? "default"
    let images = dictionary["image"] as? [String: Any]
    news.imageURL = images?["link"] as? String ?? "default"
    return news
 }

By the way the applicationDocumentsDirectory function is wrong. It must be

func applicationDocumentsDirectory() -> URL {
    // The directory the application uses to store the Core Data store file. This code uses a directory named "yo.BlogReaderApp" in the application's documents directory.
    return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
}

Edit:

To return the inserted array you have to change saveInCoreDataWith to

private func saveInCoreDataWith(array: [[String: Any]]) -> [NewsObject] {
    var newsArray = [NewsObject]()
    for dict in array {
        newsArray.append(self.createNewsEntityFrom(dictionary: dict))
    }
    do {
        try CoreDataStack.sharedInstance.managedObjectContext.save()
        return newsArray
    } catch let error {
        print(error)
    }
    return []
}

and in the Alamofire closure replace

self.saveInCoreDataWith(array: data)
self.nextToken = json["nextPageToken"] as? String ?? "empty"
print("Token = "+self.nextToken!)
for dic in data{
   self.news.append(News(dictionary: dic))
   print(self.news.count)                   
}

with

self.news = self.saveInCoreDataWith(array: data)
self.nextToken = json["nextPageToken"] as? String ?? "empty"
print("Token = "+self.nextToken!)
print(self.news.count)

Upvotes: 1

Related Questions