How to fetch results without NSFetchedResultsControllerDelegate? - ios

I am trying to fetch results from a class that is not a NSFetchedResultsControllerDelegate is that why I am getting 0 results?
public static func getTopics() -> Array<ipTopic> {
if (topics == nil) {
var fetchedResultsController: NSFetchedResultsController<ipTopic>!
let pc = CoreDataHub.getPersistentContainer()
let blogIdeasFetchRequest = NSFetchRequest<ipTopic>(entityName: "ipTopic")
let primarySortDescriptor = NSSortDescriptor(key: "ipTopicsClip", ascending: true)
blogIdeasFetchRequest.sortDescriptors = [primarySortDescriptor]
fetchedResultsController = NSFetchedResultsController<ipTopic>(
fetchRequest: blogIdeasFetchRequest,
managedObjectContext: pc.viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
print(fetchedResultsController.fetchedObjects?.count)
for topic in fetchedResultsController.fetchedObjects! {
topics.append(topic)
}
}
return topics
}

This will creates new private queue and takes a block of code to run. This can live with your container or you can use container as argument
func read(_ block: #escaping (NSManagedObjectContext) -> Void) {
persistentContainer.performBackgroundTask {
block($0)
}
}
This is how you would use it
func fetchEvents() {
read { context in
let request: NSFetchRequest<Event> = Event.fetchRequest()
let events = try? context.fetch(request)
print(events)
}
}

Related

FetchRequest results doesn't want to convert to an Array

I simply have an extension:
extension NSManagedObject {
class func all(sortedBy: String?, ascending: Bool, predicate: NSPredicate?, context: NSManagedObjectContext) -> [NSManagedObject] {
let request = fetchRequest()
if let predicate = predicate {
request.predicate = predicate
}
if let sortedBy = sortedBy {
request.sortDescriptors = [NSSortDescriptor(key: sortedBy, ascending: ascending)]
}
do {
return try context.fetch(request) //Cannot convert return expression of type '[any NSFetchRequestResult]' to return type '[NSManagedObject]'
} catch let error as NSError {
print("❌ Core Data Fetch All Error \(error), \(error.userInfo)")
return []
}
}
}
I want to use it like this for example:
Person.all(sortedBy: nil, ascending: true, predicate: nil, context: context)
Home.all(sortedBy: "streer", ascending: true, predicate: nil, context: context)
Implementation of Fetchable protocol:
protocol Fetchable {
associatedtype FetchableType: NSManagedObject = Self
func delete()
}
extension Fetchable where Self: NSManagedObject, FetchableType == Self {
func delete() {}
}

Core data not being updated after managedContext.save but no errors thrown

I'm trying to update an already saved entry in Core data, objectContext isn't nil neither is the managedObject. The new values get set to the managedObject with said Id. No errors are thrown when calling managedContext.save() but no changes are reflected in the coreData
I have tried using setValue() no avail and I have searched far and wide on google and stack overflow for a similar problem and most of the time it says to say that the managedObject is nil.
var taskName:String!
var resultsController: NSFetchedResultsController<Tasks>!
// MARK: - Properties
// Manage objects and update tasks
//var resultsController: NSFetchedResultsController<Tasks>!
var managedContext: NSManagedObjectContext!
var tasksArray = [NSManagedObject]()
var fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
func settextfields() {
let res = resultsController.fetchedObjects!
print(res)
for r in res {
if r.name == taskName {
txt_name.text = "\(r.name ?? "Task name" )"
txt_date.text = "\(r.date ?? "Task date")"
segmentedBtn.selectedSegmentIndex = Int(r.priority)
}
}
}
func loadTable() {
let request: NSFetchRequest<Tasks> = Tasks.fetchRequest()
// Sort by date
let sortDescriptor = NSSortDescriptor(key: "date", ascending: true)
request.sortDescriptors = [sortDescriptor]
resultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: coreData.managedContext, sectionNameKeyPath: nil, cacheName: nil)
// Fetch data
do {
try resultsController.performFetch()
print("Fetch successful")
} catch {
print("Error performing fetch: \(error)")
}
}
#IBAction func saveEdit(_ sender: Any) {
guard let name = txt_name.text, !name.isEmpty else {
return
}
guard let date = txt_date.text, !date.isEmpty else {
return
}
do {
loadTable()
let res = resultsController.fetchedObjects!
for r in res {
i += 1
if r.name == taskName {
print(r)
guard let name = txt_name.text, !name.isEmpty else {
return
}
guard let date = txt_date.text, !date.isEmpty else {
return
}
do {
r.name = name
r.date = date
r.priority = Int16(segmentedBtn.selectedSegmentIndex)
}
do {
try managedContext.save()
print(managedContext, r)
dismiss(animated: true)
print("Edit Successful!")
} catch {
print("Error saving task: \(error)")
}
}
}
}
I actually rewrote the save function using predicate like the person above suggested and it worked. Then I added a viewWillLoad to the mainViewController to reload the tableView once the entry is updated
#IBAction func saveEdit(_ sender: Any) {
guard let name = txt_name.text, !name.isEmpty else {
return
}
guard let date = txt_date.text, !date.isEmpty else {
return
}
do {
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Tasks")
fetchRequest.predicate = NSPredicate(format: "name = %#", taskName)
let test = try managedContext.fetch(fetchRequest)
let obj = test[0] as! NSManagedObject
obj.setValue(name, forKey: "name")
obj.setValue(date, forKey: "date")
obj.setValue(Int16(segmentedBtn.selectedSegmentIndex), forKey: "priority")
do {
try managedContext.save()
dismiss(animated: true)
print("Edit Successful!")
} catch {
print("Error saving task: \(error)")
}
} catch {
print(error)
}
}

