In my ViewController, I have a function for fetching all the 'headingNumbers' (an attribute of the entity 'Vocabulary'). I understood that this is not good MVC practice.
So I created a new swift file.
I did not import UIKit. Youtube Stanford - Developing iOS 9 Apps with Swift - 2, 22:53: "Never import UI KIT in a model file because the model is UI independent"
(https://www.youtube.com/watch?v=j50mPzDMWVQ)
However, now I get these messages like
"Use of unresolved identifier 'UIApplication'".
Which makes sense, as I did not import the UIKit.
The question is: how do I now execute a fetch request in my new swift file.
(As you probably now by now, I am a beginner)
import Foundation
import CoreData
class QueryData {
private var selectedHeadingNumber:String = "-123456789"
private var setOfHeadingNumbers:[String] = [String]()
func getHeadingNumbers2() -> [String] {
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
// Create Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Vocabulary")
// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "headingNumber", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Execute Fetch Request
do {
let result = try managedObjectContext.executeFetchRequest(fetchRequest)
for managedObject in result {
if let foundHeadingNumber = managedObject.valueForKey("headingNumber") {
if let result_number = foundHeadingNumber as? NSNumber
{
let result_string = "\(result_number)"
if !setOfHeadingNumbers.contains(result_string) {
print("Headingnumber: \(foundHeadingNumber) ")
setOfHeadingNumbers.append(result_string)
print("updated selectedHeadingNumber: ", selectedHeadingNumber)
selectedHeadingNumber = result_string
// set the default lessonnumber to the first lesson
if selectedHeadingNumber == "-123456789" {
selectedHeadingNumber = result_string
print("updated selectedHeadingNumber: ", selectedHeadingNumber)
}
}
}
//setOfHeadingNumbers.append(first)
}
}
} catch {
let fetchError = error as NSError
print(fetchError)
}
} // end of if statement
return setOfHeadingNumbers
} // end of func
} // end of class
What you actually need is NSManagedObjectContex. Pass it as an argument to method:
func getHeadingNumbers2(inContext context: NSManagedObjectContext) -> [String]
{
...
}
Or inject it as dependency in initializer
class QueryData
{
let context: NSManagedObjectContext
init(context: NSManagedObjectContext)
{
self.context = context
}
func getHeadingNumbers2() -> [String]
{
let fetchRequest = NSFetchRequest(entityName: "Vocabulary")
let result = try self.context.executeFetchRequest(fetchRequest)
...
}
}
Related
I currently have two managed objects for Core Data that has one-to-many relationship.
Goal
extension Goal {
#nonobjc public class func createFetchRequest() -> NSFetchRequest<Goal> {
return NSFetchRequest<Goal>(entityName: "Goal")
}
#NSManaged public var title: String
#NSManaged public var date: Date
#NSManaged public var progress: NSSet?
}
Progress
extension Progress {
#nonobjc public class func createFetchRequest() -> NSFetchRequest<Progress> {
return NSFetchRequest<Progress>(entityName: "Progress")
}
#NSManaged public var date: Date
#NSManaged public var comment: String?
#NSManaged public var goal: Goal
}
For every goal, you can have multiple Progress objects. The problem is when I request a fetch for Progress with a particular Goal as the predicate, nothing is being returned. I have a suspicion that I'm not using the predicate properly.
This is how I request them.
First, I fetch Goal for a table view controller:
var fetchedResultsController: NSFetchedResultsController<Goal>!
if fetchedResultsController == nil {
let request = Goal.createFetchRequest()
let sort = NSSortDescriptor(key: "date", ascending: false)
request.sortDescriptors = [sort]
request.fetchBatchSize = 20
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.context, sectionNameKeyPath: "title", cacheName: nil)
fetchedResultsController.delegate = self
}
fetchedResultsController.fetchRequest.predicate = goalPredicate
do {
try fetchedResultsController.performFetch()
} catch {
print("Fetch failed")
}
And pass the result to the next screen, Detail view controller:
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
vc.goal = fetchedResultsController.object(at: indexPath)
navigationController?.pushViewController(vc, animated: true)
}
Finally, I fetch Progress using the Goal as the predicate from Detail view controller:
var goal: Goal!
let progressRequest = Progress.createFetchRequest()
progressRequest.predicate = NSPredicate(format: "goal == %#", goal)
if let progress = try? self.context.fetch(progressRequest) {
print("progress: \(progress)")
if progress.count > 0 {
fetchedResult = progress[0]
print("fetchedResult: \(fetchedResult)")
}
}
Goal is being returned properly, but I get nothing back for Progress. I've tried:
progressRequest.predicate = NSPredicate(format: "goal.title == %#", goal.title)
or
progressRequest.predicate = NSPredicate(format: "ANY goal == %#", goal)
but still the same result.
Following is how I set up the relationship:
// input for Progress from the user
let progress = Progress(context: self.context)
progress.date = Date()
progress.comment = commentTextView.text
// fetch the related Goal
var goalForProgress: Goal!
let goalRequest = Goal.createFetchRequest()
goalRequest.predicate = NSPredicate(format: "title == %#", titleLabel.text!)
if let goal = try? self.context.fetch(goalRequest) {
if goal.count > 0 {
goalForProgress = goal[0]
}
}
// establish the relationship between Goal and Progress
goalForProgress.progress.insert(progress)
// save
if self.context.hasChanges {
do {
try self.context.save()
} catch {
print("An error occurred while saving: \(error.localizedDescription)")
}
}
Actually you don't need to refetch the data. You can get the progress from the relationship
Declare progress as native Set
#NSManaged public var progress: Set<Progress>
In DetailViewController delete the fetch code in viewDidLoad and declare
var progress: Progress!
In the first view controller filter the progress
let goal = fetchedResultsController.object(at: indexPath)
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController,
let progress = goal.progress.first(where: {$0.goal.title == goal.title}) {
vc.progress = progress
navigationController?.pushViewController(vc, animated: true)
}
And consider to name the to-many relationship in plural form (progresses)
I figured out that it's due to Core Data Fault where Core Data lazy loads the data and unless you explicitly access the data, the value will not be displayed.
You can either do something like the following:
let goal = fetchedResultsController.object(at: indexPath)
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController,
let progress = goal.progress.first(where: {$0.goal.title == goal.title}) {
vc.goalTitle = goal.title
vc.date = progress.date
if let comment = progress.comment {
vc.comment = comment
}
navigationController?.pushViewController(vc, animated: true)
}
or setreturnsObjectsAsFaults to false.
Here's a good article on the topic.
I'm working with old xcdatamodel, it was created in xcode 7.3 (that's a crucial since I don't have the following issue on modern models). At the same time, this issue is not cured by simple changing Tool Version to Xcode 9.0 for my xcdatamodel.
I'm fetching data in for loop, in the thread of context I use for fetching data. When I try to fetch the entity that has already been fetched once, coreData crashes with EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP). Zombie tracking says [CFString copy]: message sent to deallocated instance 0x608000676b40.
This is the concept of what I do:
LegacyDatabaser.context.perform {
do {
for _ in 0..<10 {
let entity = try self.legacyDatabase.getEntity(forId:1)
print(entity.some_string_property) // <- crash here
}
} catch {
// ...
}
}
Here is the context initializer:
class LegacyDatabaser {
static var context: NSManagedObjectContext = LegacyDatabaseUtility.context
// ...
}
And
class LegacyDatabaseUtility {
fileprivate class var context: NSManagedObjectContext {
//let context = NSManagedObjectContext(concurrencyType:.privateQueueConcurrencyType)
//context.persistentStoreCoordinator = storeContainer.persistentStoreCoordinator
//return context // This didn't help also
return storeContainer.newBackgroundContext()
}
private static var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name:"MyDBName")
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
}
Here is the data fetcher:
func getEntity(forId id: NSNumber) throws -> MyEntity? {
// Create predicate
let predicate = NSPredicate(format:"id_local == %#", id)
// Find items in db
let results = try LegacyDatabaseUtility.find(predicate:predicate, sortDescriptors:nil, in:LegacyDatabaser.context)
// Check it
if results.count == 1 {
if let result = results.first as? MyEntity {
return result
} else {
return nil
}
} else {
return nil
}
}
And:
static func find(predicate:NSPredicate?, sortDescriptors:[NSSortDescriptor]?, in context: NSManagedObjectContext) throws -> [NSManagedObject] {
// Create a request
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName:"MyEntity")
// Apply predicate
if let predicate = predicate {
fetchRequest.predicate = predicate
}
// Apply sorting
if let sortDescriptors = sortDescriptors {
fetchRequest.sortDescriptors = sortDescriptors
}
// Run the fetchRequest
return try context.fetch(fetchRequest)
}
I don't address the context somewhere in a parallel, I'm sure I use the correct thread and context (I tested the main context also, the same result). What I'm doing wrong, why re-fetching the same entity fails?
If anyone catches any quirk crashes like one I described above, check the name of properties in your NSManagedObject. In my case, crashes were on call new_id property which is, I guess, kinda reserved name. Once I renamed this property, the crashes stopped.
in my ios swift application I have a database using Core Data.
It has many entities, all entities have an integer field called syncStatus. it can be 0, 1, or 2.
On startup, I want to loop through ALL the entities that have syncStatus = 1 and change it to 0
Is there a way to do it without fetching each type alone and changing it?
So what I want is:
fetch ALL entities with syncStatus = 1
Loop through them and set syncStatus = 0
Currently I'm doing them one by one:
fetch UserEntities with syncStatus = 1
Loop through them and set syncStatus = 0
fetch countryEntities with syncStatus = 1
Loop through them and set syncStatus = 0
Do the same for every entity one by one
code:
let allUsers = context?.fetch(FetchRequest<UserEntity>().filtered(with: "syncStatus", equalTo: "1"))
let allCountries = context?.fetch(FetchRequest<CountryEntity>().filtered(with: "syncStatus", equalTo: "1"))
.
.
.
I'm just trying to find a generic approach, in case later we add another entity/table we don't have to come back to this code and add it here also.
First of all, fetching all entries and filter them is much more expensive than applying a predicate.
I recommend to use a protocol extension with static methods. The benefit is that you can call the methods directly on the type
protocol SyncStatusResettable
{
associatedtype Entity: NSManagedObject = Self
var syncStatus : String {get set}
static var entityName : String { get }
static func resetSyncStatus(in context: NSManagedObjectContext) throws
}
extension SyncStatusResettable where Entity == Self
{
static var entityName : String {
return NSStringFromClass(self).components(separatedBy: ".").last!
}
static func resetSyncStatus(in context: NSManagedObjectContext) throws
{
let request = NSFetchRequest<Entity>(entityName: entityName)
request.predicate = NSPredicate(format: "syncStatus == 1")
let items = try context.fetch(request)
for var item in items { item.syncStatus = "0" }
if context.hasChanges { try context.save() }
}
}
To use it, adopt SyncStatusResettable for all NSManagedObject subclasses and call
do {
try UserEntity.resetSyncStatus(in: managedObjectContext)
try CountryEntity.resetSyncStatus(in: managedObjectContext)
} catch { print(error) }
managedObjectContext is the current NSManagedObjectContext instance
NSManagedObjectModel allows you to enumerate through the entities it contains, and NSEntityDescription can give you properties for each entity. Once you have a reference to the model:
let entitiesWithSync = model.entities.filter {
$0.properties.contains(where: { $0.name == "syncStatus" })
}
Will give you all of the relevant entities. You can then use this list of entities to drive your updates - note that using NSBatchUpdateRequest is faster if you're doing this on startup. You can create batch update requests using the entity descriptions obtained in the loop above.
In the past I have looped through all the entitiesByName from the object model:
lazy var managedObjectModel: NSManagedObjectModel = {
let modelUrl = NSBundle.mainBundle().URLForResource("SomeProject", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelUrl)
}
func updateAllData() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
context.performAndWait {
let allEntities = self.managedObjectModel.entitiesByName
for (entity, items) in allEntities {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
...
}
}
}
I'm doing a 'medium-weight' Core Data Migration. I'm using a Mapping Model to migrate from one legacy store / data model to a different store and different model (i.e. completely different .xcdatamodeld) files, and using custom NSEntityMigrationPolicy objects where applicable.
Where previously I had all sorts of objects unrelated on the object graph, I now want to have a master object Library that would enable me to easily wipe out all of the associated data (using the Cascade delete rule).
I've run into problems during the migration because of a custom method in my NSEntityMigrationPolicy subclass:
class LegacyToModernPolicy: NSEntityMigrationPolicy {
func libraryForManager(_ manager: NSMigrationManager) -> Library {
let fetchRequest: NSFetchRequest<Library> = NSFetchRequest(entityName: Library.entity().name!)
fetchRequest.predicate = nil
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "filename", ascending: true)]
fetchRequest.fetchLimit = 1
do {
// will fail here if NSFetchRequest<Library>
let results = try manager.destinationContext.fetch(fetchRequest)
log.info("results: \(results)")
if results.count == 1 {
// can fail here if NSFetchRequest<NSManagedObject>
return results.first! as! Library
} else {
let newLib = Library(context: manager.destinationContext)
return newLib
}
} catch {
log.error("Error fetching: \(error.localizedDescription)")
}
let newLib = Library(context: manager.destinationContext)
return newLib
}
}
An exception will be thrown, and the error message is:
Could not cast value of type 'NSManagedObject_Library_' (0x6100000504d0) to 'SongbookSimple.Library' (0x101679180).
The question is, why is that happening, and does it matter? Because a migration is happening, perhaps it's enough to return the NSManagedObject with the correct entity description ?
The reason is that during migration, you should not be using instances of NSManagedObject subclasses. You need to express all of these in the form of NSManagedObject. So the code above must become:
class LegacyToModernPolicy: NSEntityMigrationPolicy {
static func find(entityName: String,
in context: NSManagedObjectContext,
sortDescriptors: [NSSortDescriptor],
with predicate: NSPredicate? = nil,
limit: Int? = nil) throws -> [NSManagedObject] {
let fetchRequest: NSFetchRequest<NSManagedObject> = NSFetchRequest(entityName: entityName)
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = sortDescriptors
if let limit = limit {
fetchRequest.fetchLimit = limit
}
do {
let results = try context.fetch(fetchRequest)
return results
} catch {
log.error("Error fetching: \(error.localizedDescription)")
throw error
}
}
func libraryForManager(_ manager: NSMigrationManager) -> NSManagedObject {
do {
var library: NSManagedObject? = try LegacyToModernPolicy.find(entityName: Library.entity().name!,
in: manager.destinationContext,
sortDescriptors: [NSSortDescriptor(key: "filename", ascending: true)],
with: nil,
limit: 1).first
if library == nil {
let dInstance = NSEntityDescription.insertNewObject(forEntityName: Library.entity().name!, into: manager.destinationContext)
// awakeFromInsert is not called, so I have to do the things I did there, here:
dInstance.setValue(Library.libraryFilename, forKey: #keyPath(Library.filename))
dInstance.setValue(NSDate(timeIntervalSince1970: 0), forKey: #keyPath(Library.updatedAt))
library = dInstance
}
return library!
} catch {
fatalError("Not sure why this is failing!")
}
}}
You can read more about my less-than-fun experiences with Core Data Migrations here.
these days I start to learn ios applications development using SWIFT language
so I started to build my own app which contain forms that collect information from users and save\retrieve it to\from core data
my home page hide/show its buttons depending on data retrieved from the core data and do simple check on it so the data have to be up to date to avoid mistakes
but when I add user to the Core data and return to the home page it show the buttons as nothing has been added but if leave the home page to other page and then back to home page then the last user added appears
it seems like the context did not finish the data saving before the home appears
How I can fix that and ensure that the context object finish saving then show the home page
thanks a lot
Please keep in mind that waiting for context to save before performing segue might be not the best solution - depending on task it can take a long time. If use this approach you should show some progress indicator to user or smth.
Otherwise it will look like your app UI is freezing and that is a bad UX.
Anyway answering your question you have 3 basic solutions :
use competition closure
use delegation
use notifications
I assume you use some sort of custom class to load the CoreData Stack and you probably have function for saving context. Than it might look like this :
private func saveContext(completition : (()->() )?) {
if let moc = self.context {
var error : NSError? = nil
if moc.hasChanges && !moc.save(&error){
println(error?.localizedDescription)
abort()
}
//Call delegate method
delegate?.MiniCoreDataStackDidSaveContext()
//Send notification message
defaultCenter.postNotificationName("MyContextDidSaveNotification", object: self)
//Perform completition closure
if let closure = completition {
closure()
}
}
}
And you use it like this :
MyCoreDataStack.saveContext(){
performSegueWithIdentifier(SEGUE_ID,nil)
}
or
NSNotificationCenter.defaultCenter().addObserverForName("MyContextDidSaveNotification",
object: MyCoreDataStack.saveContext,
queue: NSOperationQueue.mainQueue(),
usingBlock: { _ in performSegueWithIdentifier(SEGUE_ID, sender: nil) }
)
In case you don't have any Stack - I've written this small singleton class as an example it lacks of proper error handling etc.
In a private function saveContext it combines all three approaches (it's only for example, I would not advice to use delegation with singleton pattern)
import CoreData
protocol MiniCoreDataStackDelegate : class {
func MiniCoreDataStackDidSaveContext()
}
#objc(MiniCoreDataStack)
class MiniCoreDataStack {
struct Constants {
static let persistentStoreName = "Store"
static let contextSaveNotification = "MiniCoreDataStackDidSaveContextNotification"
}
private var managedObjectModel : NSManagedObjectModel
private var persistentStoreCoordinator : NSPersistentStoreCoordinator? = nil
private var store : NSPersistentStore?
private let defaultCenter = NSNotificationCenter.defaultCenter()
var defaultContext : NSManagedObjectContext!
var stackIsLoaded : Bool = false
weak var delegate : MiniCoreDataStackDelegate?
class var defaultModel: NSManagedObjectModel {
return NSManagedObjectModel.mergedModelFromBundles(nil)!
}
class var sharedInstance: MiniCoreDataStack {
struct Singleton {
static let instance = MiniCoreDataStack()
}
return Singleton.instance
}
class func storesDirectory() -> NSURL {
let applicationDocumentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory,inDomains: .UserDomainMask).last as! NSURL
return applicationDocumentsDirectory
}
private func storeURLForName(name:String) -> NSURL {
return MiniCoreDataStack.storesDirectory().URLByAppendingPathComponent("\(name).sqlite")
}
func localStoreOptions() -> NSDictionary {
return [
NSInferMappingModelAutomaticallyOption:true,
NSMigratePersistentStoresAutomaticallyOption:true
]
}
init( model : NSManagedObjectModel = MiniCoreDataStack.defaultModel){
managedObjectModel = model
}
func openStore(completion:(()->Void)?) {
println("\(NSStringFromClass(self.dynamicType)): \(__FUNCTION__)")
var error: NSError? = nil
let tempPersistenStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
if let newStore = tempPersistenStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: self.storeURLForName(Constants.persistentStoreName), options: self.localStoreOptions() as [NSObject : AnyObject], error: &error){
self.persistentStoreCoordinator = tempPersistenStoreCoordinator
defaultContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
defaultContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
defaultContext.persistentStoreCoordinator = persistentStoreCoordinator
self.stackIsLoaded = true
println("\(NSStringFromClass(self.dynamicType)): Store loaded")
if let completionClosure = completion {
completionClosure()
}
} else {
println("\(NSStringFromClass(self.dynamicType)): !!! Could not add persistent store !!!")
println(error?.localizedDescription)
}
}
private func saveContext(context: NSManagedObjectContext? = MiniCoreDataStack.sharedInstance.defaultContext!, completition : (()->() )?) {
if !self.stackIsLoaded {
return
}
if let moc = context {
var error : NSError? = nil
if moc.hasChanges && !moc.save(&error){
println(error?.localizedDescription)
abort()
}
//Call delegate method
delegate?.MiniCoreDataStackDidSaveContext()
//Send notification message
defaultCenter.postNotificationName(Constants.contextSaveNotification, object: self)
//Perform completition closure
if let closure = completition {
closure()
}
}
}
func save(context: NSManagedObjectContext? = MiniCoreDataStack.sharedInstance.defaultContext!,completition : (()->() )? ) {
//Perform save on main thread
if (NSThread.isMainThread()) {
saveContext(context: context,completition: completition)
}else {
NSOperationQueue.mainQueue().addOperationWithBlock(){
self.saveContext(context: context, completition : completition)
}
}
}
func fetchResultsControllerForEntity(entity : NSEntityDescription, predicate :NSPredicate? = nil, sortDescriptors:[NSSortDescriptor]? = nil, sectionNameKeyPath:String? = nil, cacheName: String? = nil,inManagedContext context : NSManagedObjectContext? = nil ) ->NSFetchedResultsController {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entity
fetchRequest.sortDescriptors = sortDescriptors
fetchRequest.predicate = predicate
fetchRequest.fetchBatchSize = 25
var aContext = context ?? self.defaultContext!
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: aContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName)
var error: NSError?
if !fetchedResultsController.performFetch(&error){
println("Could not fetch : \(error)")
}
return fetchedResultsController
}
func executeFetchRequest(request : NSFetchRequest, context: NSManagedObjectContext? = nil) -> [NSManagedObject] {
var fetchedObjects = [NSManagedObject]()
let managedContext = context ?? defaultContext
managedContext?.performBlockAndWait{
var error: NSError?
if let result = managedContext?.executeFetchRequest(request, error: &error) {
if let managedObjects = result as? [NSManagedObject] {
fetchedObjects = managedObjects
}
}
if let err = error{
println(err)
}
}
return fetchedObjects
}
func insertEntityWithClassName(className :String, andAttributes attributesDictionary : NSDictionary? = nil, andContext context : NSManagedObjectContext = MiniCoreDataStack.sharedInstance.defaultContext ) -> NSManagedObject {
let entity = NSEntityDescription.insertNewObjectForEntityForName(className, inManagedObjectContext: context) as! NSManagedObject
if let attributes = attributesDictionary {
attributes.enumerateKeysAndObjectsUsingBlock({
(dictKey : AnyObject!, dictObj : AnyObject!, stopBool) -> Void in
entity.setValue(dictObj, forKey: dictKey as! String)
})
}
return entity
}
func deleteEntity(entity: NSManagedObject){
self.defaultContext!.deleteObject(entity)
}
}
Using Stack :
//Open store
MiniCoreDataStack.sharedInstance.openStore()
//Insert Entity
let newEntity = MiniCoreDataStack.sharedInstance.insertEntityWithClassName(YourEntityName)
//Saving
MiniCoreDataStack.sharedInstance.save(){
// completition closure
}
//Perform fetch request
MiniCoreDataStack.sharedInstance.executeFetchRequest(YourFetchRequest)