Swift Looping persistence of variables / scoping - swift

So this will be very abstract code however what i am doing it looping through an array of objects, calling a helper function to parse the complex object from json and insert it into an array. The issue I am having is that the object I am parsing into does not get cleared each time through the loop:
for item in parsedEvolutionList {
if let abilityKey: String = item.allKeys[0] as? String{
if let abilityData = item[abilityKey] as? NSDictionary{
if var newAbility = EvolutionAbilityStore.SharedInstance.retreaveEvolutionAbilityByKey(abilityKey) {
newAbility.parseFromJson(abilityData)
if evolutionList == nil {
evolutionList = []
}
evolutionList?.append(newAbility)
} else {
log.warning("Atempting to parse EvolutionAbility and \(item) was not found in the store")
}
} else {
log.warning("Atempting to parse EvolutionAbility and \(item) does not have data for ability to parse")
}
} else {
log.warning("Atempting to parse EvolutionAbility and \(item) is formatted incorectaly")
}
}
So the newAbility is an array, it contains the content of the first parsed object after the first loop, on second pass already has the content of the first pass and adds the content of the second pass, so on and so forth, then when adding to the evolution list it adds another pointer to the same object so all my objects in the evolutionList end up being objects to the same pointer. Why is newAbility persisting through iterations of the loop? that makes no sense to me with how that interferes with scoping

Oh oh oh, the creation of the object comes from a singleton, which does persist by designe ... has nothing to do with percectance of data in the loop!

Related

Append Values in NSSet To An Array of Type [String]

I am attempting to get the values from an NSSet in core data and append those values to an array of type String.
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
recipeStep.append(String(i))
}
}
}
entity.steps is the list of steps tied to a core data entity. This is an NSSet. I am trying to copy those values to an array of type String.
#State var recipeStep: [String]
When trying to do this in my for in loop, I receive the following error: No exact matches in call to initializer
If I remove the conversion of "I" to String, I receive the following error:
Cannot convert value of type NSSet.Element (aka Any) to expected argument type String
Any idea on how to get this to work?
NSSet is defined in Objective C, which didn't have generics. It's an untyped collection, so you don't statically know anything about its elements.
As you've noticed, your i variable isn't a String, it's an Any.
You're confusing type coercion ("casting") with type conversion. If i were a Double, you could call String(i) to invoke an initializer which takes a double, and processes into a String.
You tried something similar by calling String(i), where you're making the Swift compiler find an initializer on String with the signitiure init(_: Any).
There is no such initializer. And besides, that's not what you want. You don't want to create a new String from a different kind of value. You already have a string, it's just "hidden" behind an Any reference.
What you're looking for is to do a down-cast, from Any to String:
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
guard let step = i as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
recipeStep.append(i as String)
}
}
}
I'll warn you though, there are several issues/blemishes with this code:
You're using a for loop to do what is ultimately just a mapping operation
Your computation doesn't return its ouput, and instead indirectly achieves its goal through a side-effect on recipeStep
Your computation doesn't take a its input as a parameter, and instead indirectly achieves its goal through a side-effect on entity
i is conventionally expected to be an integer index of a for loop iterating over a sequence of numbers. Here it's an Any (a String at runtime)
Here's what I would suggest instead:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
guard let steps = entity.steps else { return [] }
return steps.map { step in
guard let stringStep = step as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
return step
}
}
Then in the rest of your code (and your tests), you can write self.recipeSteps = getRecipeSteps(from: myEntity). Elegant!
If you're certain that these entity.steps values can only ever be strings, then you can boil this down to a single map with a force-cast:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
entity.steps?.map { $0 as! String } ?? []
}
Just convert directly:
let set = Set(["1", "2", "3"])
let array = Array(set)
DDLog(set)//Set<String>)
DDLog(array)//[String]

swift check if variable of type Data is empty

I was going through docs of Data in swift from this link https://developer.apple.com/documentation/foundation/data
So basically what i am doing is, i am creating a variable of type data inside a function and later on putting some value in it and then returning this variable from the function like this:
var data = Data.init()
//call some function which returns a data variable on success
// and then put that variable inside this
// data = returnedData
return data
After returning this data from the function how can i check if it is empty.I couldn't find any method in the docs.
var data = Data.init()
//call some function which returns a data variable on success
// and then put that variable inside this
// data = returnedData
return data
In this case you variable data will never be nil because you are instatiating it. What you need to do is to check if it's empty or not.
if data.isEmpty {
print("data is empty")
}
You can check with the isEmpty property
if (data.isEmpty) {
print("Data is empty")
}
You just check
if data.count == 0 {
// empty
} else {
/// data not empty
// In fact, count is equivalent to bytes of data
}
Call the is isEmpty upon your return
if data.isEmpty {
// nothing returned
}

Why isn't this array of dictionaries being copied/ assigned?