Core data entity manager

I want to write class for manage all entity that i have in my application.
there is my code fore managerClass for one custom entity, but I have a problem to set manageObject type in this context.
public func clearEntityContex() {
let fetchRequest: NSFetchRequest<Shop> = Shop.fetchRequest()
if let fetchResult = try? self.stack.mainQueueContext.fetch(fetchRequest){
for user : Shop in fetchResult {
self.stack.mainQueueContext.delete(user as Shop)
}
do {
try stack.mainQueueContext.save()
}
catch {
debugPrint(error.localizedDescription)
}
}
}
//MARK: _fetch Contex
public func fetchsShopEntity() -> [Shop]? {
var shops : [Shop]?
let fetchRequest: NSFetchRequest<Shop> = Shop.fetchRequest()
do {
let fetchResult =
try self.stack.mainQueueContext.fetch(fetchRequest)
if fetchResult.count > 0 {
shops = fetchResult
}
}
catch {
fatalError("Failed to fetch Account: \(error)")
}
return shops
}
//MARK: _save entity
public func saveEntityInModel(entityItem : Shop){
if let entity = NSEntityDescription.entity(forEntityName: "Shop", in: self.stack.mainQueueContext) {
if let contex = NSManagedObject(entity: entity, insertInto: self.stack.mainQueueContext) as? Shop {
contex.packID = entityItem.packID
contex.packName = entityItem.packName
contex.packImage = entityItem.packImage
contex.priceDolar = entityItem.priceDolar
contex.packDescription = entityItem.packDescription
do {
try stack.mainQueueContext.save()
}
catch {
debugPrint(error.localizedDescription)
}
}
}
}
for example i want to write method that can clear any entityContext. but i cant pass manageObject to this method.
public func clearEntityContex(entityObject: NSManagedObject) {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = entityObject.fetchRequest()
if let fetchResult = try? self.stack.mainQueueContext.fetch(fetchRequest){
for entity in fetchResult {
self.stack.mainQueueContext.delete(entity as entityObject)
}
do {
try stack.mainQueueContext.save()
}
catch {
debugPrint(error.localizedDescription)
}
}
}
how can solve pass NSManagedObject to this method?
thanks for all reply
Here's a more generic implementation that we use in our projects.
import CoreData
class ACSwiftCoreData: ACCoreDataPlugin {
let managedObjectModelName: String
let databasePath: URL
init(managedObjectModelName: String, databasePath: URL) {
self.managedObjectModelName = managedObjectModelName
self.databasePath = databasePath
}
// MARK: Managed Object Contexts
private var sharedContext: NSManagedObjectContext?
func getSharedManagedObjectContext() throws -> NSManagedObjectContext {
if let sharedContext = self.sharedContext {
return sharedContext
}
let context = try self.createManagedObjectContext()
self.sharedContext = context
return context
}
func createManagedObjectContext() throws -> NSManagedObjectContext {
let storeCoordinator = try self.getPersistentStoreCoordinator()
let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = storeCoordinator
managedObjectContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType)
return managedObjectContext
}
// MARK: Creating Entities
func createEntityInSharedContext<EntityType>(_ entityName: String) throws -> EntityType {
let context = try self.getSharedManagedObjectContext()
return try self.createEntity(entityName, context: context)
}
func createEntity<EntityType>(_ entityName: String, context: NSManagedObjectContext) throws -> EntityType {
let entity = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context)
guard let expectedEntity = entity as? EntityType else {
throw self.errorWithMessage("ACSwiftCoreData: Entity for name \(entityName) does not match class \(EntityType.self).")
}
return expectedEntity
}
// MARK: Saving Entity
func saveEntity(_ entity: NSManagedObject) throws {
guard let context = entity.managedObjectContext else {
throw errorWithMessage("ACSwiftCoreData: Cannot save Entity. ManagedObjectContext is missing.")
}
if context.hasChanges {
try context.save()
}
}
// MARK: Delete Entity
func deleteEntity(_ entity: NSManagedObject) throws {
guard let context = entity.managedObjectContext else {
throw errorWithMessage("ACSwiftCoreData: Cannot delete Entity. ManagedObjectContext is missing.")
}
context.delete(entity)
try context.save()
}
// MARK: Fetch Requests
func fetchEntitiesInSharedContext<EntityType: AnyObject>(_ entityName: String, predicate: NSPredicate?) -> [EntityType] {
guard let context = try? self.getSharedManagedObjectContext() else {
return [EntityType]()
}
return self .fetchEntities(entityName, context: context, predicate: predicate)
}
func fetchEntities<EntityType: AnyObject>(_ entityName: String, context: NSManagedObjectContext, predicate: NSPredicate?) -> [EntityType] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
fetchRequest.predicate = predicate
let results = try? context.fetch(fetchRequest)
guard let resultEntitys = results as? [EntityType] else {
return [EntityType]()
}
return resultEntitys
}
// MARK: Technical Details
private var storeCoordinator: NSPersistentStoreCoordinator?
private func getPersistentStoreCoordinator() throws -> NSPersistentStoreCoordinator {
if let storeCoordinator = self.storeCoordinator {
return storeCoordinator
}
let model = try self.getManagedObjectModel()
let storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
var options = [AnyHashable: Any]()
options[NSMigratePersistentStoresAutomaticallyOption] = true
options[NSInferMappingModelAutomaticallyOption] = true
try storeCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: self.databasePath, options: options)
self.storeCoordinator = storeCoordinator
return storeCoordinator
}
private var objectModel: NSManagedObjectModel?
private func getManagedObjectModel() throws -> NSManagedObjectModel {
if let objectModel = self.objectModel {
return objectModel
}
let momName = self.managedObjectModelName
guard let modelUrl = Bundle.main.url(forResource: momName, withExtension:"momd") else {
throw self.errorWithMessage("ACSwiftCoreData: DataModel Url could not be created.")
}
guard let objectModel = NSManagedObjectModel(contentsOf: modelUrl) else {
throw self.errorWithMessage("ACSwiftCoreData: DataModel could not be loaded.")
}
self.objectModel = objectModel
return objectModel
}
// MARK: Error handling
private func errorWithMessage(_ message: String) -> NSError {
let userInfo = [NSLocalizedDescriptionKey: message]
let error = NSError(domain: "com.appcron.accomponents", code: 0, userInfo: userInfo)
return error
}
}
In some project that I made and when I used CoreData, I usually create a Singleton with function to fetch, save and delete a CoreData object.
This is my CoreDataController:
import Foundation
import CoreData
import UIKit
final class CoreDataController {
static let sharedInstances = CoreDataController()
private var context: NSManagedObjectContext
private init(){
let application = UIApplication.shared.delegate as! AppDelegate
self.context = application.persistentContainer.viewContext
}
func loadAll() {
print("Fetch from CoreData")
let fetchRequest: NSFetchRequest<YourEntity> = YourEntity.fetchRequest()
do {
let entityArray = try self.context.fetch(fetchRequest)
guard entityArray.count > 0 else {
print("There aren't element in CoreData "); return}
} catch let error {
print("FetchRequest error")
print(" Print error: \n \(error) \n")
}
}
func save(entityToSave: String, item: String){
let entity = NSEntityDescription.entity(forEntityName: entityToSave, in: self.context)
let newItem = YourEntity(entity: entity!, insertInto: self.context)
newItem.name = item
do {
try self.context.save()
} catch let error {
print("Problem with \(newItem)")
print(" Print error: \n \(error) \n")
}
print("Element \(newItem) saved in CoreData")
}
func loadFromName(entityName:String, name: String) -> Any {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
request.returnsObjectsAsFaults = false
let predicate = NSPredicate(format: "yourEntityAttribute = %#", yourEntityAttribute)
request.predicate = predicate
let items = self.loadFromFetchRequest(request: request)
return items[0]
}
private func loadFromFetchRequest(request: NSFetchRequest<NSFetchRequestResult>) -> [Any] {
var array = [Any]()
do {
array = try self.context.fetch(request)
guard array.count > 0 else {print("There aren't element in CoreData"); return []}
for item in array {
print("Item: \(item)")
}
} catch let error {
print("FetchRequest Error")
print(" Print Error: \n \(error) \n")
}
return array
}
func delete(entityName: String, name: String) {
let item = self.loadFromName(entityName: entityName, name: name)
self.context.delete(item as! NSManagedObject)
do {
try self.context.save()
} catch let error {
print("Deleting problem")
print("Print Error: \n \(error) \n")
}
}
func loadData(entity: String) -> [YourEntity] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
fetchRequest.returnsObjectsAsFaults = false
var data = [YourEntity]()
do {
data = try self.context.fetch(fetchRequest) as! [YourEntity]
} catch let error {
print("Print Error: \n \(error) \n")
}
return data
}
}
When you have to call, just write:
CoreDataController.sharedInstances.save(entityToSave: "Profile", item: textfield.text!)
or other functions!!!
I hope this is useful for you.
For use this Class with every Entity you can write like that:
let entity = NSEntityDescription.entity(forEntityName: entityToSave, in: self.context)
let newItem: Any
switch entityToSave {
case "YourEntity":
newItem = YourEntity(entity: entity!, insertInto: self.context)
(newItem as! YourEntity).entityAttribute = firstItem
(newItem as! YourEntity).entityAttribute = secondItem
case "YourEntity2":
newItem = YourEntity2(entity: entity!, insertInto: self.context)
(newItem as! YourEntity2).entityAttribute = firstItem
(newItem as! YourEntity2).entityAttribute = secondItem
case "YourEntity3":
newItem = YourEntity3(entity: entity!, insertInto: self.context)
(newItem as! YourEntity3).entityAttribute = firstItem
(newItem as! YourEntity3).entityAttribute = secondItem
case "YourEntity4":
newItem = YourEntity4(entity: entity!, insertInto: self.context)
(newItem as! YourEntity4).entityAttribute = firstItem
(newItem as! YourEntity4).entityAttribute = secondItem
default:
fatalError("Error in entityToSave function")
}
do {
try self.context.save()
} catch let error {
print("Problem to save \(newItem)")
print("Print Error: \n \(error) \n")
}
print("Element \(newItem) saved correctly")
}

