Mutating functions and Properties - swift

I have a struct Town
with a population of 11
struct Town {
var population: Int = 11 {
didSet(oldPopulation){
print("The population has changed to \(population) from \(oldPopulation)")
}
}
}
and I have a mutating function
mutating func changePopulation(amount: Int) {
population += amount
}
I created a zombie class that calls a function that decreases population by 10
final override func terrorizeTown() {
guard var town = town else {
return
}
town.population <= 0 ? print("no people to terrorize") : town.changePopulation(amount: -10)
super.terrorizeTown()
}
when I run this i the main.swift file
var myTown = Town()
var fredTheZombie = Zombie()
fredTheZombie.name = "Fred"
fredTheZombie.town = myTown
//myTown.changePopulation(amount: 0)
print(myTown.population)
fredTheZombie.terrorizeTown()
print(myTown.population)
What I don't understand is the results..
11
The population has changed to 1 from 11
Fred is terrorizing a town!
11
Why is it that when I print (myTown.population) again do i receive a value of 11, when I have called a mutating function on the property. I don't understand.. should the result not be 1, why do i get 11?

It is because the line guard var town = town makes a copy of your Town object. Hence, it is the copy that is mutated.

Related

Why objectWillChange has no effect

I got this sample code about ObservableObject from Apple official website
import Foundation
class Contact: ObservableObject {
#Published var name: String
#Published var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func changeAge() -> Int {
self.age += 1
return self.age
}
}
class Test {
init() {
let john = Contact(name: "John Appleseed", age: 24)
_ = john.objectWillChange
.sink { _ in
print("\(john.age) will change")
}
print(john.changeAge())
}
}
let test = Test()
When running on terminal by swift Contact.swift, the result is only 25, but official website shows the result should be
// Prints "24 will change"
// Prints "25"
Why the first line Prints "24 will change" is not shown?
Thank you.
You have to store the AnyCancellable that's returned by sink, otherwise it cancels the subscription as soon as it's deinitialized when you assign to _.
In your simple example just assigning to a local variable is enough to get the printout that you want, because the variable still lives when you change the age:
let c = john.objectWillChange.sink {...}
But in a real app, you'd want to assign it to a property, which would keep the subscription for the duration that you need. Typically, it's done like this:
class Test {
private var c: Set<AnyCancellable> = []
init() {
let john = Contact(name: "John Appleseed", age: 24)
john.objectWillChange
.sink { _ in
print("\(john.age) will change")
}
.store(in: &c) // store here
print(john.changeAge())
}
}

Swift Choosing Specific Value by Condition

