Passing generic parameters in a generic class - swift

Is it possible to get something like below using generics in swift? Any help would be appreciated more than anything else.
class ABC {
var title: String?
}
class Option: <T> {
var name: String?
var data: T?
}
let object1 = ABC()
let option = Option(name: "Test", data: object1)
// Getting data again
let data = option.data

Here is how you can use Generics in Swift.
protocol PPP {
var title: String? {get set}
}
class ABC: PPP {
var title: String?
}
class XYZ: PPP {
var title: String?
}
class Option<T> {
var name: String?
var data: T?
init(name: String?, data: T?) {
self.name = name
self.data = data
}
}
let object1 = ABC()
let option1 = Option(name: "Test1", data: object1)
let object2 = XYZ()
let option2 = Option(name: "Test2", data: object2)
Since classes in Swift doesn't have a default initializer, so create one that accepts 2 parameters - String? and T?.
You can use type(of:) method to identify the type of an object.
print(type(of: option1.data)) //ABC
print(type(of: option2.data)) //XYZ
You can use protocol to access title in both ABC and XYZ.
Conform ABC and XYZ to protocol PPP and implement its title property in both ABC and XYZ.
print(option1.data?.title)
print(option2.data?.title)

Related

How to disable default nil-initialization of Optional

Let's say I have these two structures:
struct DataOne {
let id: String
// ...
}
struct DataTwo {
let id: String
// ...
}
And there is a separate conversion function:
extension DataOne {
func convertToDataTwo() -> DataTwo {
.init(
id: self.id,
// ...
)
}
}
At some point, the var name: String? field is added to both structures:
struct DataOne {
let id: String
var name: String?
// ...
}
struct DataTwo {
let id: String
var name: String?
// ...
}
But the assembly does not swear because the field is optional.
And when converting, the name field is lost.
Is it possible to disable the autofill of the option or to call the warnings?
I tried to find such a rule in SwiftLint, but I didn't find it.
If your var's type is written T? with no default value, and Swift synthesizes a memberwise init for your type, then the synthesized init uses a default value of nil for that var.
However, if your var's type is written Optional<T> with no default value, then the synthesized init does not use a default value.
So write this instead:
struct DataOne {
let id: String
var name: Optional<String>
// ...
}
struct DataTwo {
let id: String
var name: Optional<String>
// ...
}
Or write out your init instead of letting the compiler synthesize it:
struct DataOne {
let id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
struct DataTwo {
let id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
You can use Xcode's Editor > Refactor > Generate Memberwise Initializer command to write most of the init for you, then delete the = nil default:

How to pass generic property in swift?

Let's assume I have these two models:
struct Vegetarian: Menus {
var id: String? = UUID().uuidString
var vegiDishes: [String]?
var veganDishes: [String]?
}
struct Vegan: Menus {
var id: String? = UUID().uuidString
var veganDishes: [String]?
}
They are both conforms to this protocol:
protocol Menus: Identifiable, Codable, Hashable {
var id: String? {get set}
}
extension Menus {
var vegiDishes: [String]? {
get{ return [] } set{}
}
var veganDishes: [String]? {
get{ return [] } set{}
}
}
I have this object:
var myMenu: Vegetarian = Vegetarian()
If I want to add a dish, I'll do this:
addDish(menu: myMenu, dish: "Some Vegetarian Dish")
But since the two models have a different type of dishes properties, I'll have to have something like this:
func addDish<T: Menus> (menu: T, dish: String) {
switch menu {
case is Vegetarian:
var newMenu: Vegetarian = Vegetarian()
newMenu.veganDishes?.append(dish)
case is Vegan:
var newMenu: Vegan = Vegan()
newMenu.veganDishes?.append(dish)
default:
print("no such type")
}
}
Is there a way to simplify my method so I could specify a property in the call?
Let's say, something like this:
addDish(menu: myMenu, dish: "Some Vegetarian Dish", genericProperty: veganDishes)
Which will make my method look like this?
func addDishGenericProperty<T: Menus> (menu: T, dish: String, genericProperty?) {
menu.genericProperty.append(dish)
}
This seems perfect for a key-path! Or more specifically, a WritableKeyPath.
Since Vegetarian is a struct, it is a value type. This means we can't just set vegiDishes for example and expect that value to be reflected to our myMenu from outside the function. Instead you can use inout so myMenu is changed.
I had to also clear up the Menus and the types quite a bit, so they use non-optional arrays instead. If you need to check if the array is empty, use myMenu.vegiDishes.isEmpty.
Here is an example of how you can achieve all this:
protocol Menus: Identifiable, Codable, Hashable {
var id: String { get }
}
struct Vegetarian: Menus {
enum CodingKeys: CodingKey {
case vegiDishes
case veganDishes
}
let id = UUID().uuidString
var vegiDishes: [String]
var veganDishes: [String]
init(vegiDishes: [String] = [], veganDishes: [String] = []) {
self.vegiDishes = vegiDishes
self.veganDishes = veganDishes
}
}
struct Vegan: Menus {
enum CodingKeys: CodingKey {
case veganDishes
}
let id = UUID().uuidString
var veganDishes: [String]
init(veganDishes: [String] = []) {
self.veganDishes = veganDishes
}
}
Adding dishes:
func addDish<T: Menus>(menu: inout T, dish: String, kind: WritableKeyPath<T, [String]>) {
menu[keyPath: kind].append(dish)
}
var myMenu: Vegetarian = Vegetarian()
addDish(menu: &myMenu, dish: "Some Vegetarian Dish", kind: \.vegiDishes)
print(myMenu.vegiDishes)
Prints:
["Some Vegetarian Dish"]
Or alternatively, you could literally just do the following if possible in your case:
myMenu.vegiDishes.append("Some Vegetarian Dish")

How to create a protocol conform Comparable in Swift

I have a use case to compare the enployee's rank. Here is what I want to do:
protocol Enployee: Comparable {
var id: String { get }
var rank: Int { get }
var name: String { get }
var type: String { get }
}
extension Enployee {
static func <(lhs: Enployee, rhs: Enployee) -> Bool {
return lhs.rank < rhs.rank
}
}
But I got the following error:
Protocol 'Enployee' can only be used as a generic constraint because it has Self or associated type requirements
Then I changed my code:
extension Enployee {
static func <(lhs: Self, rhs: Self) -> Bool {
return lhs.rank < rhs.rank
}
}
I can compile it. But when I continue working on my user case:
struct Engineer: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Engineer"
}
struct Manager: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Manager"
}
let staff1 = Engineer(id: "123", rank: 2, name: "Joe")
let staff2 = Engineer(id: "124", rank: 2, name: "Frank")
let staff3 = Manager(id: "101", rank: 10, name: "John")
public struct Department<T: Comparable> {
}
let queue = Department<Enployee>()
I got another error message:
Protocol 'Enployee' as a type cannot conform to 'Comparable'
Any idea?
The error message tells you what the problem is. Having declared Department<T: Comparable> you cannot resolve T as Enployee; it is a protocol, not a type conforming to Comparable (such as Engineer).
A generic has to be resolved to one type. It would be legal to declare Department<T: Enployee> if that is what you really mean. But then you are setting yourself up for multiple Department types, a department of Manager and a department of Engineer. That is probably not what you want, so this is a bad use of a generic.
Moreover, type strings are a horrifically bad smell:
struct Engineer: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Engineer" // uck
}
It seems much more likely that you want a class and subclasses here. Why fight against OOP?

