Will an array of a superclass contain subclasses as well? - ios

I created a class Person. Person contain properties like name and email. Both are from type String.
Beside the Person class, I have a subclass Student that inherited from superclass Person. Subclass Student contain properties like student number (String) and isGraduated (Boolean).
I have an empty array of persons from the class Person, like:
var persons: [Person]()
After I created both Person and Student objects inside the array persons, I read them out using a UITableView. Both models will be print in the cells. But when I want to check the value of isGraduated from the selected row, auto-completion doesn't give me the value of the property: persons.isGraduated.
The first thought of this problem is, will my persons array contain also the subclass Student? My second thought would be, that I think I should not check the value isGraduated inside the TableView. My wish of this function is that it will do something, like call the native camera if the person is graduated.
Looking forward to the solution.

Tree options here
A: Change the array type to Student
var students: [Student]()
B: Cast each element from the persons array to a Student. Ideally inside a for each person.
guard let student = person as? Student else {
return // or continue if inside a for loop
}
Then u will have access to that student variable.
C: Or if they might be students and persons at the same time in the array then
for person in persons {
switch person{
case let student as Student: //student case
//do something
default: //person case
//do something
}
}

If you want to check what is the type of your person : Person or Student you can use is keyword like this :
if persons[0] is Person {
//if a person
} else if persons[0] is Student {
//if a student
}
If you want to use a function member for your specific type, you need to downcast the type by using the keyword as like this :
if let person = persons[0] as? Person {
//person is now a Person type
} else if let student = persons[0] as? Student {
//student is now a Student type
}

Related

Swift Realm - get types of all database objects

I have a list of various objects in my Realm Database all of which are created as default ClassName: Object classes. Is there any way to get types (classes names) of those objects, that can be saved as variables, or create an enum of these types?
The issue is that Realm Results objects are homogenous - they only contain one object type. That translates to meaning you will never have a situation where Results would contain Person and Dog classes at the same time.
That being said we have the option of using the Realm AnyRealmValue
Let me set this up: Suppose you have a PersonClass and a DogClass
let dog = DogClass()
let person = PersonClass()
and a class with a List that can contain AnyRealmValue
class MyClass: Object {
#Persisted var myList = List<AnyRealmValue>()
}
when then need to cast those objects to AnyRealmValue to make this work
let obj0: AnyRealmValue = .object(dog)
let obj1: AnyRealmValue = .object(person)
and we add those to the List
let m = MyClass()
m.myList.append(obj0)
m.myList.append(obj1)
You mentioned switch but here's a simple if...then clause to handle them differently - depending on the class
if let person = item.object(PersonClass.self) {
print("is a person")
} else if let dog = item.object(DogClass.self) {
print("is a dog")
}

Dictionary ObjectIdentifier: CustomClass Questions about Dictionary append in Swift

Hi? I have created BananaMilkFactory, CiderFactory and CantataCoffeeFactory that inherit superclass called Beverage. And I made an Inventory that bundles Beverages into an array.
For example, if you create 3 Banana Milk, 2 Cider, 1 Cantata Coffee, Inventory will have a total of 6 Beverages.
The function I want is [ObjectIdentifier: Inventory] and I want to return the Beverages of the same ObjectIdentifier separately.
// [BananaMilk(), BananaMilk(), BananaMilk(), Cider(), Cider(), CantataCoffee()]
private var inventory: [Beverage]
func readInventores() -> [ObjectIdentifier : Inventory] {
var allInventores = [ObjectIdentifier : Inventory]()
self.inventory.forEach { beverage in
// Help~!!
}
return allInventores
}
What should I do to return it in [ObejctIdentifier: Inventory] format?
create an array which contains class name of object (BananaMilkFactory, CiderFactory ....). Iterate through the array,put objects with the same class name in a new array.I don't know if I understand your question correctly.

Realm query Object property field by its property

