Core Data is returning 0 - swift

Update: Following some advice from the comments, I am using NSManagedObject Subclassing. So my code is completely different so I am rewriting the question to better reflect what I am doing now.
So my question is: I am saving non-zero values to Core Data, but when I retrieve a value I am getting zero back. What could be wrong?
So now let's pretend I have two entities named FirstEntity, SecondEntity with two attributes named firstAttribute, secondAttribute. And since I did the NSManagedObject Subclassing I have two classes one named FirstEntity and the second named SecondEntity. Each class has an extension for the two attributes (firstAttribute, secondAttribute).
Saving Values:
So when I save a value I would use code along the lines of:
let number = 100
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
firstEntity.firstAttribute = number
Retrieving Values: When I try to receive an attribute's value in Core Data I use code like this:
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
print(firstEntity.firstAttribute) // This returns zero when I clearly saved a value of 100 above.
I am pretty new to Core Data, and I have looked up many examples of how to use Core Data online. I am still kind of confused about what I could possibly be doing wrong. If there is any clarification required just ask in the comments, and I will definitely respond.
Potential Error: So what I am doing is I am trying to save an attributes value in the completionHandler of a function. Also, if I print the variable's value inside the completionHandler I get the proper value of the attribute. For example:
someFunction() {
number in // Pretend number is 5000
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
firstEntity.firstAttribute = number
print(firstEntity.firstAttribute) // Prints 5000 to the console
}
However, let's say I go to access the firstAttribute value outside of the completionHandler and I print the value to the console I get a value of 0. For example:
class MyClass {
func myFunction() {
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
print(firstEntity.firstAttribute) // Prints 0
}
}
I also wanted to mention that I am making sure that the completionHandler in the function has set the value of the firstAttribute before calling the MyClass's myFunction().

Ok, so I think I found the answer I was creating too many instances of the entity. So what I ended up doing was I created a global constant:
let firstEntity = NSEntityDescription.insertNewObjectForEntityForName("FirstEntity", inManagedObjectContext: managedObject) as! FirstEntity
Then I referenced firstEntity whenever I needed to retrieve or save a value so I didn't have multiple instances of FirstEntity. Then I can access the value from anywhere.

Related

Error - objects in different contexts when setting relationship during a batch insert

I have a function used to create an object graph in my app for testing purposes. The data structure is very simple at present with a one-to-many relationship between Patient and ParameterMeasurement entities.
As setup of the test state involves around 800 entries it makes sense to do this as a batch insert which works...until you try and establish the relationship between ParameterMeasurement and Patient (which, in the reciprocal, is a one-to-one) at which point the app crashes with the dreaded "Illegal attempt to establish a relationship 'cdPatient' between objects in different contexts"
I'm struggling to understand why this is happening as both Patient and ParameterMeasurement entities are created using the same managed object context which is passed to the function by the caller.
I've already tried to store the objectID of the Patient (created before instantiating ParameterMeasurement instances) and then creating a local copy of the Patient instance inside the batch insert closure (code in place below but commented out) but this does not resolve the issue. I've also checked my model (all OK, relationships are good), deleted the app and reset the sim but still no joy.
Finally, I've stuck in print statements to check the MOCs associated with both entities at the point of instantiation and the MOC passed to the function. As expected, the memory addresses match which makes it look like the error message is a red herring.
Can anyone point me in the right direction? This seems to have been a common issue in the past (lots of posts 5y+ ago with ObjC but little in Swift) but the examples on don't deal with this specific scenario.
func addSampleData(to context: NSManagedObjectContext) throws {
try addParameterDefinitions(to: context, resetToDefaults: true)
let fetchRequest = ParameterProfile.fetchAll
let profiles = try context.fetch(fetchRequest)
for _ in 1...10 {
let patient = Patient(context: context)
patient.cdName = "Patient \(UUID().uuidString.split(separator: "-")[0])"
patient.cdCreationDate = Date()
// let patientID = patient.objectID
for profile in profiles {
let data: [(Date, Double)] = DataGenerator.placeholderDataForParameter(with: profile)
var idx = 0
let total = data.count
let batchInsert = NSBatchInsertRequest(entity: ParameterMeasurement.entity()) { (managedObject: NSManagedObject) -> Bool in
guard idx < total else { return true }
// let patientInContext = context.object(with: patientID) as! Patient
if let measurement = managedObject as? ParameterMeasurement {
// measurement.cdPatient = patientInContext
measurement.cdPatient = patient
measurement.cdName = profile.cdName
measurement.cdTimestamp = data[idx].0
measurement.cdValue = data[idx].1
}
idx += 1
return false
}
do {
try context.execute(batchInsert)
try context.save()
} catch {
fatalError("Import failed with error: \(error.localizedDescription)")
}
}
}
}
Core Data model definitions:
Having done some more digging on this, it appears that batch inserts cannot be used to add relationships to the persistent store as noted here. I'm guessing its because of the difficulties associated with correctly associating entities during the process - frustrating but not a deal breaker.
For now, I'll revert to individual insertion of entities although I could do the process in 2 passes, i.e. a batch insert of the "basic" properties and a second pass setting the relationships on the inserted entities. It seems like a bit too much effort at this level though and any time saving is likely to be minimal for the extra code complexity (and risk of bugs!)

Retrieving NSOrderedSet from Core Data and casting it to entity managedObjectSubclasss

