Iterate over static properties of a struct - ios

Is there a simple way to iterate over all of the properties of a struct? The approach for non-static properties that I am familiar with is using Mirror, but this returns an empty array when the struct contains only static properties. Here's an example of what I am trying to achieve:
struct Tree {
static let bark = "Bark"
static let roots = "Roots"
}
let treeParts = [String]()
// insert code here to make treeParts = ["Bark", "Roots"]

Since I also have an interest of how to do this I made the example below. Why not just create the struct with non static properties plus a static instance variable which makes the struct a singleton. The following code sample details an example use case for mapping values of a Person object to a JSON dictionary using the REST API's naming semantics. The property names of PersonJSONKeys have to match the property names of Person.
The code for the allProperties() function comes from How to loop over struct properties in Swift?. You can modify this function easily to only return the values of a structs properties.
struct PersonJSONKeys: PropertyLoopable {
static let instance: PersonJSONKeys = PersonJSONKeys()
private init() {}
let name = "name"
let firstName = "first_name"
let age = "age"
}
struct Person: PropertyLoopable {
let name = "Doe"
let firstName = "John"
let age = "18"
}
let person = Person()
let personProperties = person.allProperties()
var personJSON: [String:Any] = [:]
for jsonProperty in PersonJSONKeys.instance.allProperties() {
let propertyName = jsonProperty.key
let jsonKey = jsonProperty.value as! String
personJSON[jsonKey] = personProperties[propertyName]
}
Since the Struct is now a singleton all of its properties will be initialised only once and the thread safety is given by its static instance variable.

I was also looking for this and ended up using an enum. It's a good thing if you only have a list of static values (but enums also come with some limitations, e.g. no extensions...):
enum Tree: String, CaseIterable {
case bark = "Bark"
case roots = "Roots"
}
let allTrees = Tree.allCases.map{ $0.rawValue }
print(allTrees) // => ["Bark", "Roots"]

Related

can a JSON be deserialized into an existing object?

We have some classes that conform to the codable interface.
Now we have a requirement similar to deep copy, but we need to pass the value into an existing object.
For example:
class A: Codable{
var a1: Int
var a2: String
....aN: Codable
enum CodingKeys: String, CodingKey {
case a1
case a2
case a3
...
case aN
}
}
let a = A()
We get a new JSON, and we can easily convert it into A object through decode.
But now we don't want the a's memory address to change. We want its ARC to remain in its current state.
That is, when deserializing, we want to write the content directly to the object a.
We hope to be more automatic. We can traverse the processing according to allkeys. We don't have to write each one.
a[keyPath: \.a1] = newObj[keyPath: \.a1]
a[keyPath: \.a2] = newObj[keyPath: \.a2]
...
a[keyPath: \.aN] = newObj[keyPath: \.aN]
or
a.a1 = newObj.a1
a.a2 = newObj.a2
...
a.aN = newObj.aN
Deserializing will always result in creating a new object. So, I guess, the most viable (if not the only) option is to manually compare all properties and update the existing object accordingly.
You can write helper methods in your existing classes for convenience. Probably, something like this:
import Foundation
class A: Codable {
var a1: String
init(a1: String) {
self.a1 = a1
}
}
extension A {
func updateWith(_ another: A) {
if a1 != another.a1 {
a1 = another.a1
}
}
}
let a = A(a1: "a1")
let anotherString = "{\"a1\":\"new a1\"}"
if let anotherData = anotherString.data(using: .utf8),
let another = try? JSONDecoder().decode(A.self, from: anotherData) {
a.updateWith(another)
}
print(a.a1) // "new a1"

How to iterate two arrays both containing custom classes simultaneously

lets say you have the classes:
class Artwork {
var title = ""
var location =""
var author = ""
}
class GroupedArtworks {
var location = ""
var artworks = [Artworks]()
}
then you have an variable that contains several objects of the class "Artwork":
var arts = [artwork1, artwork2...]
How would I group the objects of the class "Artwork" by "location"?
I would like to end up with another variable containing objects of the class "GroupedArtworks"
and for each object, in the attribute "artworks" have all the objects that have the same "location"
so something like:
var orderedArtworks = [groupedartworks1, groupedartworks2...]
No doubt for loops are involved here.
The solution is super easy with Swift's Dictionary init(grouping:by:)
let artworks: [Artwork] = []
// dictionary type will be [String: [Artwork]]
let dictionary = Dictionary(grouping: artworks, by: \.location)
// now we can easy iterate over (key: String, value: [Artwork]) pairs
let groups: [GroupedArtworks] = dictionary.map { location, artworks in
GroupedArtworks(location: location, artworks: artworks)
}
// or simply
let groups = dictionary.map(GroupedArtworks.init)
// Swift will infer the types of dictionary and the init
but you will need to add this init to your GroupedArtworks
class GroupedArtworks {
let location: String
let artworks: [Artwork]
init(location: String, artworks: [Artwork]) {
self.location = location
self.artworks = artworks
}
}
Documentation
As someone correctly pointed out in the comments, since dicts are unordered collections, your array of GroupedArtworks will be unordered one as-well. But this should not be a problem since you can easily sort it by lets say location.
let groups = dictionary.map(GroupedArtworks.init).sorted(by: \.location)

