Accessing swift classess - ios

Is there a way to access the variable in a class using a String? E.g. I have the following struct Person.swift:
struct Person: Codable {
let name: String?
let id: String?
}
Can I access the variable name and change the variable using a some sort of string-reflection method, like Java, instead of a call to the Person.name = "" method?

You can access props as strings using KVC
The only downside is It's originally from Objects c so you can not use structs, and your props have to be Objective C dynamic types:
class Person: NSObject, Codable {
#objc dynamic var name: String = ""
}
let person = Person.init()
person.name = "Mat" // Name value "Mat"
person.setValue("Josh", forKey: "name") // Name value "Josh"

Related

Dynamic key property wrapper

I have a simple property wrapper written to easily get and set user preferences in the UserDefaults.
#propertyWrapper
struct UserDefault<Value> {
let key: String
let defaultValue: Value
var wrappedValue: Value {
get { UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue }
set { UserDefaults.standard.set(newValue, forKey: key) }
}
}
// Example usage
#UserDefault(key: "com.example.flag", defaultValue: false)
var trackingEnabled: Bool
Now this works fine for static preferences, but say I were to support multiple users and each of them would be able to store their settings.
struct Person {
let id: UUID
let name: String
#UserDefault(key: "com.example.person.\(id)", defaultValue: false)
var trackingEnabled: Bool
}
This gives me the warning Cannot use instance member 'id' within property initializer; property initializers run before 'self' is available. Is there a way to solve this so I can easily use this property wrapper with a dynamic key?
In order to use the value of id in the initialization of trackingEnabled, you can use a custom initializer for your struct:
struct Person {
let id: UUID
let name: String
var trackingEnabled: UserDefault<Bool>
init(id: UUID, name: String) {
self.id = id
self.name = name
trackingEnabled = UserDefault(key: "com.example.person.\(id)", defaultValue: false)
}
}
Or, you can define trackingEnabled as lazy, so that it has access to the id parameter by the time it's initialized:
struct Person {
let id: UUID
let name: String
lazy var trackingEnabled = UserDefault(key: "com.example.person.\(id)", defaultValue: false)
}
Neither solution uses the #propertyWrapper aspect of UserDefault, but it doesn't seem necessary here -- it still results in the same type/functionality.

Swift: JSONDecoder won't decode when struct has default array initialization

I'm decoding a simple structure and ran into unexpected behavior.
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient]
}
do {
let jsonData = SOME STING WITH VALID JSON
let contacts = try JSONDecoder().decode(Contacts.self, from: jsonData)
}
catch {
}
This decodes just fine. If I do this simple change to the structure, it no longer decodes.
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient] = []
}
Now JSONDecoder won't decode the exact same string. Why does the default initialization of the array cause the decoder to stop working?
Change your code to read:
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
var recipients: [Recipient] = []
}
The issue (as described in the comments above) is that your initial declaration of the variable is immutable (meaning it cannot be changed after it is initialized). So, if you declare the initial value for a let as [] then you cannot subsequently change it. By changing let to var you are declaring the variable as mutable (meaning it can be changed) which allows you to both supply an initial value as well as change the value later.
Your let here isn't a "default initialization." It's defining recipients as a constant with a specific, compile-time, value. If you want a default value, then you can create a initializer for that if you like:
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient]
init(recipients: [Recipient] = []) { self.recipients = recipients }
}
Generally speaking on this kind of very simple data type, I'd make recipients a var instead (see John Ayers's answer for example). It's much more flexible, while maintaining all the advantages of a value type. But in cases where you want a let constant with a default (rather than a single value), you need an init.

Optional list of NSData in Realm - Swift

I need to save a list of images as NSData in Realm. I tried using Realm optional but realmOptional<NSdata> can't be used because realmOptional does not conform to type NSDate.
Is there a way to do it?
Edit: Basically all I want is to be able to store a list of NSData but optional
something like:
#objc dynamic var photos: List<NSData>?
Solution for optional List types when using Decodable
In my case I needed an optional List because I'm decoding json into Realm objects, and it's possible that the property might not exist in the json data. The typical workaround is to manually decode and use decodeIfPresent(). This isn't ideal because it requires boilerplate code in the model:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
// etc
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.vehicles = try container.decodeIfPresent(List<Vehicle>.self, forKey: .vehicles) ?? List<Vehicle>()
self.name = try container.decode(String.self, forKey: .name)
// etc
}
}
However, we can avoid the boilerplate by extending KeyedDecodingContainer with an overload for List types. This will force all lists to be decoded using decodeIfPresent():
Realm v5:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
//etc
}
class Vehicle: Object, Decodable {
var drivers = List<Driver>()
var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any List<> is decoded
func decode<T: Decodable>(_ type: List<T>.Type, forKey key: Key) throws -> List<T> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? List<T>()
}
}
Realm v10+:
class Driver: Object, Decodable {
#Persisted var vehicles: List<Vehicle>
#Persisted var name: String = ""
// etc
}
class Vehicle: Object, Decodable {
#Persisted var drivers: List<Driver>
#Persisted var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any #Persisted List<> is decoded
func decode<T: Decodable>(_ type: Persisted<List<T>>.Type, forKey key: Key) throws -> Persisted<List<T>> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? Persisted<List<T>>(wrappedValue: List<T>())
}
}
Edit: Clarified code examples, fixed typo
The List cannot be optional, but the Objects in the list can be optional, you have to declare it like:
#Persisted var value: List<Type?>
Here is the link with the Supported data types
https://www.mongodb.com/docs/realm/sdk/swift/data-types/supported-property-types/
according to https://realm.io/docs/swift/latest/#property-cheatsheet you can not define optional lists in realm

