Reputation: 21
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
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
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