How to pass generic property in swift? - 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")

Related

Overlapping accesses to 'but modification requires exclusive access; consider copying to a local variable'

an employee can own more than one animal, but an animal must have only one employee.
First, I add an employee. Then when adding an animal, I select the employee.
After selecting the employee, I press the save button.
I want to transfer the animal I just added to that employee.
I want to take the employee's id and add it to the employee attribute of the animal I added.
Animal Model:
struct Animal {
let id: String?
var name: String?
var sound: String?
var age: Double?
var waterConsumption: Double?
var employee: Employee?
var animalType: String?
}
Employee Model:
struct Employee {
let id: String?
var name: String?
var lastName: String?
var age: Int?
var gender: String?
var salary: Double?
var experienceYear: String?
var animals: [Animal]?
}
ZooManager:
Error is on this line: newAnimal.employee?.animals?.append(newAnimal)
Overlapping accesses to 'newAnimal.employee', but modification requires exclusive access; consider copying to a local variable
protocol ZooManagerProtocol {
var animals: [Animal] { get set }
var employees: [Employee] { get set }
func addNewAnimal(_ model: Animal)
}
class ZooManager: ZooManagerProtocol {
static var shared = ZooManager()
var animals: [Animal] = []
var employees: [Employee] = []
private init() { }
func addNewAnimal(_ model: Animal) {
var newAnimal = model
guard var employee = employees.filter({ $0.id == model.employee?.id }).first else { return }
newAnimal.employee = employee
newAnimal.employee?.animals?.append(newAnimal)
dump(newAnimal)
}
func addNewEmployee(_ model: Employee) {
employees.append(model)
}
}
The solution recommended by the compiler works
func addNewAnimal(_ model: Animal) {
var newAnimal = model
guard var employee = employees.filter({ $0.id == model.employee?.id }).first else { return }
employee.animals?.append(newAnimal)
newAnimal.employee = employee
}
I've never seen this error before, hopefully someone can add a good answer explaining the why and not just the how!
EDIT:
Here's the why https://github.com/apple/swift-evolution/blob/main/proposals/0176-enforce-exclusive-access-to-memory.md

What is the difference between { } and = when assigning an enum to a variable in Swift?

What is the difference between { } and = when assigning an enum to a variable in Swift?
Why would you say var type: ItemType { .recipe } over var type: ItemType = .video.
Does the {} indicate that it is a computed property?
Also, is the { get } needed after the type in the protocol?
enum ItemType: String, Decodable {
case video
case recipe
}
protocol Item: Decodable {
var type: ItemType { get }
var title: String { get }
var imageURL: URL { get }
}
struct Video: Item {
var type: ItemType = .video
var title: String
var imageURL: URL
var url: URL
var duration: String
var resolution: String
}
struct Recipe: Item {
var type: ItemType { .recipe }
var title: String
var imageURL: URL
var text: String
var ingredients: [String]
}
Does the {} indicate that it is a computed property?
Yes.
Also, is the { get } needed after the type in the protocol?
To specify that the property, whether it be computed or stored, needs to support at least a getter. The setter is optional.

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?

Passing generic parameters in a generic class

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)

Implement generic method in swift

