Is there a more efficient way to retrieve all the names/titles of attributes of a NSManagedObject than this:
func getAllAttributeTitles(_ myStatSheet:StatSheet) -> Array<String> {
let dictAttributes = myStatSheet.entity.attributesByName
var arrAttributeTitles:Array<String> = []
for (key, _) in dictAttributes {
arrAttributeTitles.append(key)
}
return arrAttributeTitles
}
As I mentioned, what you've got is the right way to do it. There are other ways but I wasn't at a Mac earlier and couldn't try them out.
A more "Swift-y" way to get the array would be something like
let arrAttributeTitles = myStatSheet.entity.attributesByName.enumerated().map { $0.element.key }
This won't be any more efficient, since it's really doing the same things, but it might be more what you were thinking of when you asked. It's still getting attributesByName and iterating over the result to get strings naming the attributes.
It might be worth noting that the argument type on your method could be NSManagedObject instead of StatSheet, since the code will work for any managed object of any entity type.
Related
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
}
I have an array of data that doesn't need to be a managed List, meaning I don't need Realm to create a new model for the items with links and the ability to query on the items, etc. I just want a simple array, typically of primitives that don't inherit from Object anyway, that will be persisted with my main object.
The only solution I can think of is to use NSData and NSKeyedArchiver/NSKeyedUnarchiver. Is that the best/only way to do this? Should I just use List even if I don't think I'll need it — what's the best practice for this situation?
Realm doesn't support arrays of primitives (although that functionality is coming soon), so the most straightforward solution is to use a List filled with model objects that just wrap your primitives. There's nothing wrong with archiving to and from NSData and storing the data in your Realm model, though, if you feel that better suits your particular use case.
Here's how I decided to deal with this:
var instructions: [String] {
get {
if _instructions == nil {
_instructions = NSKeyedUnarchiver.unarchiveObject(with: instructionsData) as? [String]
}
return _instructions!
}
set {
instructionsData = NSKeyedArchiver.archivedData(withRootObject: newValue)
_instructions = newValue
}
}
fileprivate var _instructions: [String]? = nil
dynamic var instructionsData = Data()
override static func ignoredProperties() -> [String] {
return ["instructions", "_instructions"]
}
That way I can use the array as I normally would and still have it persisted in a simple way (without having to create an actual List and having to manage a bunch of new models/objects).
This question has been asked and answered for a couple other coding languages, but I think I may have a unique problem anyway. So, I want to duplicate a three dimensional array (filled with arbitrary objects). I believe I found that this:
var duplicateArray = originalArray
Does not work, since, for whatever reason, they thought it would a nice safety measure to have this create a duplicate array, but filled with pointers as sub-arrays instead of duplicating the sub-arrays as well. This seems like a strange design choice, since if duplicateArray and originalArray were one-dimensional, this would work as intended. Anyway, so I tried this (where object is some arbitrary object):
var duplicateArray = [[[object]]]()
for x in 0..<originalArray.count {
var tempArrYZ = [[object]]()
for y in 0..<originalArray[x].count {
var tempArrZ = [object]()
for z in 0..<originalArray[x][y].count {
let copiedObj = originalArray[x][y][z]
tempArrZ.append(copiedObj)
}
tempArrYZ.append(tempArrZ)
}
duplicateArray.append(tempArrYZ)
}
This still does not work; all the values in duplicateArray will act like a pointer for their values in originalArray. Perhaps someone has a simple way of deeply duplicating multidimensional arrays, or perhaps someone can find my error?
EDIT: How is this a duplicate of that other question? I'm asking specifically how to "deeply" duplicate. The question that's being referred to nebulously asked about duplicating arrays.
var duplicateArray = originalArray
Would work if the objects are not of reference type. However, for the reference type you need to actually create the copy of the object with copy. Your original code was pretty close.
var duplicateArray = [[[object]]]()
for x in 0..<originalArray.count {
var tempArrYZ = [[object]]()
for y in 0..<originalArray[x].count {
var tempArrZ = [object]()
for z in 0..<originalArray[x][y].count {
let copiedObj = originalArray[x][y][z].copy()
tempArrZ.append(copiedObj)
}
tempArrYZ.append(tempArrZ)
}
duplicateArray.append(tempArrYZ)
}
As already stated, your problem isn't really the copying of the array, it's the copying of Objects. Arrays, like all structs, are copied by value. Objects are copied by reference.
When you copy an array of objects, it's a brand new array with brand new references to the contained objects. Your code is simply creating additional references to the same objects then organizing them in a similar fashion.
Anyway, here's my simpler/functional implementation for copying arrays:
func copyArrayWithObjects <T: Copying>(items: [T]) -> [T]{
return items.map { $0.copy() }
}
func copy2DArrayWithObjects <T: Copying>(items: [[T]]) -> [[T]] {
return items.map(copyObjectsInArray)
}
func copy3DArrayWithObjects<T: Copying>(items: [[[T]]]) -> [[[T]]] {
return items.map(copy2DObjectInArray)
}
Then you can simply do this:
let copiedArray = copy3DArrayWithObjects(originalArray)
Theoretically I think it's possible to create a function to do this for an n-dimension array, but I haven't found a solution yet.
I think it would be best to write an extension on Array that adds conformance to NSCopying, which recursively copies the elements. This solution would be very elegant because it could scale to any number of dimmensions.
Swift arrays are value types so the snippet you provided is fine.
var duplicateArray = originalArray
See this example in a Playground as proof:
var array = [[["test"]]]
var newarray = array
// print different memory addresses
print(unsafeAddressOf(array[0][0][0])) // 0x00007ff7a302a760
print(unsafeAddressOf(newarray[0][0][0])) // 0x00007ff7a33000e0
If you use NSArray or reference types inside the Swift array, then they will no longer copy implicitly and will be treated with the same address - this can also be proved in the Playground. You would need to call copy() explicitly on reference types.
Can I merge a for-in and if-let in one statement?
for item in array {
if let f = item as? NSDictionary {
result.addObject(newFile(f))
}
}
array is made by a JSON, so I don't know if each item is a NSDictionary or not. I have to check.
I was looking for something like this:
for item as? NSDictionary in array {
// code
}
Like Python or Ruby.
#nickfalk is on the right track, but we can do better. His result unfortunately returns [AnyObject], which you can't then call newFile with (I assume). But that's ok, we can get the rest of the way pretty easily.
What you want is partial map. That is to say, you want to map some (but possibly not all) of the elements of one list to another list (from AnyObject to File, if we can). So there must be some rule for choosing, and some rule for mapping. Optional let's us combine those. Let's call the function that does that f. Then its type is:
f: T->U?
So there's some magic function that will possibly convert T to U. We want to map with that. Sounds easy:
extension Array {
func partialMap<U>(f: T->U?) -> [U] {
var result = [U]()
for x in self {
if let u = f(x) {
result.append(u)
}
}
return result
}
}
So now we've hidden all the nasty mutation and var and whatnot down deep where we don't have to look at it. We have a function that takes a mapping function from "something" to "maybe something else" and returns a list of "something elses that we could map."
Now everything is nice and immutable and reusable:
let result = array.partialMap { ($0 as? NSDictionary).map(newFile) }
Whoa there. What's that map in the middle? Well, as? returns NSDictionary?. When you map an optional, then if the optional is None, it returns None, otherwise it applies the function to the value and wraps it in Some. So this whole thing takes AnyObject and returns File? just like we wanted. One partialMap later we have our answer.
I would probably just go for something like:
let result = array.filter() { $0 is NSDictionary }
If you need result to be an NSDictionary array, you can just cast it:
let result = array.filter() { $0 is NSDictionary } as [NSDictionary]
If your goal is to reduce an NSArray to an array only containing NSDictionary filter is a very powerful tool. Create the appropriate filtering function:
func filterForNSDictionary(object: AnyObject) -> Bool{
return object.isKindOfClass(NSDictionary)
}
Then simply pass in you array and function to the filter function
let result = filter(array, filterForNSDictionary)
As #RobNapier points out my solution above will end up with a result array being of the type [AnyObject] this can of course easily be remedied:
let result = filter(array, filterForNSDictionary) as [NSDictionary]
This could be considered risky, if you force the array to be of the wrong type. as [NSString] (for instance9 would most likely blow up in your face down the line...
Rob's solution being pure awesome cleverness of course and #MattGibson delivering the perfect shorthand, while exposing me as an absolute beginner in this field.
NSDictionary in this example :
var example: [Int:AnyObject] = [1:["A":"val","B":[1:["B1":"val"]]]]
how to update "B1" value OR should i not use NSDictionary for this complex value?
Thank you
I would suggest either of the following two options, but not a mix:
Find a way to structure you data, so you can use the power of the Swift Dictionary class.
Use a NSDictionary and ignore Swift's awesomeness.
Almost needless to say: It's so much better to go for the first option, if your data allows it. Using AnyObjects in a Dictionary isn't a good way. Note that you could also use structs to structure your data.
You cannot update this structure at all, because it implicitly has an immutable type. As a test:
var example: [Int: AnyObject] = [1:["A":"val","B":[1:["B1":"val"]]]]
if let dict=example[1] as? NSMutableDictionary {
"Mutable dictionary"
} else if let dict=example[1] as? NSDictionary {
"Immutable dictionary" // < THIS will be printed as a result
} else {
"Other"
}
Your model is pretty confusing - I would recommend classes/types that wrap your data and give you a better idea of what you're dealing with at each level.
It is possible to create this structure in such a way that the layered dictionaries are mutable, but it's a pain, and neither easy to read nor deal with.