How to use generics in own created model class?
I have one FeatureListModel class and other have FavoriteModel class. Both store the same properties, the only difference is the different class model name.
I need to display model properties value in ProductDetail controller.
How could I manage this stuff using generics?
Here is my code (Swift 4.2):
1st Model: FavoriteListModel
class FavoriteListModel {
var categoryID: Int?
var item_name: String?
var MRP: String?
}
2nd Model: FeatureListModel
class FeatureListModel {
var categoryID: Int?
var item_name: String?
var MRP: String?
}
I have 8-10 more properties, but this is just some stuff in my code.
Controller - ProductDetailTableViewController
class ProductDetailTableViewController : UITableViewController {
var productDetails: FavoriteListModel!
var productFeatureList: FeatureListModel!
fileprivate func displayProduct() {
if productDetails != nil {
title = productDetails.item_name
categoryID = productDetails.categoryID!
}else if productFeatureList != nil {
categoryID = productFeatureList.categoryID!
title = productFeatureList.item_name
}
}
and in my Product Detail Table Controller, I am accessing model objects and display on the screen.
I don't want if-else check.
You are mixing up generics and protocols. In your case a protocol is preferable.
In ProductDetailTableViewController there is an object which responds to the getter of item_name (by the way please conform to the camelCased naming convention itemName) and categoryID. The type of the object as well as the existence of other properties and functions is not significant.
Create a protocol
protocol Listable {
var itemName : String { get }
var categoryID : Int { get }
}
Then adopt the protocol in your classes (do you really need a class?) and declare at least categoryID as non-optional since you are force unwrapping the value later anyway. Don't use optionals as an alibi not to write an initializer.
class FavoriteListModel : Listable { ...
class FeatureListModel : Listable { ...
In ProductDetailTableViewController rather than two properties declare one property as Listable and instead of objective-c-ish nil checking use optional binding:
var details: Listable!
fileprivate func displayProduct() {
if let productDetails = details {
title = productDetails.itemName
categoryID = productDetails.categoryID
}
}
What you have here is not a use case for the Generics. Generics are used when you have for example a function that does exact same thing but can be used with two different parameter types. That's when you use generics.
Another concept is super class (parent class or base class) which is used when you have a class with common properties and then other classes with those properties and then extra and different unique properties which in this case, each class subclasses the parent class.
What you have here is neither of them. A good architecture for this case is just a single model type (class or struct) and using two different collections (array or set) in your view controller.
You can also create a favorite class or featured class which holds an array with your models.
Related
Context, an iOS app using UIKit and MVC.
Model A has a singleton object, and one of its property value (foo in this case) can be changed during the runtime. Model B has a property, Model C, that is initialized using the property value of Model A.
class ModelA {
private(set) var foo: CustomObjectClassName
static let shared = ModelA()
}
class ModelB {
private var bar: ModelC
init() {
self.bar = ModelC(ModelA.shared.foo)
}
// TODO: Observer `foo` value change in Model A and then
// reinit ModelC to replace the old `bar` object
}
What is the common design pattern or mechanism that should be used to let Model B know about property change in Model A and re-initialize its own property?
I found two patterns that can be used; however, the more I read about them, they seem to be designed for communication between Model and Controller.
NotificationCenter (Notification & Observer)
Key-value Observing
Related Information
Using Key-Value Observing in Swift, https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift
NotificationCenter, https://developer.apple.com/documentation/foundation/notificationcenter
Thanks for El Tomato comment, delegation can be a pattern be used.
A delegation pattern would work since in my case Model A is only used by a single entity Model B.
protocol ModelADelegate {
func fooDidChange() -> Void
}
class ModelA {
public var delegate: ModelADelegate?
private(set) var foo: CustomObjectClassName {
didSet {
delegate.fooDidChange()
}
}
static let shared = ModelA()
}
class ModelB, ModelADelegate {
private var bar: ModelC
init() {
ModelA.shared.delegate = self
self.bar = ModelC(ModelA.shared.foo)
}
func fooDidChange() {
self.bar = ModelC(ModelA.shared.foo)
}
}
I am trying to do something like this in Swift.
public class BaseModel {
}
public class SubModel:BaseModel {
}
public class BaseClass {
public var model:BaseModel
init(_ model:BaseModel) {
self.model = model
}
}
public class SubClass: BaseClass {
override var model:SubModel
}
But the complier is not allowing me to override model object with a subclass. Is it possible to achieve something like what I am trying to do above in Swift using inheritance?
As written, this wouldn't be type-safe. Your interface requires that subclass.model = model has to work for any model (and in this specific example, SubClass(model) also is "legal" for any model because it's currently inheriting the init).
What I believe you really mean is that all BaseClass can return a Model, but SubClass can only be set with a SubModel.
How you fix this depends heavily on what the users of SubClass look like and why you're reaching for inheritance. As a rule, you should be hesitant to reach for inheritance in Swift. It's fully supported, but Swift tends to prefer other tools than class inheritance.
A common solution for this specific example would be a generic, for example:
// Place any general Model requirements here.
public protocol BaseModel {}
// Just marking things final to emphasize that subclassing is not required
// These can all also be structs depending on if you need values or references
public final class SubModel: BaseModel {}
public final class BaseClass<Model: BaseModel> {
var model: Model
init(_ model: Model) {
self.model = model
}
}
// You can typealias specific instances if that helps
// With this, the syntax is extemely close to what you were trying to do
typealias SubClass = BaseClass<SubModel>
let sc = SubClass(SubModel())
let model: BaseModel = sc.model
// But, it's type safe
public final class OtherModel: BaseModel {}
sc.model = OtherModel // Cannot assign value of type OtherModel to type SubModel
let bad = SubClass(OtherModel()) // Cannot convert value of type 'OtherModel' to expected argument type 'SubModel'
If BaseClass and SubClass were more complex, and had more internal logic to them, then you could move up to protocols for these, but it would depend on the particular problem you were solving. I'd generally start with generics for the situation you're describing.
You cannot change the types of stored properties in Swift. But covariant overrides are fine for methods and computed properties. So as long as you make model a computed property, you can use inheritance here, but you must be very careful when doing this to avoid crashes.
The simplest approach is to just add a new property with its own name to SubClass:
var subModel: SubModel { model as! SubModel }
But to get the overriding behavior you're asking for, you need to make model a computed property:
public class BaseClass {
private var _model: BaseModel
public var model: BaseModel { _model }
init(_ model:BaseModel) {
self._model = model
}
}
Then you can override model in SubClass:
public class SubClass: BaseClass {
public override var model: SubModel { super.model as! SubModel }
init(_ model: SubModel) {
super.init(model)
}
}
But note that this is dangerous. It is possible for BaseClass or a subclass of SubClass to break the invariant, and then this will crash. To fix that, you should make _model a let value, and make SubClass final:
public class BaseClass {
private let _model: BaseModel
public var model: BaseModel { _model }
init(_ model:BaseModel) {
self._model = model
}
}
public final class SubClass: BaseClass {
public override var model: SubModel { super.model as! SubModel }
init(_ model: SubModel) {
super.init(model)
}
}
All of this is awkward and hard to keep correct. It's hard to keep class inheritance correct in all OOP languages, and that leads to a lot of bugs. That's why Swift encourages other tools, like generics, to solve these problems. They're much easier to write correctly, and the compiler can catch your mistakes.
I have a function which takes one argument. I wanted my function to accept two object types. How can I do it? Here is the example below:
func accept(user: Customer) {
...
}
It should accept Customer and Employee object reference.
accept(objRefCustomer)
accept(objRefEmployee)
Please help me in this case.
Alternative to super-classing: use protocols
You needn't necessarily use a superclass for this case (if Customer and Employee are struct value types; superclass option is not possible), but can rather use the more generic approach of protocols.
Define a protocol Users which blueprints properties and methods for your Customer and Employee instances (if we let Customer and Employee conform to Users, then we promise that instances of these two structures will have accessible the blueprinted properties and methods):
protocol Users {
var name: String { get }
func printTypeOfUser()
}
Define the Customer and Employee structures, and their conformance to the protocol Users:
struct Customer : Users {
let name: String
init(name: String) { self.name = name }
func printTypeOfUser() {
print("Is a Customer!")
}
}
struct Employee : Users {
let name: String
let id: Int
init(name: String, id: Int) { self.name = name; self.id = id }
func printTypeOfUser() {
print("Is an Employee!")
}
}
Now you can define a generic function where its generic, say T, is type constrained to types conforming to the protocol Users, which in this case is equivalent to the Customer or Employee types
func accept<T: Users>(user: T) {
print("Name of user: \(user.name) [\(user.dynamicType)]")
user.printTypeOfUser()
// do something additional employee-specific if user is an employee?
if let employee = user as? Employee {
print("User is an employee with id: \(employee.id)")
}
}
Example usage of this function for Employee as well as Customer instances:
let employee = Employee(name: "John", id: 1)
let customer = Customer(name: "Sarah")
accept(employee) /* Name of user: John [Employee]
Is an Employee!
User is an employee with id: 1 */
accept(customer) /* Name of user: Sarah [Customer]
Is a Customer! */
Instead of changing your Class structure and code base, you can use AnyObject. It will also be easier for you if, for example, in future you have to make this function accept parameters of class WaterMelon. Making all these classes inherit from a common parent class would be unnecessary overhead, not to mention hectic.
AnyObject is swift equivalent of objective c id. AnyObject is a protocol that can represent an instance of any class type.
It also has a more general counterpart, Any, which can represent any type at all (including structs and enums).
Following code will accept any class type parameter you pass:
func accept(sender : AnyObject) { //Or AnyObject? if you want to handle nil as well
...
}
To access properties of the classes you pass as AnyObject, you can use type casting.
For example below code will check sender type and typecast it for you:
if let customerRef = sender as? Customer {
// ...
// Sender is of customer class type. Use it with customerRef that we created
let customerName = customerRef.dynamicType.sampleNameProperty //Access a property of class Customer
customerRef.funcOfCustomerClass() //Call a method of class Customer
}
else{
//Sender is not of customer class type.
//Then it must be Employee??? Handle cases for employee here.
}
create a protocol, and use it as argument type. protocol can be also empty, it will work anyway. Works with struct and class as well;
ex:
protocol SomeFakeProtocol {}
class SomeClass: SomeFakeProtocol { //code here }
struct SomeStruct: SomeFakeProtocol { //code here }
func someFunction(arg: SomeFakeProtocol) { //code here }
Benefits - you can allow to use only types you want to. And, sure, you can do things like this:
extension String: SomeFakeProtocol {}
You can create a super class called People of Cutomer and Employee.
Then set user as type of People:
func accept(user: People) {
...
}
You don't need a super class, you can just pass an object of type AnyObject and in your function check the type of the object passed:
func accept(user: AnyObject) {
if let usr = user as? Person {
...
}
}
But if you have many types you want to pass you may want to make a protocol or a super class.
I have a CoreDataStore class which has two generic placeholders and can be used for each entity type in the model. The idea is that it fetches an NSManagedObject subclass (based on one of the generic types) from the store, converts it into the appropriate object (based on the other generic type) and returns that object.
The purpose of this behaviour is so I'm keeping the Core Data aspects encapsulated and avoiding passing NSManagedObject instances all around the app.
Example potential usage
This is purely how the usage might look to further demonstrate what I am trying to achieve.
let personStore = CoreDataStore<ManagedPerson, Person>()
let personData = personStore.fetchSomeObject() // personData is a value type Person
I have the following code, separated over several files but shown here in a modified fashion for simplicity.
import Foundation
import CoreData
// MARK: - Core Data protocol and managed object
protocol ManagedObjectProtocol { }
class ManagedPerson: NSManagedObject, ManagedObjectProtocol {
var title: String?
}
class ManagedDepartment: NSManagedObject, ManagedObjectProtocol {
var name: String?
}
// MARK: - Simple struct representations
protocol DataProtocol {
typealias ManagedObjectType: ManagedObjectProtocol
init(managedObject: ManagedObjectType)
}
struct Person {
var title: String?
}
struct Department {
var name: String?
}
extension Person: DataProtocol {
typealias ManagedObjectType = ManagedPerson
init(managedObject: ManagedPerson) {
self.title = managedObject.title
}
}
extension Department: DataProtocol {
typealias ManagedObjectType = ManagedDepartment
init(managedObject: ManagedDepartment) {
self.name = managedObject.name
}
}
class CoreDataStore<ManagedObject: ManagedObjectProtocol, DataObject: DataProtocol> {
func fetchSomeObject() -> DataObject {
var managedObject: ManagedObject // fetch an NSManagedObject
// Error here
return DataObject(managedObject: managedObject)
}
}
The error I am receiving is when I try to initialise the struct in fetchSomeObject:
Cannot invoke initializer for type 'DataObject' with an argument list of type '(managedObject: ManagedObject)'
Obviously the compiler can't figure out that the DataObject (which is restricted to types conforming to DataProtocol) can be initialised with a ManagedObject (which is restricted to types conforming to ManagedObjectProtocol) despite it being declared as such in DataProtocol.
Is there any way to achieve this functionality? Additionally is this a reasonable approach or am I completely off the wall with this?
Update
After a bit of digging it seems that Swift generics are invariant which I believe is causing what I'm running into.
Think your CoreDataStore again, for example, CoreDataStore<ManagedPerson, Department> doesn't make any sense. Why not? Because the Department is a DataProtocol without problem, but its corresponding typealias ManagedObjectType is not ManagedPerson.
The reason why your code won't compile is just the same. Here return DataObject(managedObject: managedObject) you can't initialize an DataObject from an armbitary ManagedObject, only a DataObject.ManagedObjectType is acceptable.
So what you need is a type constraint, add this where clause, your code should work:
class CoreDataStore<ManagedObject: ManagedObjectProtocol, DataObject: DataProtocol
where DataObject.ManagedObjectType == ManagedObject>
I am trying to create a fairly generic UITableView implementation that can display different types of cells. I'm using associated types to specify the data source type, cell type, and so on. I have most of it working really well, but I am having some trouble subclassing the implementation. I'm showing the least amount of code I can below to get the point across.
Here's my high-level architecture:
protocol DGTableViewAble {
typealias DGTableViewItemType
...
var items: [DGTableViewItemType] { get set }
}
class DGTableView: UITableView, DGTableViewAble {
typealias DGTableViewItemType = User
var items: [DGTableViewItemType] = [] { ... }
}
class DGPostsTableView: DGTableView {
typealias DGTableViewItemType = Post
}
...
Things work great when I assign an array of User objects to any DGTableView instance. For example, this works great:
var users: [User] = [...]
var userTableView: DGTableView
userTableView.items = users
However, when I try this:
var posts: [Post] = [...]
var postsTableView: DGPostsTableView
postsTableView.items = posts
I get the error:
Cannot assign a value of type '[Post]' to a value of type '[DGTableViewItemType]'
It seems like the compiler has trouble determining the associated types as I have things set up. Any ideas why? Any suggestions on improving the architecture?
You're not inheriting from DGTableViewAble in your class interface for DGPostsTableView:
class DGPostsTableView: DGTableView, DGTableViewAble {
typealias DGTableViewItemType = Post
...
}
Post isn't declaring viewable protocol?
class DGPostsTableView: DGTableView, DGTableViewAble {
typealias DGTableViewItemType = Post
}
It will also have to conform to said protocol. Maybe marking the unneeded protocol components as optional could solve your issue?