I am applying a simple inheritance project using Swift. However, after completing my code, getting this error all the time.This project creates vehicle class and has 2 child classes as Car and Bicycle.
Car has speed depending on its chosen gear.
So I implement a gearBox dictionary which has gear as key and speed as value.
I am not sure if I could assign speed value by controlling its gear in my Car class.
Any help would be appreciated.
MY ERROR
Playground execution failed: error: psionix.playground:19:34: error: use of 'self' in property access 'speed' before super.init initializes self
super.init(speedVehicle: speed)
^
MY CODE
//: Playground - noun: a place where people can play
import UIKit
class Vehicle{
var speed : Double
init(speedVehicle:Double){
speed = speedVehicle
}
func printDescription(){
print("Vehicle has \(speed) km/h speed by default.")
}
}
class Car : Vehicle{
var speedCar :Double
let gearBox : [Int : Double] = [1:10.5, 2:23.3, 3:33.4, 4:45.0, 5:51.2]
init(){
super.init(speedVehicle: speed)
}
func calculateTravelTime(distance:Double, gearCar:Int)->Double{
var travelTime : Double
travelTime = distance/speedCar
for (gear, speed) in gearBox{
if(gear == gearCar){
speedCar = speed
}
}
return travelTime
}
override func printDescription() {
print("My Car has \(gearBox.count) gears and has \(speedCar) km/h speed.")
}
}
class Bicycle : Vehicle{
var hasBasket : Bool
var distance : Double // in km
var speedBicycle : Double
init(hasBasket: Bool){
super.init(speedVehicle: speed)
self.hasBasket = hasBasket
}
func calculateTravelTime(speed:Double)->Double{
let time = (distance/speed)
print("Bicycle travel time is: \(time)")
return time
}
override func printDescription() {
if(hasBasket == true){
print("My Bicycle has \(speedBicycle) km/h speed and its has a basket.")
}
else{
print("My Bicycle has \(speedBicycle) km/h speed and its has no basket.")
}
}
}
let myVehicle = Vehicle(speedVehicle:3.0)
myVehicle.printDescription()
let myCar = Car()
print("My car travel time is \(myCar.calculateTravelTime(distance: 100.0, gearCar:3)) hours.")
myCar.printDescription()
let myBicycle = Bicycle(hasBasket:true)
print("My bicycle travel time is \(myBicycle.calculateTravelTime(speed: 10)) hours.")
myBicycle.printDescription()
The problem is you are trying to call the initializer of your superclass without actually providing all necessary values as input arguments to super.init(speed:). You need to change your init methods to provide a speed value.
class Car : Vehicle{
...
init(speed: Double){
super.init(speedVehicle: speed)
}
}
class Bicycle : Vehicle{
...
init(speed: Double,hasBasket: Bool){
super.init(speedVehicle: speed)
self.hasBasket = hasBasket
}
}
And do the same for all your Vehicle subclasses. Moreover, the speedCar property is not needed, since Car already has a speed property inherited from Vehicle, same for speedBicycle in Bicycle.
If you want all your subclass instances to have the same speed, just set up a default value in the init method like so:
class Car : Vehicle{
...
init(){
let carSpeed = 50.0
super.init(speedVehicle: carSpeed)
//you could even do super.init(speedVehicle: 50), I just used a variable for clarity
}
}
Moreover, you should never iterate through a dictionary to find a certain key, you can safely access values using subscripting. If the key doesn't exist, the value will simply be nil.
func calculateTravelTime(distance:Double, gearCar:Int)->Double{
if let currentSpeed = gearBox[gear] {
speedCar = currentSpeed
}
return distance/speedCar
}
You are init your Bicycle and Car, before you set it speed. Add speed before you called super.init method
Edit this
init(hasBasket: Bool){
speedBicycle = 0
super.init(speedVehicle: speedBicycle)
self.hasBasket = hasBasket
}
And This
init(){
speedCar = 0
super.init(speedVehicle: speedCar)
}
It makes sense that any Vehicle has an initial speed of either a user desired value, or 0 by default. Same happens with a Car, and since a Car is a Vehicle, you don't need two different speeds.
This would be my refined approach:
import Foundation
class Vehicle: CustomStringConvertible {
var speedInKmH: Double
init(speedInKmH: Double = 0) {
self.speedInKmH = speedInKmH
}
var description: String {
return "Vehicle has \(speedInKmH) km/h speed by default."
}
}
struct GearBox {
var gear: Int
var speedInKmH: Double
}
class Car: Vehicle {
let gearBox: [GearBox] = [GearBox(gear: 1, speedInKmH: 10.5), GearBox(gear: 3, speedInKmH: 33.4), GearBox(gear: 5, speedInKmH: 51.2),
GearBox(gear: 2, speedInKmH: 23.3), GearBox(gear: 4, speedInKmH: 45.0)]
func travelTimeInHoursWith(distanceInKm: Double, gear: Int) -> Double {
if let matchedGearBox = gearBox.filter({ $0.gear == gear }).first {
self.speedInKmH = matchedGearBox.speedInKmH
}
return distanceInKm / speedInKmH
}
override var description: String {
return "My Car has \(self.gearBox.count) gears and has \(self.speedInKmH) km/h speed."
}
}
let toyota = Car()
print(toyota.travelTimeInHoursWith(distanceInKm: 22, gear: 4))
print(toyota)
I did some improvements to make it clear, you can take my code as example and modify your other implemented class/classes

