Classes and calling the properties Dynamically, Swift - ios

I have two swift classes with two fairly similar properties (I have made the ones below the same for the purpose of this question) I want to use. I want to call the properties dynamically in the ViewDidLoad and I have used an array as shown below so I can change the index and choose which class to call. Unfortunately I get the error
Anyobject does not have a member named speciesType
What could I have missed or am I approaching the whole problem in the wrong way?
class Lion {
var age = 45
var speciesType = "Felidae"
}
class Lioness {
var age = 10
var speciesType = "Panthera"
}
var animal1 = Lion()
var animal2 = Lioness()
var animalKingDom = [animal1, animal2]
var colourChanging = animalKingDom[0].speciesType

Why dont you create a protocol that extracts out the common properties from both the classes.
class Lion {
var age = 45
var speciesType = "Felidae"
}
class Lioness {
var age = 10
var speciesType = "Panthera"
}
protocol LionType {
var speciesType: String { get set }
var age: Int { get set }
}
extension Lion: LionType { }
extension Lioness: LionType { }
var animal1 = Lion()
var animal2 = Lioness()
var animalKingDom: [LionType] = [animal1, animal2]
var colourChanging = animalKingDom[0].speciesType
I created a protocol LionType and added extension to both the classes to conform to the protocol. Since both the classes already have age and speciesType, the classes will remain unchanged. And finally, a small modification to your animalKingDom array could make it work easily.
By the way, I added a protocol LionType, I think it makes more sense to name it AnimalType which fits for all animals not just lions :)

I prefer the syntax where you declare the protocol before the classes, and let the classes explicit implement the protocol upon creation.
import Cocoa
protocol Animal {
var age: Int { get set }
var speciesType: String { get set }
}
class Lion: Animal {
var age: Int
var speciesType: String
init(age: Int, speciesType: String) {
self.age = age
self.speciesType = speciesType
}
}
class Lioness: Animal {
var age: Int
var speciesType: String
init(age: Int, speciesType: String) {
self.age = age
self.speciesType = speciesType
}
}
var l1 = Lion(age: 5, speciesType: "Cat")
var l2 = Lioness(age: 6, speciesType: "Doge")
var animalArray: [Animal] = [l1, l2]
for animal in animalArray {
print(animal.age)
print(animal.speciesType)
}
Also I'm not sure I would give a class for both male and female of an animal. But it totally depends on your business domain.

The compiler infer that the generic type of your array is 'AnyObject'.
Try removing one element from your array, and the compiler will find the good type of your array.
The compiler is not able to know that Lion and Lioness has a property in common.
You can either make a superclass or a protocol like GeneratorOne suggests.

