SWIFT : Update Value NSDictionary - swift

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.

Related

Get all attribute names of Core Data entity; Swift

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.

What Data Structure should I use for this particular case?

I have a dictionary, something like this
var dict = [String : [String]]()
The functionality I want to achieve is that, I have a hashtable which I can quick get the list of data from.
In my code, I use a dictionary and an array.
I am not very good with algorithem and data structure, so I am wondering if there is any better data structure that is suitable for something like this?
Use:
var dict = [String : [String]]()
Swift already has built in search algorithms that allow you you to retrieve data inside of your dictionary with simple subscript syntax like so
dict["element"]
You will use it in this way -
Declaration:
var dict: [String: [String]] = [:]
Initialise:
dict["element"] = myArray

How to access CFDictionary in Swift 3?

I need to read and write some data from CFDictionary instances (to read and update EXIF data in photos). For the life of me, I cannot figure out how to do this in Swift 3. The signature for the call I want is:
func CFDictionaryGetValue(CFDictionary!, UnsafeRawPointer!)
How the heck do I convert my key (a string) to an UnsafeRawPointer so I can pass it to this call?
If you don't have to deal with other Core Foundation functions expecting an CFDictionary, you can simplify it by converting to Swift native Dictionary:
if let dict = cfDict as? [String: AnyObject] {
print(dict["key"])
}
Be careful converting a CFDictionary to a Swift native dictionary. The bridging is actually quite expensive as I just found out in my own code (yay for profiling!), so if it's being called quite a lot (as it was for me) this can become a big issue.
Remember that CFDictionary is toll-free bridged with NSDictionary. So, the fastest thing you can do looks more like this:
let cfDictionary: CFDictionary = <code that returns CFDictionary>
if let someValue = (cfDictionary as NSDictionary)["some key"] as? TargetType {
// do stuff with someValue
}
What about something like:
var key = "myKey"
let value = withUnsafePointer(to: &key){ upKey in
return CFDictionaryGetValue(myCFDictionary, upKey)
}
You can write something like this:
let key = "some key" as NSString
if let rawResult = CFDictionaryGetValue(cfDictionary, Unmanaged.passUnretained(key).toOpaque()) {
let result = Unmanaged<AnyObject>.fromOpaque(rawResult).takeUnretainedValue()
print(result)
}
But I guess you would not like to write such thing at any time you retrieve some data from that CFDictionary. You better convert it to Swift Dictionary as suggested in Code Different's answer.

How to iterate over an array and check element type at the same time in swift? [duplicate]

This question already has answers here:
For-in loop and type casting only for objects which match type
(2 answers)
Closed 6 years ago.
I'd like to know if it is possible to iterate over an array is swift and check if the current element has a certain type.
A use case would be : I have an array with NSDictionarys and NSArrays and I ony want to take care of the NSDictionarys.
I'd like to do this without the condition inside the loop :
for entity in array
{
if entity is NSDictionary
{
// Do something
}
}
I'm looking for something like :
for (entity in array) as? NSDictionary
{
// Do something
}
Of course I know that what I have just written doesn't work, but I guess you can get the idea of what I'm asking for.
Any idea would be really appreciated !
Solution 1
You can filter your array
let things = [NSDictionary(), NSArray(), NSDictionary(), NSArray()]
let dicts = things.flatMap { $0 as? NSDictionary }
Now dicts is defined as [NSDictionary] an contains only the 2 dictionaries.
Solution 2
You can also perform a for loop only on the values that are dictionaries
let things = [NSDictionary(), NSArray(), NSDictionary(), NSArray()]
for dict in things.flatMap( { $0 as? NSDictionary }) {
}
Using filter with the type check operator is
As an alternative to the flatMap solutions, you may use filter along with the type check operator is:
let arr: [AnyObject] = [NSDictionary(), ["foo", "bar"], NSDictionary(), "foo"]
for dict in arr.filter({ $0 is NSDictionary }) {
// do something with dictionary
}
Use the type check operator (is) to check whether an instance is of
a certain subclass type. The type check operator returns true if the
instance is of that subclass type and false if it is not.
From Swift Language Guide - Type Casting. Since you don't have to worry about subclass matches (you know the elements to be either NSDictionary or NSArray) the is filter approach can be an appropriate alternative here.
Note that when using the flatMap and type cast operator (as), the iterate element in the for loop (dict) will be of type NSDictionary, whereas the filter approach above will perform no downcasting, hence preserving the NSDictionary in its wrapped form, as AnyObject. Probably the flatMap approach is to prefer here.
If you don't care for elements to have the same indexes as in original array, you can flatMap to the required type.
for element in array.flatMap({ $0 as? NSDictionary }) {
// do something
}

Mixing for-in and if-let in Swift

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.