Im making a Fitness app to learn Core data, and I have found that I need to let the user store every performed workout as a WorkoutLog item, and in there, there should be a series of ExerciseLogs which represents performances of that exercise (it contains each lift and also a reference to the actual exercise design).
Problem is that after a while i realize that i need to have these ordered, so that the next time i want to show the user their workout, the order that the exercisese were performed should be the same.
So I checked "ordered" in the top right of the image there, and now my code is in dire need of an update. I have tried to read as much as I could about working with NSOrderedSet and how to fetch them from core data and then manipulate them, but I havent really found much of use to me. (I have no experice in objective-c)
For example my code that used to be:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
guard let exerciseLogsToDelete = workoutLogToDelete.loggedExercises as? Set<ExerciseLog> else {
print("error unwrapping logged exercises in deleteWorkoutLog")
return
}
I get the error: .../DatabaseFacade.swift:84:77: Cast from 'NSOrderedSet?' to unrelated type 'Set' always fails
So what ive learned about sets and core data no longer seems applicable.
Im far from an expert in programming, but im very eager to learn how to get access to the loggedExercises instances.
TLDR; Is there a way to cast NSOrderedSet to something I can work with? How do we usually work with NSManagedSets from core data? Do we cast them to Arrays or MutableSets? I would very much appreciate an example or two on how to get started with retrieving and using these ordered sets!
Thanks
For anyone else wondering how to get started with orderedSets in core data:
After setting my the WorkoutLog.loggedExercises "to-many" relationship to be ordered, I managed to access them through the mutableOrderedSetValue function like this:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
let orderedExerciseLogs: NSMutableOrderedSet = workoutLogToDelete.mutableOrderedSetValue(forKey: "loggedExercises")
let exerciseLogsToDelete = orderedExerciseLogs.array
for exerciseLog in exerciseLogsToDelete {
guard let exerciseLog = exerciseLog as? ExerciseLog else {
return
}
Works great so far.
And to rearrange the NSOrderedSet I ended up doing something like this:
// Swap the order of the orderedSet
if let orderedExerciseLogs: NSOrderedSet = dataSourceWorkoutLog.loggedExercises {
var exerciseLogsAsArray = orderedExerciseLogs.array as! [ExerciseLog]
let temp = exerciseLogsAsArray[indexA]
exerciseLogsAsArray[indexA] = exerciseLogsAsArray[indexB]
exerciseLogsAsArray[indexB] = temp
let exerciseLogsAsOrderedeSet = NSOrderedSet(array: exerciseLogsAsArray)
dataSourceWorkoutLog.loggedExercises = exerciseLogsAsOrderedeSet
}

Variable used within its own initial value Swift 3

I try to convert my code to swift 3 an I have spent hours on the following error:
Type 'Any' has no subscript members
Here's was my original code:
let data: AnyObject = user.object(forKey: "profilePicture")![0]
I looked at the answers here but I'm still stuck. (I do programming as a hobby, I'm not a pro :/)
I've try that:
let object = object.object(forKey: "profilePicture") as? NSDictionary
let data: AnyObject = object![0] as AnyObject
But now I get this error:
Variable used within its own initial value
Second issue: Use always a different variable name as the method name, basically use more descriptive names than object anyway.
First issue: Tell the compiler the type of the value for profilePicture, apparently an array.
if let profilePictures = user["profilePicture"] as? [[String:Any]], !profilePictures.isEmpty {
let data = profilePictures[0]
}
However, the array might contain Data objects, if so use
if let profilePictures = user["profilePicture"] as? [Data], !profilePictures.isEmpty {
let data = profilePictures[0]
}
Or – what the key implies – the value for profilePicture is a single object, who knows (but you ...)
And finally, as always, don't use NSArray / NSDictionary in Swift.

CoreData: Loading Integers out of a File

I got following code to load the data stored:
func loadLevel(){
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let request = NSFetchRequest(entityName: "Level")
request.returnsObjectsAsFaults = false
do{
let results:NSInteger = try context.executeFetchRequest(request)
level = results
}catch{
print("Error")
}
}
In the line where I am trying to store the data in the variabel results I get following error:
Cannot convert value of type '[AnyObject]' to specified type 'NSInteger' (aka 'Int')
I do know that I am not doing the error handling correct and as it should be. Thats the 'second step' on my list. Hopefully you can help me on my first problem.
The executeFetchRequest returns an array of results, because fetches can and usually do return more than one result. These results can be of different types depending on how the fetch request was configured. So the return type is [AnyObject].
You're attempting to assign the result of the fetch to a single NSInteger. You can't just assign an array of AnyObject like that. Your results will be zero or more instances of your Level instance, either instances of NSManagedObject or instances of a custom subclass you've configured for the entity.
What you should do is:
Change results to be of type [AnyObject]
Look through this array to find instances of Level
Get integer values from whichever attribute of Level contains the integer that you need.

Swift : Core Data Integer weird values

Just started learning Swift and have a question around Core Data. I have a very simple entity named "Score" with one attribute "points" which is of type Int16.
Here's my data model class:
class Score: NSManagedObject {
#NSManaged var points: Int}
Here's the code on my View Controller:
override func viewDidLoad() {
super.viewDidLoad()
let entity = NSEntityDescription.entityForName("Score", inManagedObjectContext: managedObjectContext!)
let score = Score(entity: entity!, insertIntoManagedObjectContext: managedObjectContext)
println(score.points)
}
What prints in the console is this number: -5764607523034234879
I don't understand what I'm doing wrong. I looked elsewhere and perhaps I need to do something with NSNumber, but I'm unfamiliar with that class.
Shouldn't score.points be empty right now?
It looks like you've got your questions answered, but you CAN set a default value for attributes in NSManagedObject. Just not via code.
The default value is set when you save without setting a value, so you still need to deal with the uninitialized pointer reference to a NSNumber value until it is saved.