What's the difference between : and = in swift

Sorry if the title is rather confusing, but I'm curious to know the difference between these two lines:
var title = String()
var title: String
Is one being initialized and one only be declared? Which is more correct?
For example, if I have a struct should I use one of the other?
So the reason I ask this is because I'm learning about how to grab some JSON from a url and then display it in my app. One of the new ways of doing so is using Decodable. So, I have a struct in a model class like so:
struct Videos: Decodable {
var title = String()
var number_of_views : Int
var thumbnail_image_name: String
var channel: Channel
var duration: Int
}
In another class I have this:
URLSession.shared.dataTask(with: url){(data,response,error) in
if(error != nil){
print(error!)
return
}
guard let data = data else { return }
do{
self.Videos2 = try JSONDecoder().decode([Videos].self, from: data)
//self.collectionView?.reloadData()
}catch let jsonErr{
print(jsonErr)
}
}.resume()
So, should I declare or initialize the variables in my struct? I'm assuming I should just declare them like so:
var title: String?
Would that be the correct syntax in my struct?
UPDATE:
I understand this question was more broad then I originally proposed it to be. I'm sorry about that, but thank you so much for all your great answers that clarified a lot up for me.
The difference is that : defines the type of your variable, whereas = assigns an actual value to the variable.
So:
var title = String()
This calls the initializer of the String type, creating a new String instance. It then assigns this value to title. The type of title is inferred to be String because you're assigning an object of type String to it; however, you could also write this line explicitly as:
var title: String = String()
This would mean you are declaring a title variable of type String, and assigning a new String to it.
var title: String
This simply says you're defining a variable of type String. However, you are not assigning a value to it. You will need to assign something to this variable before you use it, or you will get a compile error (and if this is a property rather than just a variable, you'll need to assign it before you get to the end of your type's init() method, unless it's optional with ? after it, in which case it gets implicitly initialized to nil).
EDIT: For your example, I'd probably declare all the variables using let and :, assuming that your JSON provides values for all of those properties. The initializer generated by Decodable should then set all the properties when you create the object. So, something like:
struct Videos: Decodable {
let title: String
let number_of_views : Int
let thumbnail_image_name: String
let channel: Int
let duration: Int
}
This initializes a value
var title = String()
This declares a value but does not initialize it
var title: String
If you attempt to use the latter, such as print(title), you will get a compiler error stating Variable 'title' used before being initialized
It does not matter whether the value is a class or a struct.
The = operator is the assignment operator, it assigns a value to the object on the left of the =
Typically, class or struct properties are declared but not initialized until the init() is called. A simple class might be
class MyClass {
let myProperty: String
init(aString: String) {
self.myProperty = aString
}
}
Whereas inside the scope of a function you may declare a local variable that only lives inside the scope of the function.
func doSomethingToAString(aString: String) -> String {
let extraString = "Something"
let amendedString = aString + extraString
return amendedString
}
In your specific example, the struct synthesizes an initializer that will allow you to initialize the struct with all the values needed to fill your properties. The initializer generated by Decodable should then set all the properties when you create a Videos struct, you will do it something like:
let aVideos = Videos(title: "My Title", number_of_views: 0, thumbnail_image_name: "ImageName", channel: Channel(), duration: 10)
Is one being initialized and one only be declared?
Yes, meaning that the declared cannot be used. If you tried to set a value for it, you would get a compile-time error:
variable 'title' passed by reference before being initialized
Which is more correct?
There is no rule of thumb to determine which is more correct, that would be depends on is there a need to initialize title directly.
On another hand, when it comes to declare properties for a class, saying var title = String() means that you are give title an initial value ("") which means that you are able to create an instance of this class directly, example:
class Foo {
var title = String()
}
let myFoo = Foo()
However, if title declared as var title: String, you will have to implement the init for Foo:
class Foo {
var title: String
init(title: String) {
self.title = title
}
}
let myFoo = Foo(title: "")
Also, you have an option to declare it as lazy:
lazy var title = String()
which means:
A lazy stored property is a property whose initial value is not
calculated until the first time it is used. You indicate a lazy stored
property by writing the lazy modifier before its declaration.
Properties - Lazy Stored Properties

Swift: How to access variable element of an enum?

