Intermediate Swift 3 - Part 4: Properties | Ray Wenderlich

In this Intermediate Swift 3 video tutorial you'll learn about the various properties options that you can add to your objects.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/3536-intermediate-swift-3/lessons/4

Hey, great video, but the solution is in the challenge file, thanx for the quick conversion to swift3/Xcode8 though.

This entire series is really nicely done and very informative. One correction to note, computed properties are not read only (Apple docs). Thanks again.

Hello Brian,

I need help. I’ve been going through your class and struct videos and need clarification.
Here’s my CLASS, with properties, example that maybe you can help with the details.

class List
{
var firstStringArray:[String] = [ ]

var aStringArray = [“A”,“B”,“C”]
var anotherStringArray = [“X”,“Y”,“Z”]
}

If I make an instance of the class:
var myList = List()

And I assign: myList.firstListArray = myLIst.aStringArray

Now, I want to reuse and re-assign firstListArray.
I want it to stop pointing or referencing aStringArray and now point at
ONLY anotherStringArray.

How do I accomplish this?
In other words how do I change it’s “reference”?

Thanks,

Gary
PS
Am I better off using a Struct when my properties are initialized
to literal values or use a Class like my example?

In fact, Array are implemented as Struct, so when you assign an array to another one, you create a copy of it.
This can be simply verified with the following code :

var myList = List()
myList.firstStringArray = myList.aStringArray

print(myList.aStringArray)           // PRINTS ["A", "B", "C"]
myList.firstStringArray.removeLast(1)
print(myList.aStringArray)           // PRINTS ["A", "B", "C"]

The last element of firstStringArray was removed, but since it’s a copy of aStringArray and not a reference, aStringArray isn’t changed.

If you want to use references, you have to encapsulate your array in a “dummy” class, such as :

class arrayReference : CustomStringConvertible
{
    var array:[String] = []
    
    convenience init() {
        self.init(array: [])
    }

    init(array:[String])
    {
        self.array = array
    }

    var description: String
    {
        return "\(array)"
    }
}

Hi Brian,

Great!
Thanks for all your help.

Gary

1 Like

Hi Brian, nice video but i got one doubt here-
In the [(Apple Docs)] (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html) link you have provided,it’s stated that :

‘You must declare computed properties—including read-only computed properties—as variable properties with the var keyword, because their value is not fixed. The let keyword is only used for constant properties, to indicate that their values cannot be changed once they are set as part of instance initialization.’

I understood the need for declaring read-write Computed Properties as variables.But can you please explain me why we need to declare the read-only computed properties as variables?

If you use a read only computed property, the value returned can change too (even if it cannot explicitly be set), thus you need to use the var keyword.

Simple example :

class CounterClass {

    private var count = 0    
    var numberOfTimesThisVariableWasUsed : Int{
        get {
            count += 1
            return count
        }
    }
}

let counter = CounterClass()
let value1 = counter.numberOfTimesThisVariableWasUsed
// value1 = 1
let value2 = counter.numberOfTimesThisVariableWasUsed
// value2 = 2

The numberOfTimesThisVariableWasUsed is a read-only computed property, but this value is changed each time you get it. A var keyword is needed since the value can change at runtime.

1 Like

Hey,

In order to fire the didSet using the initializer you might need to use “defer”

Brian (@vegetarianzombie),

I was doing the challenge and instead of using if statements I wanted to use a switch statement, but I got errors: “Expression pattern of type ‘Bool’ cannot match values of type ‘Double’”. My code looked like this:

struct FuelTank {
    var lowFuel: Bool
    var level: Double {
        didSet {
            let min = 0.0
            let max = 1.0
            let isLowFuel = 0.1
            switch level {
            case level > max :
                level = max
            case level < min :
                level = min
            case level <= isLowFuel :
                lowFuel = true
            default :
                lowFuel = false
            }
        }
    }
}

Can’t you use switch in didSet?

Bob

The first line of Apple documentation about switch summarize it :

A switch statement considers a value and compares it against several possible matching patterns.

The matching patterns must be of the same type of your value. So in your switch, you should use Double values for each case. Instead, you are using Bool :
For example level > max results in a boolean, it’s true or false.

You can use boolean in a if…else control flow :