Cast from '(key: String, value: [Dates])' to unrelated type 'Dates' always fails

As my objective is to use the MVVM architecture,I have configured my Model class fine, but now having troubles with initialising my ViewModel class. It gives me an error saying "Cast from '(key: String, value: [Any])' to unrelated type 'Dates' always fails". When I run it, it crashes. Any help would much appreciate.
View Model Class as follow
struct JobsViewModel {
private let title : String?
private let imageArray : [Formats]?
private let category : String?
private let shifts : [ShitDetails]?
private let distance : String?
//Dependency injection
init(mainData:Dates) {
self.title = mainData.title
self.imageArray = mainData.client?.photos
self.shifts = mainData.shifts
self.category = mainData.job_category?.description
self.distance = mainData.distance
}
}
Model Class as follow
public struct Schedule: Codable {
public let data : [String:[Dates]]
}
public struct Dates: Codable {
public let title: String?
public let distance: String?
public let client: Images?
public let shifts: [ShitDetails]?
public let job_category:JobCategory?
}
On success of my API call I'm trying to initailize it as bellow and its where it crash.
var jobsViewModel = [JobsViewModel]() //jobsViewModel is a instance variable
Network.shared.retrieveHotelDetails(successBlock: { (results) in
let mainData = results as? Schedule
self.jobsViewModel = mainData?.data.map({return JobsViewModel(mainData: $0 as! Dates)}) ?? []
}
{"2018-06-07": [
{
"someKey": "Test1",
"someKey": "Test1"
}
],
"2018-06-06": [
{
"someKey": "Test1",
"someKey": "Test1"
}
]}
So you're trying to map from that (mainData?.data is a [String:[Dates]] Dictionary) to a "JobsViewModel" Array.
Your main issue is that you essentially try to map from something that contains multiple arrays to one array. If you want to do that, flatMap will be your best option.
Just like this:
self.jobsViewModel = mainData.data.flatMap({ $0.value }).map({ JobsViewModel(mainData: $0 )})
This first "flat maps" all of the Dates Arrays in your dictionary into one large dictionary that contains them all and then creates (via "map") a JobsViewModel for each of them by using each Dates object in the JobsViewModel initializer.

Default initialiser in protocol requires unwanted mutable properties

In Swift 3: Imagine you want your models to be value types (struct) through out your app. But you would also like persistence of said models using Core Data/Realm, which requires you to create classes. Then you can convert your structs to classes and vice verse using JSON (which would require structs and classes to both support JSON deserialization and serialization).
Wouldn't it be neat if you don't have to write JSON deserialization (and analogously for serialization, but I'm focusing on deserialization here) in two places, but use put deserialization in a protocol, that both your struct and class uses.
Using structs we want our JSON model to have immutable fields, thus all properites being let constants. But using a protocol implementation of the deserialization does not allow for this AFAIK.
The code example below works, but it is ugly, because of all unwanted requirements (UR) marked in comments in the code.
struct JSON {
let json: [String: Any]
func value<Value>(_ key: String) throws -> Value {
guard let value = json[key] as? Value else { throw NSError() }
return value
}
}
protocol JSONDeserializable {
init(json: JSON) throws
}
protocol UserModel: JSONDeserializable {
var username: String { get set } // Unwanted requirement (UR) #1: property needs "set" so that it can be initialized within protocol
init() // UR2: needs empty init, because of default implementation of `init(json: JSON)` in `extension UserModel`
}
extension UserModel {
init(json: JSON) throws {
self.init() // UR3: Needs to call this otherwise compilation error: `'self' used before chaining to another self.init requirement`
username = try json.value("username")
}
}
struct UserStruct: UserModel {
// UR4: property cannot be `let`, beause of `set` in protocol.
var username: String = "" // UR5: Property have to have default value because of it being a empty init
init() {}
}
final class UserClass: NSObject, UserModel {
// UR6: analogue with UR4
var username: String = "" // UR7: analogue with UR5
}
let json: JSON = JSON(json: ["username": "Sajjon"])
let u1 = try UserStruct(json: json)
let u2 = try UserClass(json: json)
print(u1.username) // prints "Sajjon"
print(u2.username) // prints "Sajjon"
Is there another way of achieving this, with a lower amount of unwanted requirements? Or an optimal solution with zero UR? 🙄
Thanks to what #hamish pointed out, the best solution would be (where struct JSON and protocol JSONDeserializable remains the same as in the question). This is not a perfect solution since you have to implement the initializer of the class. The neat part is that you don't have to implement any initializer for the struct, since it has one implicitly.
protocol UserModel: JSONDeserializable {
var username: String { get }
var firstname: String { get }
var country: String { get }
init(
username: String,
firstname: String,
country: String
)
}
extension UserModel {
init(json: JSON) throws {
self.init(
username: try json.value("username"),
firstname: try json.value("firstname"),
country: try json.value("country")
)
}
}
struct UserStruct: UserModel {
let username: String
let firstname: String
let country: String
// struct has default initializer
}
final class UserClass: UserModel {
let username: String
let firstname: String
let country: String
init(
username: String,
firstname: String,
country: String
) {
self.username = username
self.firstname = firstname
self.country = country
}
}
let json: JSON = JSON(json: [
"username": "Sajjon",
"firstname": "Alexander",
"country": "Sweden"
])
let u1 = try UserStruct(json: json)
let u2 = try UserClass(json: json)
print(u1.username) // prints "Sajjon"
print(u2.username) // prints "Sajjon"

Resources