I think that what you really want to do is have two instances of a single class (or better yet, if your real example is anything like this one, a struct). Let's keep using your Lion class. Two instances would then be
var lion = Lion()
lion.age = 45
lion.family = "Felidae"
var lioness = Lion()
lioness.age = 10
lioness.family = "Panthera"
(I'm ignoring the fact that your male and female are in different families. :))

Related

Variable name in dot notation in Swift

I want to use variable names in dot notation in Swift.
I'm wrestling with trying to make sense of JSON in Swift, and having a really hard time doing things that are so easy in JS (like cycling through objects, etc).
I have variables of custom types:
var questionConfiguration: QuestionConfiguration
var priorityConfiguration: PriorityConfiguration
Each of these types has an attribute, displayOrder, I want to access this dynamically. I have tried various things, including the following (i is a variable of type Int):
var configs = [self.questionConfiguration, self.priorityConfiguration]
print([configs[i]].displayOrder)
var configs = ["questionConfiguration", "priorityConfiguration"]
print([configs[i]].displayOrder)
How can I achieve this?
EDIT:
struct QuestionConfiguration: Codable {
var displayOrder: Int
}
struct PriorityConfiguration: Codable {
var displayOrder: Int
}
You can create a protocol for same property name then you can access it easily
protocol SameProp {
var displayOrder: Int {get set}
}
class QuestionConfiguration : SameProp {
var displayOrder : Int = 4
}
class PriorityConfiguration : SameProp {
var displayOrder : Int = 6
}
var questionConfiguration: QuestionConfiguration = QuestionConfiguration()
var priorityConfiguration: PriorityConfiguration = PriorityConfiguration()
var configs = [questionConfiguration, priorityConfiguration] as [SameProp]
for elements in configs{
print(elements.displayOrder) // will print 4 and 6
}
Swift is very particular about data types and I suspect you are experiencing challenges because of it.
When you do this:
var configs = [self.questionConfiguration, self.priorityConfiguration]
In Swift, all the elements in an array have to have the same type. You've not given Swift any hints about what types this array should contain so the compiler has to infer a type. QuestionConfiguration and PriorityConfiguration are two different types. So the compiler is likely to infer that configs is of type [Any] also called Array<Any>.
Then you try something like this:
configs[i].displayOrder (not sure why your code has the extra brackets)
But configs[i] is inferred to be of type Any and that type doesn't have a displayOrder property so the compiler complains.
You need to tell the compiler that these types share some properties. One way to do that would be to add a protocol that describes what they share:
protocol DisplayOrderable {
var displayOrder: Int { get }
}
struct QuestionConfiguration: DisplayOrderable, Codable {
var displayOrder: Int
}
struct PriorityConfiguration: DisplayOrderable, Codable {
var displayOrder: Int
}
then you could use
var configs: [any DisplayOrderable] = [self.questionConfiguration, self.priorityConfiguration]
And configs[i].displayOrder should work.

Why I can't change value in nested Models?

I have 2 class , First class has a property which inheritance from second class
class FirstModel{
var firstType : Secondmodel?
}
class Secondmodel{
var secondType : Int?
}
Now I want to set a value to secondType so I code
var Modelll = FirstModel()
Modelll.firstType?.secondType = 100
When I try to read this property with print(Modelll.firstType?.secondType) it return nil
So first question is why I couldn't read this
But I try to do this
var Modelll = FirstModel()
var ModelSecond = Secondmodel()
ModelSecond.secondType = 100
Modelll.firstType = ModelSecond
print(Modelll.firstType?.secondType)
It works perfectly , printed out Optional(100) I really dont understand whats going on behind the scene. Can anyone explain this?
First of all, all variables and constant should be named with lowercased symbol. With Uppercased only names of classes, structs, protocols, enums etc
You problem is that when You init FirstModel, firstType variable is nil by default
var model = FirstModel()
print(model.firstType) //prints nil
so You need to do
var model = FirstModel()
model.firstType = SecondModel() //use camel case in naming
model.firstType?.secondType = 100
print(model.firstType?.secondType) // prints 100
Ammm.. Because you don't initiate firstType with an instance in your first try.
You can add something like this
class FirstModel{
var firstType : Secondmodel?
init() {
self.firstType = Secondmodel()
}
}
But this is not exactly how things should be done. Here is not a problem of nesting, you just don't give a value in firstyType property.
The best case should be like:
class Secondmodel{
var secondType : Int?
init(secondType: Int) {
self.secondType = secondType
}
}
var modelll = FirstModel()
modelll.firstType = SecondModel(secondType: 100)
You should read more about OOP.
..and objects should not be named with capital letters

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

How to achieve object merging through generics in swift?

Imagine I have a class Number:
class Number {
var val: Double?
}
and have two instances of that class, A and B.
Now imagine I want to merge Binto Athrough a statement like
merge(B, into: A)
Now of course I could write the function like this:
func merge(from: Number, into: Number){
into.val = from.val
}
But that isn't reusable at all. Is there a way I could write a generic merge class?
UPDATE: Although some of the answers offer good and viable solutions, none of them are "generic" enough (generic here is meant in a non-technical way).So looking at the answers, I got some inspiration, and here is the solution I am now considering: make Number a NSObject subclass and declare all the properties that can be merged as dynamic. For example:
class Number: NSObject {
//Put the required init and initWithCoder: here
dynamic var val: Double?
}
Then declaring a protocol that mergeable classes must respect
protocol Mergeable: class {
var mergeablePropertyKeys:[String] {get}
}
And then declaring a global function that performs a merge:
func merge<U: Mergeable, Mergeable where U.Type == V.Type>(from: U, into:V){
for property in U.mergeablePropertyKeys {
V.setValue(U.valueForKey(property), property)
}
}
And I know that this will not work because the arguments to merge are not necessarily NSObjects.
How can I make sure that the arguments to merge are both NSObjects?
Can avoid having to specify the names of all my mergeable values by simply obtaining a list of my object's dynamic values?
It sounds like what you want is a generic function that uses reflection to merge properties. Reflection is limited in Swift, but it is doable using the MirrorType. I have used this method before to build a generic json parser in swift - you could do something similar but instead of parsing a json dictionary to properties map your two object's properties.
An example of using reflection to do this in swift:
func merge<T>(itemToMerge:T) {
let mirrorSelf = Mirror(reflecting: self)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
This assumes the object defining the merge method is a subclass of NSObject (so it can take advantage of NSKeyValueCoding). You could also make this a static method that could merge any 2 objects of any NSObject type:
static func merge<T1: NSObject, T2: NSObject>(itemChanging:T1, itemToMerge:T2) {
let mirrorSelf = Mirror(reflecting: itemChanging)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
Im not sure what you are expecting but there is generic solution:
class Number<T> {
var val: T?
}
protocol Merge {
func merge(from: Self, into: Self)
}
extension Number: Merge {
func merge(from: Number, into: Number) {
into.val = from.val
}
}
Protocol
Lets define a HasValue protocol (available only for classes) like this
protocol HasValue: class {
typealias T
var val: T? { get set }
}
Merge
Now we can define a generic function
func merge<U: HasValue, V:HasValue where U.T == V.T>(from: U, into:V) {
into.val = from.val
}
The constraints in the function signature do guarantee that
Both params do conform to HasValue (therefore are classes)
val types for both params are equals
Scenario 1: params have the same type
class Number: HasValue {
var val: Double?
}
let one = Number()
one.val = 1
let two = Number()
two.val = 2
merge(one, into: two)
print(two.val) // Optional(1.0)
Scenario 2: params have different types but their values have the same type
I did not constrain the 2 params of Merge to having the same type, I am only checking that the val properties of the 2 params must have the same type.
So we could also merge different instances of different classes having val of the same type like
class Phone: HasValue {
var val: Int?
}
class Computer: HasValue {
var val: Int?
}
let iPhone = Phone()
iPhone.val = 10
let iMac = Computer()
iMac.val = 9
merge(iPhone, into: iMac)
print(iMac.val) // Optional(10)
Scenario 3: params have generic types
class Box<S>: HasValue {
var val: S?
}
let boxOfString = Box<String>()
boxOfString.val = "hello world"
let boxOfInt = Box<Int>()
boxOfInt.val = 12
merge(boxOfString, into: boxOfInt) // << compile error
let boxOfWords = Box<String>()
boxOfWords.val = "What a wonderful world"
merge(boxOfString, into: boxOfWords)
print(boxOfWords.val) // Optional("hello world")

Trying to create a Swift Realm Data Model

I have looked at the Realm.io docs. I am working on an application to track my vehicle expenses. I have put together what I think might work for a data model in Realm, but I am new to it and not sure if this is something that will work or if there is a better way to do it. Here is what I have, and I have not put this in a project and tried to compile yet. The realm.io docs are a little vague to me, so maybe someone can tell me what you think. I have included some comments in places that I am just not sure how to achieve what I'm going for...
// Vehicle model
class Vehicle : RLMObject {
dynamic var name = “”
dynamic var number = “”
dynamic var currentMiles = 0
dynamic var entries = RLMArray(objectClassName: Entry.className())
}
// Entry model
class Entry: RLMObject {
dynamic var vehicle: Vehicle //??
dynamic var date = NSDate()
dynamic var expense = 0.0
dynamic var mileage : Vehicle.currentMiles // want to update the Vehicle mileage with each entry
}
// Gas model
class Gas: Entry {
dynamic var gallons = 0
dynamic var pricePerGallon = 0.0
}
// OilChange model
class OilChange : Entry {
dynamic var milesBetweenChanges = 0
}
// Other Service model
class OtherService: Entry {
dynamic var notes = “”
}
You're on the right track! The only model that needs work is Entry, I think. First, here's your model with my annotations:
// Entry model
class Entry: RLMObject {
dynamic var vehicle: Vehicle // This is valid Swift, but you'll need to set the value in the designated initializer (`init()`).
dynamic var date = NSDate()
dynamic var expense = 0.0
dynamic var mileage : Vehicle.currentMiles // This isn't valid Swift, since `Vehicle` is a class, and doesn't have a `currentMiles` member
}
What you want is something like this:
// Entry model
class Entry: RLMObject {
dynamic var vehicle = Vehicle() // Use a default value so that `init()` succeeds, but you can still use `init(vehicle: Vehicle)` in your code
dynamic var date = NSDate()
dynamic var expense = 0.0
dynamic var mileage = 0
init() {
// Must override init() when adding a convenience initializer
super.init()
}
convenience init(vehicle: Vehicle) {
super.init()
self.vehicle = vehicle
mileage = vehicle.currentMiles
}
}
It's unfortunate that you find Realm's docs vague. Please let us know if there's anything in particular you'd like us to clarify. We're a pretty approachable bunch!

Resources