if level > max {
    level = max
} else if  level < min {
    level = min
} else if level <= isLowFuel {
    lowFuel = true
} else {
    lowFuel = false
}

There is a switch -way to do so, but I consider it a little ugly :

switch level {
    case let x where x > max :
        level = max
    case let x where x < min :
        level = min
    case let x where x <= isLowFuel :
        lowFuel = true
    default:
        lowFuel = false
}

Totally unrelated to your question, but you should split your tests in two part, because if you initialise a FuelTank with a value lower than min, then you will not pass it the test which will set lowFuel to true.
Also, didSet is not called during the initialization, so lowFuel will not be be set to true if you pass a value lower than isLowFuel as parameter.

Here is an example of functional FuelTank class :

struct FuelTank {

    private static let min = 0.0
    private static let max = 1.0
    private static let isLowFuel = 0.1

    var lowFuel: Bool = false
    var level: Double {
        didSet {
            testLevel()
        }
    }

    private mutating func testLevel() {
        if level < FuelTank.min {
            level = FuelTank.min
        } else if level > FuelTank.max {
            level = FuelTank.max
        }
        lowFuel = (level <= FuelTank.isLowFuel)
    }

    public init (level : Double){
    self.level = level
        testLevel()
    }
}  

Don’t hesitate to ask if you have further questions :slight_smile:

Great Video.

Just a comment on the Ub3r H4ck3r Challenge solution: I believe having the students use a stored property for the lowFuel Boolean is a design mistake. Here is my solution:

struct FuelTank {
    var level : Double {
        didSet {
            if level > 1.0 {
                level = 1.0
            } else if level < 0.0 {
                level = 0.0
            }
        }
    }
    var lowFuel : Bool {
        return level  < 0.10
    }
}

The solution file requires you to answer the “lowFuel” question when creating a tank, which makes no sense.

var theTank = FuelTank(lowFuel: false, level: 1.0)

Where as with my solution:

var theTank = FuelTank(level: 1.0)

To me a property like 'lowFuel" screams to be a computed property. Is there some design element that I am missing that should have lowFuel be a stored Property?

EDIT: I was thinking you might want to have the lowFuel actively set because you want to “turn-on” a low fuel indicator when this situation happens. However, you could still check the lowFuel computed property with the level didSet and the react if required.

You are right, a computed property could be used here since the value of ‘lowFuel’ is always dependent of the value of ‘level’, and thus cannot be set explicitly.

@sedawk Thanks!! This made much more sense!

i’ve been thinking about the solution of the challenge and still can’t figure how to do it. When I checked the solution, i tried initializing a tank var but it seems to not solve the issue.

var tank = FuelTank(lowFuel: false, level: 0)
print(“Fuel level: (tank.level)”) //will output 0.0
print(“is fuel low? : (tank.lowFuel)”) //will output false

i believe @sedawk 's answer and @micazeve 's explanation is much more sensible …
Hope this is changed for future version of this tutorial though. (Swift4 and up)

Hi All
I am using the following code for the uber hacker challenge.


struct FuelTank {
var lowFuel: Bool{
return level < 0.01
}
var level: Double {
willSet {
if newValue > 1.0 {
level = 1.0
} else if level < 0 {
level = 0
}
}
}
}
var tank1 = FuelTank(level: 12.0)
print(tank1.lowFuel)
print(tank1.level)


The outputs are false & 12.0, respectively.
Shouldn’t it be false & 1.0?

You need to use a didSet property observer which runs just after the level variable’s value is set and change the tank’s fuel level value right after you initialize the tank1 object with the FuelTank structure’s default initialiser like this:

struct FuelTank {
  var lowFuel: Bool{
    return level < 0.01
  }
  var level: Double {
    didSet {
      if oldValue > 1.0 {
        level = 1.0
      } else if level < 0 {
        level = 0
      }
    }
  }
}
var tank1 = FuelTank(level: 12.0)
tank1.level = 10.0
print(tank1.lowFuel) // false
print(tank1.level) // 1.0

Please let me know if you have any other questions or issues regarding the whole thing.

1 Like

Thanks a lot.
It worked.

We are in the process of updating the whole video course for Swift 4 and Xcode 9 after all so this thread and topic is closed for further questions and issues regarding it for now until then.