What is the simplest way to instantiate a new Codable object in Swift?

I have a Codable class:
class Task: Codable {
var name: String
}
When I try to instantiate it:
let newTask = Task()
allTasks.append(newTask)
It gives me error:
Missing argument for parameter 'from' in call
All I want is to insert a new object (newTask) into an array. What is the simplest way to do this?
You can inherit the initializer from NSObject:
class Task: NSObject, Codable {
var name: String = ""
}
let newTask = Task()
If you don't want to inherit NSObject, then just create your own initializer:
class Task: Codable {
var name: String?
init() {
}
}
If you don't want to make name optional (or set it to a default), it has to be initialized in init() such as:
class Task: Codable {
var name: String
init(withName name: String) {
self.name = name
}
}
let newTask = Task(withName: "ikevin8me")
Yet another solution is to use struct
struct Task: Codable {
var name: String
}
let task = Task(name: "myname")
Your Task class doesn't provide its own initializer so it gets the one defined in the Codable protocol (which it gets from the Decodable protocol).
Either add your own explicit init that takes a name parameter or change your class to a struct. Either way, you need to create a Task by passing in a name value so the name property can be initialized.
None of this addresses the fact that the code you posted makes no use of Codable so maybe there is no need for your class (or struct) to conform to Codable.
The Task class doesn't provide any initializer to initialize an object that's why it's taking initializer from Codable protocol, Provide your own initializer to initialize an object.
Usage:
1.With Class
class Task: Codable {
var name: String
init(_ name : String) {
self.name = name
}
}
var task = Task("Jarvis")
2.With struct:
struct Task: Codable {
var name: String
}
let task = Task(name: "jarvis")
I would not assign a default value to the property but implement the expected init method and a convenience variant that didn't take any arguments or alternatively have a factory method that creates an "empty" Task
Here is the code with both options
class Task: Codable {
var name: String
init(_ name: String) {
self.name = name
}
convenience init() {
self.init("")
}
static func emptyTask() -> Task {
return Task("")
}
}
You could instantiate a object from json (for example when you use an API) like this:
struct Person: Codable {
var firstName: String
var lastName: String
var age: Int
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age = "age"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
age = try container.decode(Int.self, forKey: .age)
}
init(data: Data) {
let decoder = JSONDecoder()
do {
let userDecoded = try decoder.decode(Person.self, from: data)
self = userDecoded
}
catch {
age = 0
firstName = ""
lastName = ""
}
}
func fullName() -> String {
return "\(self.firstName) \(self.lastName)"
}
}

init new class instance with args object parameter which conforms to a protocol

I've a class and a protocol into myModel.swift
public protocol IModelArgs{
var name: String { get set};
var surname: String { get set};
}
public class Model {
var name: String;
var surname: String;
init(args: IModelArgs) {
self.name = args.name;
self.surname = args.surname;
}
}
IModelArgs is the protocol of arguments object passed to Model constructor.
Into another file I need to create the instance of Model class, but I'm not able to pass args object to constructor: What I'm wrong?
let myM = Model(args: ("T1","T2"));
The main problem in your case that ("T1","T2") is a tuple and not the object that conform your protocol. In your case it should look like this:
struct ArgsObject: IModelArgs {
var name: String
var surname: String
}
let myM = Model(args: ArgsObject(name: "someName", surname: "someSurname"))
But if you want to use the protocol only to pass an object to the constructor, you do not need to do this. Create struct for it like this:
struct ArgsObject {
let name: String
let surname: String
}
class Model {
var name: String
var surname: String
init(args: ArgsObject) {
self.name = args.name
self.surname = args.surname
}
}
let myM = Model(args: ArgsObject(name: "someName", surname: "someSurname"))
Few optional comments
Don't use ; and protocol names like ISomething, it's not the Java