NSFetchedResultsController does not return any result

I am trying to use NSFetchedResultsController to fetch records from CoreData
I am using the following code:
let fetchedResultsController: NSFetchedResultsController<Appointment> = {
let fetchRequest = NSFetchRequest<Appointment>(entityName: "Appointment")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
let moc:NSManagedObjectContext = CoreDataManager.managedObjectContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
return frc
}()
And in numberOfItemsInSection delegate I have defined
if let count = fetchedResultsController.sections?[0].numberOfObjects {
return count
}
return 0
Last in cellForItemAt delegate method, I have defined
let appointment = self.fetchedResultsController.object(at: indexPath)
The issue, is it does not display any records from core data. Nor does it show any error. the list just comes empty.
If I fetch the record using the following code it works.
public func fetchAppointmentList() -> [Appointment] {
var list:[Appointment] = []
let request: NSFetchRequest<Appointment> = Appointment.fetchRequest()
do {
list = try moc.fetch(request)
} catch {
fatalError("\(error)")
}
return list
}
override func viewDidLoad() {
super.viewDidLoad()
self.appointmentList = self.fetchAppointmentList()
}
// Removed rest of the code to keep it simple.
Why is my fetchedResultsController not working?
Thanks.
Okay, I forgot to call performFetch() in viewDidLoad(), defining the following code in viewDidLoad solved my issue.
do {
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}