I'm developing an application for iOS using Swift and chose Realm as a database solution for it. I asked one question about Realm and now I have another.
Suppose we have a schema like this:
class Person: Object {
dynamic var id: String = NSUUID().UUIDString
dynamic var name: String?
dynamic var cars: Car?
class Car: Object {
dynamic var name: String?
I have one class (Person) that contains any number of objects of another class (Car). Car that are "linked" with the Person has some properties in context of that Person (and they can be different for same Car for different Persons or for two similar Cars for one Person). Using List<...>() we can not store such properties for each Item, am I right?
If we use Car only for one Person and only once we can create another class that includes only additional properties for Cars and links them with ID of Person plus ID of Car. But it does't work if we have two similar Cars with different additional properties.
So, how I see the solution. Have one table (class) stores ID of Person, ID of one Car and additional properties for this Car. For another Car for the same Person it has the same Person ID, Car ID (same or not) and another additional properties for this instance of a Car.
There is a problem (and a question that I mean). Having that structure I want to query all Cars from that table with their additional properties that have Person ID equals to some_id. How should I do this? Or maybe what another structure (maybe using List<...>) I should have to achieve such kind of behavior?
What is FastList exactly ?
If you want Items to have a property of Lists collection.
You have to redefine your Realm model. something like this.
class Car:Object{
dynamic var createDate: NSDate = NSDate()
}
class Person:Object{
let cars = List<Car>()
}
and query by predicate like this
let realm = Realm()
var ownedCarsFilterByDate = realm.objects(Person).filter("ANY cars.createDate = '\(date)'")
Edited to updated question
Your solution is to create table class, which has 'Person' , 'Car' and 'Context Attribute'.
Your model would be like this
class PersonAndCarRelation:Object{
dynamic var person: Person?
dynamic var car: Car?
dynamic var contextAttribute = ""
}
and you can query all cars associated with person
let personID = "123456789"
let personAndCarArray = realm.objects(PersonAndCarRelation).filter("person.id == \(personID)")
for personAndCar in personAndCarArray{
let personName = personAndCar.person.name
let carName = personAndCar.car.name
let context = personAndCar.contextAttribute
println("I am \(personName). I have a \(carName) with \(context)")
}

Weak object array in Swift

I would like to create an object array as a property of another class in Swift, such as:
class person{
var livingInHouse : house
name : String
}
class house{
var personArray : [person]
}
My constraints are:
I would like to easily access the objects in the personArray using subscripts: e.g. houseInstance.personArray[1].name = "Steve"
I would like to create the personArray so that the person objects are deallocated when houseInstance object is deallocated.
What is the best method in Swift to do this?
From what you say, you want the person living in an house "alive" as long as their house is alive, so it is obvious that the house must "own" the persons.
Your class Person however just maintain a reference to the house for convenience, it doesn't own it (else it would be bad!)
so :
class house
{
var personArray : person[]
}
class person
{
unowned var livingInHouse : house
name : String
}
You could then provide some convenience method to your house such as:
func add(Person p)
{
personArray += p;
p.livingHouse = self;
}

Magical Record import (next step)

I've put next step in the title as this is not the same problem as my previous question with almost the exact same title.
I have a Person entity.
Person
--------
name - mappedKeyName: FullName
email - mappedKeyName: EmailAddress
personID - mappedKeyName: Id
--------
photos
And a Photo entity.
Photo
--------
image
createDate - mappedKeyName: Date
photoID - mappedKeyName: Id
--------
owner (type Person) - mappedKeyName: UserId - relatedByAttribute: personID
There are other objects that relate to Person too and the JSON for these comes as so...
{
ObjectId : blah,
Owner : {
Id : 12345asdfg,
FullName : Oliver,
EmailAddress : oliver#oliver.com
}
}
With this JSON my setup works with the import. Any person records that don't exist (with the Id) are created. And any that do exist are updated.
However, the photos JSON object comes like this...
{
Id : thisIsThePhotoID,
Date : today,
UserId : 12345asdfg
}
When the objects come down like this the Magical record import stops when it gets to the person import.
The code crashes at...
- (id) MR_relatedValueForRelationship:(NSRelationshipDescription *)relationshipInfo
{
NSString *lookupKey = [self MR_lookupKeyForRelationship:relationshipInfo];
return lookupKey ? [self valueForKeyPath:lookupKey] : nil; // it stops here.
}
The value of lookupKey is #"personID".
Printing out relationshipInfo at the breakpoint gives...
$6 = 0x1fd695e0 (<NSRelationshipDescription: 0x1fd695e0>),
name owner,
isOptional 0,
isTransient 0,
entity Photo,
renamingIdentifier owner,
validation predicates (),
warnings (),
versionHashModifier (null)
userInfo {
mappedKeyName = UserId;
relatedByAttribute = personID;
},
destination entity Person,
inverseRelationship photos,
minCount 1,
maxCount 1,
isOrdered 0,
deleteRule 1
I really have no idea why this isn't working. I don't get any sensible errors to report.
MagicalRecord cannot map the relationship automatically with this JSON format:
{
Id : thisIsThePhotoID,
Date : today,
UserId : 12345asdfg
}
In order for MagicalRecord to map the relationship to a Person object, it would have to be an object in the JSON as well, for example:
{
Id : thisIsThePhotoID,
Date : today,
User : {
UserId : 12345asdfg
}
}
This way MagicalRecord knows it's an object and it will do the appropriate lookup in your existing database for the Person record with the above ID and map the relationship.
So there are two issues with this, though. If you cannot change the JSON output you have to create a category class on Photo where you manually map the relationship yourself. I'll get to that after the second issue.
The second issue is that the above JSON format assumes you already have parsed the users and stored the records in your database. If you have not MagicalRecord will create a new Person record with the above ID, but since no other attributes exist on that object (notice the UserId key is the only attribute in the dictionary) it will be fairly empty and not include the name and email address. You can always extend your JSON (if you have that possibility) to include those attributes as well in the Person dictionary inside the Photo dictionary:
{
Id : thisIsThePhotoID,
Date : today,
User : {
UserId : 12345asdfg,
FullName : Oliver,
EmailAddress : oliver#oliver.com
}
}
The JSON payload is quite small so it doesn't hurt to do that if you can. Plus it will only create a new Person record if one doesn't exist in the database already.
And then for the manual mapping. If you cannot change the JSON to the above format you have to manually override the relationship mapping as the JSON is not prepared the way MagicalRecord does mapping.
Create a category class for Photo called Photo+Mapping.h/.m. I like to stick with +Mapping for these. Then the class should be Photo (Mapping) in the header and implementation file and you're good to go.
MagicalRecord has a number of instance methods available to override (see the latter part of this article on MagicalRecord importing written by the author of MagicalRecord), among them are import<;attributeName>;: and import<;relationshipName>;:. There are also a willImport:, didImport: and shouldImport: methods on the class itself which allows you to override any mapping.
For your case you can use import<;relationshipName>;: or shouldImport:. I took these two because one has a bit of a benefit depending on whether you have already mapped all your Person objects and they're available for relationship mapping on the Photo object.
Here are the examples of what you can do (you can choose to combine a few of them if you wish, it doesn't hurt to do so). A note here: ALWAYS use the current NSManagedObjectContext when overriding mapping (easily accessible with MagicalRecord through self.managedObjectContext) otherwise you will end up with context issues.
Be sure to import Person:
#import "Photo+Mapping.h"
#import "Person.h"
// Assuming you only want to import the Photo object if you already have a Person stored this is a great method to tell MagicalRecord whether to continue with importing or not
-(BOOL)shouldImport:(id)data {
Person *person = [Person findFirstByAttribute:data[#"UserId"] value:#"personID" inContext:self.managedObjectContext];
if (!person) {
// no Person object exists so don't import the Photo object - again this is up to you since you might want to create the record if not
return NO;
}
// you can set the relationship here (you might as well) or use the importPerson: method below (doing a second lookup, which is unnecessary at this point)
[self setPerson:person];
return YES;
}
// If you use this method you're doing the lookup to check whether a record exist when MagicalRecord is trying to map the Person relationship
-(void)importPerson:(id)data {
Person *person = [Person findFirstByAttribute:data[#"UserId"] value:#"personID" inContext:self.managedObjectContext];
if (!person) {
// if no Person record exists for the associated UserId, you should create one (or not - if you choose not to, it's wise to throw away this Photo object)
person = [Person createInContext:self.managedObjectContext];
[person setPersonID:data[#"UserId"]];
}
// set the relationship
[self setPerson:person];
}
// finally you can also use the following method when MagicalRecord is done mapping and get rid of the Photo object if the Person relationship is nil:
-(void)didImport:(id)data {
if (!self.person) {
[self deleteInContext:self.managedObjectContext];
}
}
Hope this helps! Let me know if you have any questions.

Resources