I am implementing Protocol Oriented approach in Swift as codes below. The concept seems fun but I hope you get the idea. The problem for me is how to implement a generic function for that repeated printing tasks. Thank you in advances.
protocol Food {
var name: String { get }
}
struct Grass: Food {
var name: String { return "Grass" }
var calcium: Float!
}
struct Rice: Food {
var name: String { return "Rice" }
var calories: Float!
}
struct Insect: Food {
var name: String { return "Insect" }
var fiber: Float!
}
protocol Eat {
associatedtype food: Food
var name: String { get }
var itsFood: food { get }
}
struct Cow: Eat {
typealias food = Grass
var name: String { return "Cow" }
var itsFood: food {return food(calcium: 100)}
}
struct People: Eat {
typealias food = Rice
var name: String { return "People" }
var itsFood: food {return food(calories: 1000)}
}
struct Reptile: Eat {
typealias food = Insect
var name: String { return "Reptile" }
var itsFood: food {return food(fiber: 300)}
}
let cow = Cow()
print(cow.name)
print(cow.itsFood.name)
print(cow.itsFood.calcium)
let people = People()
print(people.name)
print(people.itsFood.name)
print(people.itsFood.calories)
let reptile = Reptile()
print(reptile.name)
print(reptile.itsFood.name)
print(reptile.itsFood.fiber)
If I understood correctly, you want a way to write a function that prints out an Eat conformer's name, food name, and the food's nutritional value.
Your current Food protocol has not got enough information on the food's nutritional value (calcium, calories, fiber). You should edit your protocol:
protocol Food {
var name: String { get }
var nutritionalValueName: String { get }
var nutritionalValue: Float! { get }
}
And implement the 2 new properties in the Food conformers. Here's an example:
struct Grass: Food {
var name: String { return "Grass" }
var calcium: Float!
var nutritionalValue: Float! { return calcium }
var nutritionalValueName: String { return "Calcium" }
}
Now, you can write a function. Note that since Eat has an associated type, it cannot be used as a parameter type directly, you need to introduce a generic parameter T and constrain it to Eat:
func printEat<T: Eat>(eat: T) {
print(eat.name)
print(eat.itsFood.name)
print("\(eat.itsFood.nutritionalValueName): \(eat.itsFood.nutritionalValue!)")
}
The function body is pretty self-explanatory.
You can call it like this:
printEat(eat: Cow())
printEat(eat: People())
printEat(eat: Reptile())
Output:
Cow
Grass
Calcium: 100.0
People
Rice
Calories: 1000.0
Reptile
Insect
Fiber: 300.0
I would use something like below to be able to use basic functions in one place. You would need super classes for common task functions, protocols would not be enough for that.
class Food {
var name: String? { return nil }
var calcium: Float?
var calories: Float?
var fiber: Float?
}
class Grass: Food {
override var name: String? { return "Grass" }
init(_ calcium: Float) {
super.init()
self.calcium = calcium
}
}
class Rice: Food {
override var name: String? { return "Rice" }
init(_ calories: Float) {
super.init()
self.calories = calories
}
}
class Insect: Food {
override var name: String? { return "Insect" }
init(_ fiber: Float) {
super.init()
self.fiber = fiber
}
}
protocol Eat {
var name: String? { get }
var itsFood: Food? { get }
func printInfo()
}
class Animal: Eat {
var name: String? { return "Cow" }
var itsFood: Food? { return Food() }
func printInfo() {
print(name ?? "")
print(itsFood?.name ?? "")
print(itsFood?.calcium ?? 0)
}
}
class Cow: Animal {
override var name: String? { return "Cow" }
override var itsFood: Grass {return Grass(100) }
}
class People: Animal {
override var name: String? { return "People" }
override var itsFood: Food {return Rice(1000)}
}
class Reptile: Animal {
override var name: String? { return "Reptile" }
override var itsFood: Food {return Insect(300)}
}
let cow = Cow()
cow.printInfo()
Check out this :
struct Fruit {
let fruitName : String
let color : String
init(_ name: String,_ color: String) {
self.fruitName = name
self.color = color
}
}
let fruit1 = Fruit("Apple", "Red")
let fruit2 = Fruit("Grapes", "Green")
let fruitStack = Stack<Fruit>()
fruitStack.push(fruit1)
fruitStack.push(fruit2)
let fruitFfromStack = fruitStack.pop()
print("Fruit popped from Stack, Name : \(String(describing: fruitFfromStack?.fruitName)) ,Color : \(String(describing: fruitFfromStack?.color))")
let fruitFfromStack1 = fruitStack.pop()
print("Fruit popped from Stack, Name : \(String(describing: fruitFfromStack1?.fruitName)) ,Color : \(String(describing: fruitFfromStack1?.color))")
https://reactcodes.blogspot.com/2019/01/generic-stack-implementation-with.html