override func doesn't work

I'm learning swift from a book and typed an example of structs and classes. However the example does not work like it should because the program won't decrease the population whenever a zombie is called. I know this is a stupid question but I just can't understand why. Can somebody explain what's wrong? So here's the code:
main.swift
import Foundation
var myTown = Town()
myTown.changePopulation(by:500)
let fredTheZombie = Zombie()
fredTheZombie.town = myTown
fredTheZombie.terrorizeTown()
fredTheZombie.town?.printDescription()
myTown.printDescription()
Town.swift
import Foundation
struct Town {
var population = 5422
var numberOfStopLights = 4
func printDescription() {
print("Population: \(myTown.population), number of stoplights: \ . (myTown.numberOfStopLights).")
}
mutating func changePopulation(by amount: Int){
population += amount
}
}
Monster.swift
import Foundation
class Monster{
var town: Town?
var name = "Monster"
func terrorizeTown(){
if town != nil{
print("\(name) is terrorazing a town!")
}
else{
print("\(name) hasn't found a town to terrorize yet...")
}
}
}
Zombie.swift
import Foundation
class Zombie: Monster {
override func terrorizeTown() {
town?.changePopulation(by: -10)
super.terrorizeTown()
}
}
If you change Town from a struct to a class it will work.
A struct is copied each time you assign it, so when you put zombie.town = myTown it is not the same town, the zombie has a copy of the town and the original town is not changed when the zombie's town is updated. A class will work the way you expect it to.
Since Town is a struct, it will be copied when you assign it to your zombie. Therefore, fredTheZombie.terrorizeTown() terrorizes a copy of myTown - so myTown keeps it's value.
What you want is a class, which will be transferred by reference when you assign it to your monster -- so you only have one Town:
class Town {
var population = 5422
var numberOfStopLights = 4
func printDescription() {
print("Population: \(myTown.population), number of stoplights: \ . (myTown.numberOfStopLights).")
}
func changePopulation(by amount: Int){
population += amount
}
}

how to make deinit take effect in swift

I have a Car class. Let's say a car goes to the junkyard, this car should no longer be counted in the total population. I have the deinit function, but how do I systematically remove a car from the car population? In other words, how do I get the deinit to take effect?
I have a class variable isJunk but don't know how to use it to make this work.
class Car {
static var population: Int = 0
var isJunk: Bool = false
var color: String
var capacity: Int
var driver: Bool?
var carOn: Bool = false
init (carColor: String, carCapacity: Int) {
self.capacity = carCapacity
self.color = carColor
Car.population += 1
}
deinit {
Car.population -= 1
}
func startCar() {
self.carOn = true
}
}
class Car {
static var population: Int = 0
init() {
Car.population += 1
}
deinit {
Car.population -= 1
}
}
var cars: [Car] = [Car(), Car()]
print("Population:", Car.population) // "Population: 2"
// now the second car is removed from array and we have no other references to it
// it gets removed from memory and deinit is called
cars.removeLast()
print("Population:", Car.population) // "Population: 1"
However, the same can be achieved just by asking the number of items in cars array. And that's usually a better alternative than a private counter of instances.
To keep the items in memory you will always need some kind of register (e.g. an array) for them. And that register can keep them counted.
One possibility:
class CarPopulation {
var liveCars: [Car] = []
var junkCars: [Car] = []
}
Or you can keep them in one array and set junk on the car and count non-junk cars when needed:
class CarPopulation {
var cars: [Car] = []
func liveCars() -> Int {
return self.cars.filter { !$0.junk }.count
}
}
There are many possibilities but extracting the counters to some other class that owns the cars is probably a better solution.
The deinit is called when you deallocate your instance of Car (when you entirely get rid of the instance of the object). When you put a Car instance in the junkyard I don't think you want to get rid of the instance of Car, you really just want to change its location. I would suggest a different function to handle changing the location of Car.
Perhaps:
func changeLocation(newLocation: String) {
// Perhaps add an instance variable to 'remember' the location of the car
switch newLocation {
case "junkyard":
Car.population -= 1
default:
// Perhaps check whether previous location was Junkyard and increment
// counter if the Car is coming out of the Junkyard
print("Unrecognized location")
}
}