CoreData blocking UI

I'm working on an existing project, using CoreData, that:
Adds many many items from different CoreData Entity types, after receiving them from web service, but it blocks the UI Thread for many seconds, even if I use it in another thread.
Please help me, is there any way to prevent CoreData from blocking UI with minimum changes as the project is almost completed?
I'm new to CoreData and I don't have enough time to study the doc or reprogram the source code, unfortunately.
My DataController:
class DataController {
var managedObjectContext: NSManagedObjectContext
let modelName = "something"
init(closure:()->()) {
guard let modelURL = NSBundle.mainBundle().URLForResource(modelName, withExtension: "momd"),
let managedObjectModel = NSManagedObjectModel.init(contentsOfURL: modelURL)
else {
fatalError("DataController - COULD NOT INIT MANAGED OBJECT MODEL")
}
let coordinator = NSPersistentStoreCoordinator.init(managedObjectModel: managedObjectModel)
managedObjectContext = {
let parentContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
parentContext.persistentStoreCoordinator = coordinator
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.parentContext = parentContext
return managedObjectContext
}()
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
let options = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true,
NSSQLitePragmasOption: ["journal_mode": "DELETE"]
]
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last
let storeURL = NSURL.init(string: "something", relativeToURL: documentsURL)
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options)
dispatch_async(dispatch_get_main_queue()) {
closure()
}
}
catch let error as NSError {
fatalError("DataController - COULD NOT INIT SQLITE STORE: \(error.localizedDescription)")
}
}
}
func save(moc: NSManagedObjectContext? = nil) {
var managedObjectContext = moc
if moc == nil {
managedObjectContext = self.managedObjectContext
}
managedObjectContext!.performBlockAndWait {
if managedObjectContext!.hasChanges {
do {
try managedObjectContext!.save()
} catch {
print("ERROR saving context \(managedObjectContext!.description) - \(error)")
}
}
if let parentContext = managedObjectContext!.parentContext {
self.save(parentContext)
}
}
}
}
My classes are something like:
class MOCity: NSManagedObject {
#NSManaged var id: NSNumber?
#NSManaged var name: String?
// Insert code here to add functionality to your managed object subclass
class func save(id: NSNumber, json: JSON, managedObjectContext: NSManagedObjectContext) {
guard let newObject = MOCity.getById(id, create: true, managedObjectContext: managedObjectContext) else {
fatalError("City - NO OBJECT")
}
newObject.id <-- json["Id"]
newObject.name <-- json["Name"]
}
internal class func getById(id: NSNumber, create: Bool = false, managedObjectContext: NSManagedObjectContext) -> MOCity? {
let fetchRequest = NSFetchRequest(entityName: "City")
fetchRequest.predicate = NSPredicate(format: "id = \(id)")
do {
guard let fetchResults = try managedObjectContext.executeFetchRequest(fetchRequest) as? [MOCity] else {
fatalError("city - NO FETCH RESULTS")
}
if fetchResults.count > 0 {
return fetchResults.last
}
else if fetchResults.count == 0 {
if create {
guard let object = NSEntityDescription.insertNewObjectForEntityForName("City", inManagedObjectContext: managedObjectContext) as? MOCity else {
fatalError("getCity - COULD NOT CREATE OBJECT")
}
return object
} else {
return nil
}
}
}
catch let error as NSError {
...
}
return nil
}
}
And my web service class:
Alamofire.request(.POST, url,parameters:data).validate().responseJSON(completionHandler: {response in
switch response.result {
case .Success:
if let jsonData = response.result.value {
if let array = jsonData as? NSArray {
for arrayItem in array {
MOCity.save(arrayItem["Id"] as! NSNumber, json: JSON(arrayItem as! NSDictionary)!, managedObjectContext: self.dataController.managedObjectContext)
}
self.dataController.save()
}
}
break
case .Failure(let error):
...
}
})
One of the issue could be connected for the QOS level.
QOS_CLASS_USER_INITIATED: The user initiated class represents tasks
that are initiated from the UI and can be performed asynchronously. It
should be used when the user is waiting for immediate results, and for
tasks required to continue user interaction.
Try to use QOS_CLASS_BACKGROUND.
I would suggest two things. First, save the private queue context inside a performBlock function just as you do the main queue context. Second, set breakpoints at different places, such as before and after saves, to check which thread is calling that code (using NSThread's thread checking methods). That may enlighten you as to what code is being executed from which thread. Hope that helps steer you in the right direction.

Resources