I am trying to fill a dictionary in a completion block but the dictionary is always empty when I print it. I have tried just assigning it, and also using a loop to append each element individually...Any insight is appreciated! The original dictionary (returned in the completion block is populated)
the data is an array of dictionaries.
var friendsList : [[String : AnyObject]] = [[:]]
fetchFriends(username: username) { (listOfFriends) in
print("data set is:") <-----This is working, the data set is full
print(listOfFriends)
//self.friendsList = listOfFriends <---this code didn't work
for person in listOfFriends { <-------this code didn't work
for (key,value) in person {
let friend = [ key : value ]
self.friendsList.append(friend)
}
}
}
I changed the code inside the completion block to this (Don't see why this wouldn't work either...)
for person in listOfFriends {
print(person) <-----this prints the correct information
self.friendsList.append(person) <----this is working here
print(self.friendsList) <---prints as expected
}
}
Then when I print friendsList in viewDidLoad AFTER calling this function to fill it, it just prints [[:]] to the console
The reason you find it empty in viewDidLoad is that the call fetchFriends is asynchronous, so it's completed after viewDidLoad is called.
What happens is:
you call ferchFriends
controller is loaded and viewDidLoad is called
the property is still empty
fetchFriends completes and property array is filled properly

Retrieve original argument name in function

I have a function that takes a dictionary key (parsed from a JSON file) and returns the value if it can be cast as a string, or an empty string if it can't be cast or doesn't exist. The function itself works fine, but I want to have it log a warning if the value is invalid/missing and I'm not sure what the best way to do it is. I know I could just pass the text of the key as a second argument, but the function is going to be running many times so I was hoping for something more elegant. Is it possible to retrieve the original argument/key from within the function, or might there be some better way of doing this?
let someDict = [String:Any]()
func getStringObject (fromKey: Any?) -> String {
if let object = fromKey as? String {
return object
} else {
print("Invalid or missing value for ???, using default.") // want to print the original argument or at least the key string
return ""
}
}
let someString = getStringObject(someDict["someInvalidKey"]) // should print someDict["someInvalidKey"] or someInvalidKey

Create empty dictionary

I have a dictionary initialized
var dictionary = [String: [Double]]()
And I want to append
dictionary["Hello"].append(0.0)
but this gives me error "nil".
I tried to solve this by
extension Dictionary {
func appendish(key: String, value: Double) {
if self[key] == nil {
this give me error "Ambiguous reference to member 'subscript'"
}
}
}
How do I solve this? Been stuck at this for hours.
Subscripting a Dictionary with a key returns an optional of type Value?. In your case, dictionary["Hello"] returns a [Double]?. This optionality models the possibility that the dictionary doesn't contain a value for the given key.
If you're only dealing with static data, it's best to just use a literal expression:
let dictionary = [
"Hello": [0.0]
]
If you're using dynamic data, then there are several ways to do what you're trying to achieve, depending on how you would like to handle the nil case:
Use optional chaining
dictionary["Hello"]?.append(0.0)
This appends to the array stored for the key "Hello", but does nothing if there's no such value for that key.
This has the downside of making bugs harder to catch, because the consequence of the silent nil case might not be observed until long after this part of the code has run.
Use force unwrapping
dictionary["Hello"]!.append(0.0)
This appends to the array stored for the key "Hello", but crashes the program if there's no such value for that key.
Unlike optional chaining, this makes it easy to catch the point of failure at runtime. Of course, it comes with the drawback of crashing your program.
Handle the nil case in your own way
if var array = dictionary["Hello"] {
dictionary["Hello"] = nil // This line is a performance optimisation that removes the need for array to be copied
array.append(0.0)
dictionary["Hello"] = array
}
else {
print("No array for the key \"Hello\"") // Handle this as you wish
}
A dictionary look up returns an Optional value because the key might not exist, in which case it returns nil.
If your intention is to append to the array if it exists or create one if there isn't one yet, then the nil coalescing operator ?? comes in handy:
var dict = [String: [Double]]()
dict["hello"] = (dict["hello"] ?? []) + [1]
print(dict) // ["hello": [1.0]]
dict["hello"] = (dict["hello"] ?? []) + [2]
print(dict) // ["hello": [1.0, 2.0]]
This method does create a new array instead of mutating the existing one.
There are a few ways you can do this. Firstly, this is incorrect code:
dictionary["Hello"].append(0.0)
There might not be an array associated with the key "Hello", in which case nil will be returned by the subscript of the dictionary. So you need to unwrap it, either forced or un-forced:
dictionary["Hello"]?.append(0.0)
// or
dictionary["Hello"]!.append(0.0)
But I think what you really want to do is
if dictionary["Hello"] != nil {
dictionary["Hello"]!.append(0.0)
} else {
dictionary["Hello"] = [0.0]
}
After a long time of fiddling around with extensions and stuff (I am not familiar with this area of swift), I finally wrote the method appendish method that you were intended to write:
extension Dictionary where Value : RangeReplaceableCollection & ExpressibleByArrayLiteral, Value.Iterator.Element == Value.Element {
mutating func appendish(key: Key, value: Value.Element) {
if self[key] != nil {
self[key]!.append(value)
} else {
self[key] = [value]
}
}
}
// test
var dict = [String: [Double]]()
dict.appendish(key: "Hello", value: 0.0)