Swift - Changing variable inside struct?

I'm a beginner in Swift and static programming. Going through Big Nerd Ranch Swift book. What I don't understand is why myTown population is changing, but not fredTheZombie.town? The exercise calls for the town population to never go below zero. This could happen when the population is less than 10. How do I change the fredTheZombie.town population variable?
struct Town {
var population = 5422
var numberOfStoplights = 4
func printTownDescription() {
print("Population: \(myTown.population), number of stoplights: \(myTown.numberOfStoplights)")
}
mutating func changePopulation(amount: Int) {
population += amount
}
}
class Monster {
var town: Town?
var name = "Monster"
func terrorizeTown() {
if town != nil {
print("\(name) is terrorizing a town!")
} else {
print("\(name) hasn't found a town to terrorize yet...")
}
}
}
class Zombie: Monster {
var walksWithLimp = true
final override func terrorizeTown() {
guard town?.population > 0 else {
return
}
if town?.population > 10 {
super.town!.changePopulation(-10)
} else {
super.town!.population = 0
}
super.terrorizeTown()
}
func changeName(name: String, walksWithLimp: Bool) {
self.name = name
self.walksWithLimp = walksWithLimp
}
}
var myTown = Town()
myTown.changePopulation(500)
let fredTheZombie = Zombie()
fredTheZombie.town = myTown
fredTheZombie.terrorizeTown()
fredTheZombie.town?.printTownDescription()
fredTheZombie.changeName("Fred the Zombie", walksWithLimp: false)
myTown.changePopulation(-5915)
print(myTown.population)
print(fredTheZombie.town!.population)
fredTheZombie.terrorizeTown()
fredTheZombie.terrorizeTown()
fredTheZombie.town?.printTownDescription()
Output:
Monster is terrorizing a town!
Population: 5922, number of stoplights: 4
7
5912
Fred the Zombie is terrorizing a town!
Fred the Zombie is terrorizing a town!
Population: 7, number of stoplights: 4
Program ended with exit code: 0
Town is a struct, a value type. When assigning a value type to a different variable (e.g. fredTheZombie.town = myTown), that value type is copied (meaning that a new Town is created with the same values as the original Town).
myTown and fredTheZombie.town are not the same town any more. If you want them to be the same instance, make them a class (a reference type).
See more info on Apple Swift Blog and in Swift Language Guide.
The biggest problem we have are optional structs. The result of unwrapping an optional struct (using town!) is again a copy, a new town. To work around that, you have to:
var myTown = super.town!
myTown.changePopulation(-10)
super.town = myTown // assign back
Whole example:
struct Town {
var population = 5422
var numberOfStoplights = 4
func printTownDescription() {
// note you have to print "self" here, not "myTown"
print("Population: \(self.population), number of stoplights: \(self.numberOfStoplights)")
}
mutating func changePopulation(amount: Int) {
population += amount
}
}
class Monster {
var town: Town?
var name = "Monster"
func terrorizeTown() {
if town != nil {
print("\(name) is terrorizing a town!")
} else {
print("\(name) hasn't found a town to terrorize yet...")
}
}
}
class Zombie: Monster {
var walksWithLimp = true
final override func terrorizeTown() {
guard super.town?.population > 0 else {
return
}
var town = super.town!
if town.population > 10 {
town.changePopulation(-10)
} else {
town.population = 0
}
super.town = town
super.terrorizeTown()
}
func changeName(name: String, walksWithLimp: Bool) {
self.name = name
self.walksWithLimp = walksWithLimp
}
}
In your case there is no need for town to be actually optional. You could just pass it to Monster in a constructor.