I may have done a really stupid thing.
In writing an upgrade for my (first) shipped app, I changed the schema of my Core Data model, adding another entity (Issuer) and creating a to-many relationship with the existing entity (Coin). I thought the lightweight migration had successfully taken care of the change:
Before:
After:
Here's the Core Data stack, created automatically when I created the project:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Coin_Portfolio")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
In testing on my iPhone, I found that I could create Coins, like this:
func createNewCoin(){
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
newCoin = NSEntityDescription.insertNewObject(forEntityName: "Coin", into: context) as! Coin
if currentOwner.name == nil {
print("In NewCoin, createNewCoin, currentOwner is nil")
}else{
print("In NewCoin, createNewCoin, currentOwner is named \(String(describing: currentOwner.name))")
}
// Problem with contexts could be here! Compare DB for this app to test bed
newCoin.denomination = nil
newCoin.grade = nil
newCoin.itemIdentifier = ""
newCoin.mintMark = nil
currentOwner.addToCoins(newCoin)
saveIt()
}
func saveIt(){
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
appDelegate.saveContext()
}
And retrieve it like this:
let context = appDelegate.persistentContainer.viewContext
let ownerFetchRequest :
NSFetchRequest<Issuer> = Issuer.fetchRequest()
do {
try ownerFRC?.performFetch()
owners = try context.fetch(ownerFetchRequest)
} catch {
fatalError("Failed to initialize FetchedResultsController: \(error)")
}
for owner in owners{
if owner.name == currentOwner.name{
currentOwnerCoinsByDenom = ((owner.coins?.allObjects as! [Coin]).sorted(by: { $0.denomination! < $1.denomination! }))
currentOwnerCoinsByYear = ((currentOwnerCoinsByDenom as! [Coin]).sorted(by: { $0.denomination! < $1.denomination! }))
print ("currentOwnerCoinsByDenom.count == \(currentOwnerCoinsByDenom)")
print(currentOwnerCoinsByDenom.count)
print ("currentOwnerCoinsByYear.count == \(currentOwnerCoinsByYear)")
print(currentOwnerCoinsByYear.count)
}
}
}
Fine, as long as I don't quit the app and relaunch it.
Upon relaunch, my fetch attempts return no Coins, even though I know that there had been some 80+ records stored in Core Data before the schema change.
I then started what I thought was a rational approach to solving the problem. See iOS data saved in Core Data doesn't survive launch. The answers received, unfortunately, didn't solve the problem.
Accordingly, I panicked, knowing my users wouldn't take kindly to losing their own records.
Here's where I may have run off the rails: I created a duplicate project in order to try to resolve the issue without screwing up my original. But I continue to experience the same problem.
I have examined the dataBase(s) using DB Browser for SQLite, but am very confused as to which DB I'm downloading -- is it the original, or the one created by the duplicate project? They both have the same name, but one of them has the original records, the other only one record.
Any ideas how I can save myself? I'm flummoxed and desperate! Will gladly supply any needed info or code.
All help deeply appreciated!
Related
Every of our data row, contains an unique uuid column.
Previously, before adopting CloudKit, the uuid column has a unique constraint. This enables us to prevent data duplication.
Now, we start to integrate CloudKit, into our existing CoreData. Such unique constraint is removed. The following user flow, will cause data duplication.
Steps to cause data duplication when using CloudKit
Launch the app for the first time.
Since there is empty data, a pre-defined data with pre-defined uuid is generated.
The pre-defined data is sync to iCloud.
The app is uninstalled.
The app is re-installed.
Launch the app for the first time.
Since there is empty data, a pre-defined data with pre-defined uuid is generated.
Previous old pre-defined data from step 3, is sync to the device.
We are now having 2 pre-defined data with same uuid! :(
I was wondering, is there a way for us to prevent such duplication?
In step 8, we wish we have a way to execute such logic before written into CoreData
Check whether such uuid exists in CoreData. If not, write to CoreData.
If not, we will pick the one with latest update date, then overwrite
the existing data.
I once try to insert the above logic into https://developer.apple.com/documentation/coredata/nsmanagedobject/1506209-willsave . To prevent save, I am using self.managedObjectContext?.rollback(). But it just crash.
Do you have any idea, what are some reliable mechanism I can use, to prevent data duplication in CoreData CloudKit?
Additional info:
Before adopting CloudKit
We are using using the following CoreData stack
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
precondition(Thread.isMainThread)
let container = NSPersistentContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
// TODO: Not sure these are required...
//
//container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//container.viewContext.undoManager = nil
//container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
Our CoreData data schema has
Unique constraint.
Deny deletion rule for relationship.
Not having default value for non-null field.
After adopting CloudKit
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
precondition(Thread.isMainThread)
let container = NSPersistentCloudKitContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
// TODO: Not sure these are required...
//
//container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//container.viewContext.undoManager = nil
//container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
We change the CoreData data schema to
Not having unique constraint.
Nullify deletion rule for relationship.
Having default value for non-null field.
Based on a feedback of a Developer Technical Support engineer from https://developer.apple.com/forums/thread/699634?login=true , hen mentioned we can
Detecting Relevant Changes by Consuming Store Persistent History
Removing Duplicate Data
But, it isn't entirely clear on how it should be implemented, as the github link provided is broken.
There is no unique constraint feature once we have integrated with CloudKit.
The workaround on this limitation is
Once duplication is detected after insertion by CloudKit, we will
perform duplicated data deletion.
The challenging part of this workaround is, how can we be notified when there is insertion performed by CloudKit?
Here's step-by-step on how to be notified when there is insertion performed by CloudKit.
Turn on NSPersistentHistoryTrackingKey feature in CoreData.
Turn on NSPersistentStoreRemoteChangeNotificationPostOptionKey feature in CoreData.
Set viewContext.transactionAuthor = "app". This is an important step so that when we query on transaction history, we know which DB transaction is initiated by our app, and which DB transaction is initiated by CloudKit.
Whenever we are notified automatically via NSPersistentStoreRemoteChangeNotificationPostOptionKey feature, we will start to query on transaction history. The query will filter based on transaction author and last query token. Please refer to the code example for more detailed.
Once we have detected the transaction is insert, and it operates on our concerned entity, we will start to perform duplicated data deletion, based on concerned entity
Code example
import CoreData
class CoreDataStack: CoreDataStackable {
let appTransactionAuthorName = "app"
/**
The file URL for persisting the persistent history token.
*/
private lazy var tokenFile: URL = {
return UserDataDirectory.token.url.appendingPathComponent("token.data", isDirectory: false)
}()
/**
Track the last history token processed for a store, and write its value to file.
The historyQueue reads the token when executing operations, and updates it after processing is complete.
*/
private var lastHistoryToken: NSPersistentHistoryToken? = nil {
didSet {
guard let token = lastHistoryToken,
let data = try? NSKeyedArchiver.archivedData( withRootObject: token, requiringSecureCoding: true) else { return }
if !UserDataDirectory.token.url.createCompleteDirectoryHierarchyIfDoesNotExist() {
return
}
do {
try data.write(to: tokenFile)
} catch {
error_log(error)
}
}
}
/**
An operation queue for handling history processing tasks: watching changes, deduplicating tags, and triggering UI updates if needed.
*/
private lazy var historyQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
var viewContext: NSManagedObjectContext {
persistentContainer.viewContext
}
static let INSTANCE = CoreDataStack()
private init() {
// Load the last token from the token file.
if let tokenData = try? Data(contentsOf: tokenFile) {
do {
lastHistoryToken = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSPersistentHistoryToken.self, from: tokenData)
} catch {
error_log(error)
}
}
}
deinit {
deinitStoreRemoteChangeNotification()
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
precondition(Thread.isMainThread)
let container = NSPersistentCloudKitContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.xxx)
// turn on persistent history tracking
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// Provide transaction author name, so that we can know whether this DB transaction is performed by our app
// locally, or performed by CloudKit during background sync.
container.viewContext.transactionAuthor = appTransactionAuthorName
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
// TODO: Not sure these are required...
//
//container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//container.viewContext.undoManager = nil
//container.viewContext.shouldDeleteInaccessibleFaults = true
// Observe Core Data remote change notifications.
initStoreRemoteChangeNotification(container)
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
precondition(Thread.isMainThread)
let backgroundContext = persistentContainer.newBackgroundContext()
// Provide transaction author name, so that we can know whether this DB transaction is performed by our app
// locally, or performed by CloudKit during background sync.
backgroundContext.transactionAuthor = appTransactionAuthorName
// Similar behavior as Android's Room OnConflictStrategy.REPLACE
// Old data will be overwritten by new data if index conflicts happen.
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// TODO: Not sure these are required...
//backgroundContext.undoManager = nil
return backgroundContext
}()
private func initStoreRemoteChangeNotification(_ container: NSPersistentContainer) {
// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(
self,
selector: #selector(storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange,
object: container.persistentStoreCoordinator
)
}
private func deinitStoreRemoteChangeNotification() {
NotificationCenter.default.removeObserver(self)
}
#objc func storeRemoteChange(_ notification: Notification) {
// Process persistent history to merge changes from other coordinators.
historyQueue.addOperation {
self.processPersistentHistory()
}
}
/**
Process persistent history, posting any relevant transactions to the current view.
*/
private func processPersistentHistory() {
backgroundContext.performAndWait {
// Fetch history received from outside the app since the last token
let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest!
historyFetchRequest.predicate = NSPredicate(format: "author != %#", appTransactionAuthorName)
let request = NSPersistentHistoryChangeRequest.fetchHistory(after: lastHistoryToken)
request.fetchRequest = historyFetchRequest
let result = (try? backgroundContext.execute(request)) as? NSPersistentHistoryResult
guard let transactions = result?.result as? [NSPersistentHistoryTransaction] else { return }
if transactions.isEmpty {
return
}
for transaction in transactions {
if let changes = transaction.changes {
for change in changes {
let entity = change.changedObjectID.entity.name
let changeType = change.changeType
let objectID = change.changedObjectID
if entity == "NSTabInfo" && changeType == .insert {
deduplicateNSTabInfo(objectID)
}
}
}
}
// Update the history token using the last transaction.
lastHistoryToken = transactions.last!.token
}
}
private func deduplicateNSTabInfo(_ objectID: NSManagedObjectID) {
do {
guard let nsTabInfo = try backgroundContext.existingObject(with: objectID) as? NSTabInfo else { return }
let uuid = nsTabInfo.uuid
guard let nsTabInfos = NSTabInfoRepository.INSTANCE.getNSTabInfosInBackground(uuid) else { return }
if nsTabInfos.isEmpty {
return
}
var bestNSTabInfo: NSTabInfo? = nil
for nsTabInfo in nsTabInfos {
if let _bestNSTabInfo = bestNSTabInfo {
if nsTabInfo.syncedTimestamp > _bestNSTabInfo.syncedTimestamp {
bestNSTabInfo = nsTabInfo
}
} else {
bestNSTabInfo = nsTabInfo
}
}
for nsTabInfo in nsTabInfos {
if nsTabInfo === bestNSTabInfo {
continue
}
// Remove old duplicated data!
backgroundContext.delete(nsTabInfo)
}
RepositoryUtils.saveContextIfPossible(backgroundContext)
} catch {
error_log(error)
}
}
}
Reference
https://developer.apple.com/documentation/coredata/synchronizing_a_local_store_to_the_cloud - In the sample code, the file CoreDataStack.swift illustrate a similar example, on how to remove duplicated data after cloud sync.
https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes - Information on transaction histories.
What's the best approach to prefill Core Data store when using NSPersistentCloudKitContainer? - A similar question
I am trying to save high score data for users that play my card game. I want to use CoreData to save and load that data but I am very new to swift programming and CoreData in general and my project was created well before I knew I wanted to use CoreData and the easiest way to import it is to check the 'use CoreData' box. So here is my ViewController code (only the important parts of course)
var trackTapCount = 0
private var managedContext:NSManagedObjectContext!
private var entityDescription:NSEntityDescription!
private var scoreData: NSManagedObject!
var capturedData = [Score]()
var storedInitials: String = ""
var storedTime: String = ""
var sessionData = Score(username: "", completionTime: 0, numTurns: 0, dateTimestamp: "")
override func viewDidLoad() {
super.viewDidLoad()
let saveAppDelegate = UIApplication.shared.delegate as? AppDelegate
managedContext = saveAppDelegate?.persistentContainer.viewContext
// managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.newBackgroundContext()// Does this work?
entityDescription = NSEntityDescription.entity(forEntityName: "ScoreData", in: managedContext)
//
// scoreData = NSManagedObject(entity: entityDescription, insertInto: managedContext)
}
My AppDelegate code
/*
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: "BoschBrooke_4.1_MemoryGame")
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)")
}
}
}
And finally, my Entity setup looks like the following:
E: ScoreData
Attributes:
initials Type: String
tapCount Type: Integer 16
timeComplete Type: String
timestamp Type: String
Any idea how I can use the NSEntityDescription and NSManagedObject to save my data properly? One of the big issues I am running into is at the entityDescription line in my ViewController. It's saying 'unexpectedly found nil while implicitly unwrapping an Optional value'.
I am adding a newly created user exercise to an existing user routine via a save command. To explain the code below, I set the objectcontext, then is the exercise is new (userExercise is nil) I execute a new save block.
I then check if this new exercise is being added to an existing routine (associatedRoutineToAddTo is a string routine name from the previous VC).
This is where the issue is. I attempt to get the UserRoutine object that it needs to be added to, using a predicate based on the routines name from the string i passed from the previous VC. then i attempt to add the exercise to the routine. This causes the crash.
The rest of the code isnt too relevant as it works fine in terms of saving an edit or new exercise with no parent routine, its just this one part.
Here is what I am trying:
func getMainContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func createExercise() {
print("SAVE EXERCISE PRESSED")
let managedObjectContext = getMainContext()
if userExercise == nil {
print("SAVING THE NEW EXERCISE")
let newUserExercise = UserExercise(context: self.managedObjectContext!)
newUserExercise.name = userExerciseName.text
newUserExercise.sets = Int64(userSetsCount)
newUserExercise.reps = Int64(userRepsCount)
newUserExercise.weight = Double(self.userExerciseWeight.text!)!
newUserExercise.dateCreated = NSDate()
if self.associatedRoutineToAddTo != nil {
let existingUserRoutine = UserRoutine(context: managedObjectContext)
let request: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
request.predicate = NSPredicate(format: "usersroutine.name == %#", self.associatedRoutineToAddTo!)
existingUserRoutine.addToUserexercises(newUserExercise)
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
} else if self.associatedRoutineToAddTo == nil {
print("THIS IS A FRESH EXERCISE WITHOUT A PARENT ROUTINE")
}
}
The error reads:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'usersroutine' between objects in different contexts (source = <UserExercise: 0x618000280e60>
edit: revised my fetch code for review:
let fetchRequest: NSFetchRequest<UserRoutine> = UserRoutine.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "usersroutine.name == %#", self.associatedRoutineToAddTo!)
do {
existingUserRoutine = try managedObjectContext.fetch(fetchRequest) as! UserRoutine
print("Routine Below Fetched")
print(existingUserRoutine)
} catch {
print("Fetch Failed")
}
existingUserRoutine.addToUserexercises(newUserExercise)
You create the object here:
let newUserExercise = UserExercise(context: self.managedObjectContext!)
And fetch the related object here:
let existingUserRoutine = UserRoutine(context: managedObjectContext)
You've created a local variable called managedObjectContext here:
let managedObjectContext = getMainContext()
And your error states:
attempt to establish a relationship 'usersroutine' between objects in different contexts
Therefore, your property managedObjectContext is not the same as that returned by getMainContext()
In addition to all that, you're creating a brand new UserRoutine, and assigning it to a value called existingRoutine, then creating a fetch request that you don't do anything with, which suggests you're a little confused about what is supposed to be happening here.
WHen you make UserExcercise you are referencing a context saved on your VC:
let newUserExercise = UserExercise(context: self.managedObjectContext!)
Then
let existingUserRoutine = UserRoutine(context: managedObjectContext)
The getMainContext() acquire context here?
OverView
I continue to run into issues with adding multiple values to my Core Data entity. All i need to do is simply add 6 string-value items from a text field into Core Data. Specific examples/critique of my code would be very appreciated, as i am nearing mental break down with this issue.
The Issue
The first time i ran this, i tested it by saving only the first line (the product name) to core data and then printing it off. It worked perfect. After that, i tried the same method for all of them, and then tried printing. My program would set a breakpoint next to the "entity1.setValue(three, forKey: "serialNo")."
I also get a message in the debugger area that says (lldb).
If i try to step through the breakpoint, everything just prints out as 'nil'.
CODE
#IBAction func saveButton(sender: AnyObject) {
let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObjectForEntityForName("UsedInfo", inManagedObjectContext:context) as NSManagedObject
let one = pickerTextField.text
let two = modelName.text
let three = serialNo.text
let four = YOM.text
let five = engineHours.text
let six = locationOfMachine.text
entity1.setValue(one, forKey: "product")
entity1.setValue(two, forKey:"modelName")
entity1.setValue(three, forKey:"serialNo")
entity1.setValue(four, forKey:"yom")
entity1.setValue(five, forKey:"engineHours")
entity1.setValue(six, forKey:"location")
print(entity1.valueForKey("product"))
print(entity1.valueForKey("modelName"))
print(entity1.valueForKey("serialNo"))
print(entity1.valueForKey("yom"))
print(entity1.valueForKey("engineHours"))
do {
try context.save()
}
catch {
print("error")
}
}
I have entities Application and Process, one application can have many processes, but one process can only have one application. I get one specific application entity:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let appfetchRequest = NSFetchRequest(entityName: "Application")
//appfetchRequest.returnsObjectsAsFaults = false
do{
let results = try managedContext.executeFetchRequest(appfetchRequest)
applications.addObjectsFromArray(results as! [NSMutableArray])
}catch let error as NSError{
print("Jakis blad z applications entity: \(error)")
}
let predicate:NSPredicate = NSPredicate(format: "name == %#", appTitle)
let application:NSArray = applications.filteredArrayUsingPredicate(predicate)
and in this I have relationship (named applicationprocesses).
I try to get an array with these entities in many ways, but no one work.
Actually, I have:
let processes = application.valueForKey("applicationprocesses").allObjects.first
print(processes?.valueForKey("applicationprocesses"))
And this give me:
Optional({(
<NSManagedObject: 0x7f945871b7a0> (entity: Application; id: 0xd000000000080002 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Application/p2> ; data: {
appcolor = "#3F3F3F";
appicon = bed;
applabel = Proces;
applabelplural = Procesy;
applicationprocesses = (
"0xd000000000140004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p5>",
"0xd000000000100004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p4>",
"0xd000000000180004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p6>",
"0xd0000000001c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p7>",
"0xd0000000000c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p3>"
);
companyid = 392;
id = 1261;
name = "aplikacja 1";
processescount = 5;
})
)})
I need to display these processes in a UITablewView, so I need an array.
I will be grateful for any help.
The problem you are having is not the result of a bad line of code somewhere. It is actually working as it is supposed to. But you can make it a lot easier to work with NSManagedObject
Any fetch from your database results in [AnyObject]. If you leave it like it is, you are forced to use key-value coding which is a pain and very easy to mess up.
However it is very simple to create Classes from CD Entities and downcast the fetch result. This is an awesome feature of CoreData that unfortunately is not stressed enough.
link to gist
Your related entities might look like this:
Go to Menu -> Editor -> Create....
Select the entities you want to create a subclass for.
New files will show up in your project :
Now you can use code like this :
Insert objects
Notice the .... as? Company this is the downcast.
It allows you to access the attributes from the CD Entity like you would access any attributes from a Struct or Class.
func createACompany() {
// no appDel here. appDel and managedContext are best declared in the class scope. See gist for entire ViewController
guard let moc = managedContext else { return }
guard let company = NSEntityDescription.insertNewObjectForEntityForName("Company", inManagedObjectContext: moc) as? Company else {
return // guard is handy to "abort"
}
company.name = "Apple"
guard let bob = NSEntityDescription.insertNewObjectForEntityForName("Employee", inManagedObjectContext: moc) as? Employee else {
return
}
bob.name = "Bob"
bob.company = company
do {
try moc.save()
} catch {
print("core data save error : \(error)")
}
moc.refreshAllObjects()
}
Fetch objects
func fetchCompanies() -> [Company] {
guard let moc = managedContext else { return [] }
let request = NSFetchRequest(entityName: "Company")
request.returnsObjectsAsFaults = true
do {
let results = try moc.executeFetchRequest(request)
guard let companies = results as? [Company] else {
return []
}
return companies
}catch let error as NSError{
print("core data fetch error: \(error)")
return []
}
}
Get related objects
Look closely at the guard statement.
let employeeSet = company.employees -> unwrap of optional NSSet
let employees = employeeSet.allObjects -> get all objects in the NSSet
as? [Employee] -> downcast the result of allObjects to an Array
of Employee
func getEmployees(forCompany company : Company) -> [Employee] {
guard let employeeSet = company.employees, let employees = employeeSet.allObjects as? [Employee] else {
return []
}
return employees
}
Side note:
If you change your mind about the naming of the class and you change it everywhere. Don't forget the update it here too :
The class field has to be updated.
Similar to the InterfaceBuilder. If you change the class name it will not find it anymore.
I believe that you want to get all the managed objects (process) relate to the application object you already got. We usually use the "entity" word for same things as table in database.
I'm sorry I didn't have swift version for this line of code. I'm not sure about my swift skill
NSArray *processes = [[application fristObject] objectIDsForRelationshipNamed:#"applicationprocesses"]
Then use API to convert objectID to ManagedObject
(__kindof NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID;
The only problem is this API, (NSArray *)objectIDsForRelationshipNamed:(NSString *)key , only supported from version 8.3