I have an object that might look something like this...
class User {
var username: String?
}
I'd like to have users that could either be a student or a teacher. At the moment, I've added in properties to the User class like this...
class User {
var username: String?
// student properties
var year: Int?
// teacher properties
var department: String?
}
I'm sure I should be using inheritance here, but I'm worried that's going to make the control flow a bit complicated. For example, as part of the login function, I do this...
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.user = User(delegate: self)
appDelegate.user!.load_from_user_defaults()
Or to get something from the current user, I would do this...
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var username = appDelegate.user.username
How would I use inheritance, in this situation, given that I wouldn't know if it was a teacher or a student logging in? Is it easier to stick to the way I'm doing it?
Some options:
Super/Sub class pattern (inheritance) where you use downcast to check if it is a Student or a Teacher.
Protocols work the same as the Super/Sub pattern but a type can conform to many protocols but can only inherit from one super.
if both Student and Teacher have the same properties you can also just add a property (a bool or an enum) to determine which is which.
create an enum instead of a protocol or super class that has a rawValue of User and has cases for both Teacher and Student. (this is complicated)
Use an enum with associated values.
Every method has it's own benefits and drawbacks.
If you want to be able to pass a User object to different functions in your app, you want to have some conformance/inheritance.
If you have some conformance/inheritance, you can load the user with a method of your choosing and then downcast as such:
if let student = user as? Student {
// do student stuffs
} else if let teacher = user as? Teacher {
// do teacher stuffs
}
Option 1, regular inheritance :
class User {
var username: String?
}
class Student : User {
// student properties
var year: Int?
}
class Teacher : User {
// teacher properties
var department: String?
}
Option 2, conform to instead of inherit from, AKA Protcols :
protocol User : class {
var username: String? { get set }
}
class Student : User {
var username: String?
// student properties
var year: Int?
}
class Teacher : User {
var username: String?
// teacher properties
var department: String?
}
Option 3, UserType property:
enum UserType {
case Student
case Teacher
}
class User {
var username: String?
// student properties, if it is a teacher we leave this blank
var year: Int?
// teacher properties,, if it is a student we leave this blank
var department: String?
var type : UserType?
}
Option 4, enum with User as rawValue:
class User: Equatable,StringLiteralConvertible {
var username: String?
// student properties
var year: Int?
// teacher properties
var department: String?
var type : String?
init(withType type:String) {
self.type = type
}
required convenience init(stringLiteral value: String) {
self.init(withType: value)
}
required convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(withType: value)
}
required convenience init(unicodeScalarLiteral value: String) {
self.init(withType: value)
}
}
func ==(lhs:User,rhs:User) -> Bool {
if lhs.username == rhs.username && lhs.department == rhs.department && lhs.year == rhs.year && lhs.type == rhs.type {
return true
} else {
return false
}
}
enum UserType : User {
case Student = "Student"
case Teacher = "Teacher"
}
let user = UserType.Teacher
Option 5, enum with associated values:
class User {
var username: String?
}
class Student {
// student properties
var year: Int?
}
class Teacher {
// teacher properties
var department: String?
}
enum UserType {
case student(Student)
case teacher(Teacher)
}
"Could be either this or that": In Swift, you use an enumeration. One class for student, one class for teacher, one enumeration for "student or teacher".
Related
I need to use two classes with the same name in swift 5. For this, I have created those two classes in two different modules, but I am confused on how to use both the classes in an UIViewController
one of my class is Person which is in models > student module and another class is Person with is in the models module
I have tried importing class like
import class models.student.Person
class BookViewController: UIViewController {
var students:[Person] = [] //it should call models.student.Person
var people: [Person] = [] //it should call models.Person
...
but above Person class is pointing to models.Person only, It is not pointing to models.student.Person
Person class in models > Person.swift is
import Foundation
// MARK: - Person
public struct Person: Codable {
public let firstName: String?
public let lastName: String?
public let address: String?
public let phone: String?
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case address = "address"
case phone = "phone"
}
public init(firstName: String?, lastName: String?, address: String?, phone: String?) {
self.firstName = firstName
self.lastName = lastName
self.address = address
self.phone = phone
}
}
and the models.student.Person.swift is
import Foundation
// MARK: - Person
public struct Person: Codable {
public let fullName: String?
public let educationalQualification: String?
public let college: String?
enum CodingKeys: String, CodingKey {
case fullName = "full_name"
case educationalQualification = "educational_qualification"
case college = "college"
}
public init(fullName: String?, educationalQualification: String?, college: String?) {
self.fullName = fullName
self.educationalQualification = educationalQualification
self.college = college
}
}
I need both the class in my BookViewController
I can't change the name of the class to a different one, I should use the same class name.
You could try typealias:
typealias StudentPerson = yourStudentFramework.Person
and then use it like:
import yourStudentFramework
class BookViewController: UIViewController {
var students:[StudentPerson] = [] //it should call yourStudentFramework.Person
var people: [Person] = [] //it should call models.Person
...
I have a shared instance user profile class which I'm using to maintain the user details throughout the app, now when I log out I want to clear out all the data in this shared instance class. Below is my sample declaration of current class
class UserProfile: NSObject {
static let sharedUserInstance = UserProfile()
var firstName: String?
var lastName: String?
var userType = UserType.student
var email: String!
var zipCode: String!
var profileImage: UIImage = Constants.defaultProfilePic
var hobbies: [String]?
var userPreferences: UserPreferences?
}
I would like to clear out all the variables in the shared instance at once. One way to go about would be to declare a clear function and set nil to each object for eg:
func clear() {
firstName = nil
lastName = nil
so on...
}
Is there any better or easier way to do this so that my UserProfile.sharedUserInstance gets resets at once ?
You can create a sigleton class for the entire project and save user information in that class and at logout time assign nil to that object
class Singleton {
private init() {}
static let shared = Singleton()
var userInfo:UserInfo?
}
on login just assign user information object to this from your viewController like this
Singleton.shared.userInfo = your_object_to_save
and on logout just assign it nil value
Singleton.shared.userInfo = nil
This is one way to do it.
I created a second class called user, which will only be accessed by UserProfile.
Each UserProfile object has a User instance variable.(I don't know how, but if you can figure out a way to make the User class private to UserProfile, that would be ideal)
In swift, variables can have getters and setters so you can have matching variables in UserProfile and have their getters and setters access the variables in User.
When done this way, you can access the variables in UserProfile without needing any knowledge of the User class.
Then in the UserProfile clear function you can just do self.user = User()
I reduced your variables down to just first name and last name for a simpler answer.
class User: NSObject {
var firstName: String?
var lastName: String?
}
class UserProfile {
static var userProfile = UserProfile()
var user = User()
var firstName: String? {
get{return user.firstName}
set{user.firstName = newValue}
}
var lastName: String? {
get{return user.lastName}
set{user.lastName = newValue}
}
func clear(){
self.user = User()
}
}
I am using Realm notification block for updating messages in a page.
let messageResult = realm.Object(MessageRealm.self)
notificationTokenMessage = messageResult.addNotificationBlock{ [weak self] (changes: RealmCollectionChange) in {
switch changes {
case .initial(_),
.update(_, _, _, _):
self?.tableView.reloadData()
default:
break
}
}
}
In MessageRealm class, there is a property, name author. An author is basically a UserRealm object.
Class MessageRealm extends Object {
dynamic var _id: String? = nil
dynamic var body: String? = nil
dynamic var author: UserRealm? = nil
override class func primaryKey() -> String? { return "_id" }
}
Class UserRealm extends Object {
dynamic var _id: String? = nil
dynamic var fullName: String? = nil
dynamic var status: String? = nil // 'online' or 'offline'
override class func primaryKey() -> String? { return "_id" }
}
When a new message is received from socket, the message page is updated as it gets notifications from Realm. But, when user update notification is received from socket, the Realm updates the message page. I don't want to update message page for an update in author object.
Probable Solutions:
Class MessageRealm extends Object {
dynamic var _id: String? = nil
dynamic var body: String? = nil
dynamic var author: UserRealm? = LinkingObjects(fromType: UserRealm.self, property: "messages")
override class func primaryKey() -> String? { return "_id" }
}
Class UserRealm extends Object {
dynamic var _id: String? = nil
dynamic var fullName: String? = nil
dynamic var status: String? = nil // 'online' or 'offline'
let messages = List<MessageRealm>()
override class func primaryKey() -> String? { return "_id" }
}
We can solve it using LinkingObjects. But, this inverse relation needs a direct relation to map. Am I right? So, need to have a property of List of Messages in User. And from MessageRealm I have to link to User. But this will be complicated to maintain.
Store author's id in MessageRealm as a foreign key like a traditional database.
What do you suggest?
How can we do normalization in Realm to avoid update issue?
Is there any convention or best practices to manage a bigger database? (I am aware of Tim's answer https://stackoverflow.com/a/31594548/2666902 )
In my opinion, the best solution would be keeping the author property as a one-to-one relationship from MessageRealm to UserRealm, since a single message can only have one author and creating an inverse relationship in UserRealm.
class MessageRealm: Object {
dynamic var _id: String? = nil
dynamic var body: String? = nil
dynamic var author: UserRealm? = nil
override class func primaryKey() -> String? { return "_id" }
}
class UserRealm: Object {
dynamic var _id: String? = nil
dynamic var fullName: String? = nil
let messages = LinkingObjects(fromType: MessageRealm.self, property: "author")
override class func primaryKey() -> String? { return "_id" }
}
This way, you only need to keep the author property of your messages updated and the messages property of UserRealm will automatically keep in sync, so any time you try to access it, you will see all MessageRealm objects, where the author equals the specific user.
I'm trying to migrate from CoreData to FireBase. Hence the need for a shared interface across the app for my model.
I have a FTEvent class...
#objc(FTEvent)
public class FTEvent: FTEventBase {
}
that inherits from FTEventBase
#objc(FTEventBase)
public class FTEventBase: NSManagedObject {
}
Now I have a new class called FTRecord, which should share an interface with FTEvent above.
class FTRecord {
let key: String
var notes: String
var rating: Int
var time: String
var timestamp: Double
}
This is now where the problem begins. I could obviously create a protocol as interface for both FTRecord and FTEvent.
protocol IEvent {
var is_deleted: Bool? { get set }
var notes: String? { get set }
var date: Date? { get set }
var timestamp: Double? { get set }
var rating: Int? { get set }
}
But this becomes very hard since Bool in CoreData is actually a NSNumber. The Date is NSDate. Because of these type differences, I have no way to create a common interface.
Is there any advice, how I could achieve that?
I've got an Article and a Category model linked by a many-to-one relationship. However, the Category model has a unique constraint on the id property because it's the primary key as you can see below.
class Article: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
dynamic var category: Category()
override static func primaryKey() -> String? {
return "id"
}
}
class Category: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
This will work until an Article got the same Category and throw an exception because of the unique constraint.
How am I supposed to implement this kind of relationship ? Is there any built-in way to persist only the Category id and retrieve the corresponding Category ?
Thanks
As you can read in Realm doc (0.92.1), you have to use a List<Object> for a many-to-one relationship.
See this link :
http://realm.io/docs/swift/latest/
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person? // Can be optional
}
class Person: Object {
... // other property declarations
let dogs = List<Dog>()
}
let someDogs = Realm().objects(Dog).filter("name contains 'Fido'")
jim.dogs.extend(someDogs)
jim.dogs.append(rex)
So in your case, I guess it should be something like that :
class Article: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
class Category: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
dynamic var articles = List<Article>()
override static func primaryKey() -> String? {
return "id"
}
}
If your Realm version is older :
class Category: Object
{
...
dynamic var categories = RLMArray(objectClassName: Article.className())
}