For hours I've been struggeling with getting an variable element of an enum.
The "Swifticons" - pod provides me with the following enum:
public enum WeatherType: Int {
static var count: Int {
return weatherIcons.count
}
public var text: String? {
return weatherIcons[rawValue]
}
case alien, barometer, celsius, owm300, owm301, owm302, and200moreOfTheseNames
}
private let weatherIcons = ["\u{f075}", "\u{f079}", and202moreOfTheseFontCharacters]
From an external API (openWeatherMap.org) I just get an weather code (let's say "300") - and I want to access Icon "owm300".
But how do I access this element of the enum without knowing the rawValue (which would be - say - 198)?
Here's the plan:
We need to enumerate all of the enum cases. We'll do that by iterating over raw values (luckily, WeatherType is backed by Int).
We will store lazily initialized dictionary that maps String to WeatherType.
And finally, we declare a static function that returns an optional WeatherType? because we can encounter an unknown value.
Here's the code:
extension WeatherType {
// just some little convenience
private typealias W = WeatherType
// 1. define the sequence of all cases
private static func allCases() -> AnySequence<W> {
return AnySequence { () -> AnyIterator<W> in
var raw = 0
return AnyIterator {
// Iterates while raw value can be converted to an enum case
if let next = W(rawValue: raw) {
raw += 1
return next
}
return nil
}
}
}
// 2. Static properties are lazy so we'll use them to store the dictionary with String to WeatherType mapping
private static let typeMap = W.allCases().reduce([String: W]()) { acc, next in
var acc = acc
acc[String(describing: next)] = next
return acc
}
// 3. Declare the mapping function
static func from(string: String) -> WeatherType? {
return W.typeMap[string]
}
}
Here's a little test:
let str = "301"
let type = WeatherType.from(string: "owm\(str)")
print(type == .owm301)
One of the easiest way I can think of is create some kind of mapping dictionary, where you would keep track of weather response code and WeatherType that it maps to like so,
let weatherCodeMapping: [Int: WeatherType] = [300: .owm300,
301: .owm301,
302: .owm302]
With this in place, you dont need to know any specific rawValue, you can simply get code by,
let weatherType = weatherCodeMapping[weatherCode]
And then create some other mapping for your image based on the weatherType.
let weatherIcon = weatherIconMapping[weatherType]
or create a single mapping directly from weather code to icon.
Swift doesn't currently have enumerable sequences of enum cases. One option that you have is to copy the list of icon names, then search for your icon's name, and use that index as the enum's rawValue:
let weatherIcons = [...]
let iconName = "owm300"
let possibleIconIndex = weatherIcons.index {
$0.caseInsensitiveCompare(iconName) == .orderedSame
}
if let iconIndex = possibleIconIndex {
let weatherIcon = WeatherIcon(rawValue: iconIndex)!
// ...
} else {
// graceful fallback for when the weather icon is missing
}
Of course, you need to figure out your own mapping between the data you get from the service and enum names, but that could be as simple as "owm\(weatherCode)".
When Swift 4.2 lands, you will be able to make your enums conform to a new protocol called CaseIterable. Enums that conform to it get a synthesized implementation of an allCases static variable. You will then be able to use that enumeration to build a string-to-enum dictionary automatically:
let nameToEnum = WeatherIcon.allCases.map { (String($0), $0) }
let mapping = Dictionary(uniqueKeysWithValues: nameToEnum)
That will however require WeatherIcon to be declared with the CaseEnumerable conformance, as adding it with an extension has no effect.

How can I find the type of a property dynamically in swift (Reflection/Mirror)?

So let's say I have a class like this:
class Employee: NSObject {
var id: String?
var someArray: [Employee]?
}
I use reflection to get the property names:
let employee = Employee()
let mirror = Mirror(reflecting: employee)
propertyNames = mirror.children.flatMap { $0.label }
// ["businessUnitId", "someArray"]
so far so good! Now i need to be able to figure out the type of each of these properties, so if I do the employee.valueForKey("someArray"), it won't work because it only gives me the AnyObject type. What would be the best way to do this? Specially for the array, I need to be able to dynamically tell that the array contains type of Employee.
You don't need to inherit from NSObject (unless you have a good reason to).
class Employee {
var id: String?
var someArray: [Employee]?
}
let employee = Employee()
for property in Mirror(reflecting: employee).children {
print("name: \(property.label) type: \(type(of: property.value))")
}
Output
name: Optional("id") type: Optional<String>
name: Optional("someArray") type: Optional<Array<Employee>>
This also works with Structs
If you are inheriting from NSObject, you can use some of the methods provided by NSCoding and the Objective-C runtime:
let employee = Employee()
employee.someArray = []
employee.valueForKey("someArray")?.classForCoder // NSArray.Type
employee.valueForKey("someArray")?.classForKeyedArchiver // NSArray.Type
employee.valueForKey("someArray")?.superclass // _SwiftNativeNSArrayWithContiguousStorage.Type
employee.valueForKey("someArray")!.dynamicType

Resources