Ray Wenderlich Course Part 12 (up to lesson 110)
Here we go - the last of the 'theoretical' Ray sections of lessons before the next project. I'm not expecting to get all of this, after how tricky the last couple of sections have been. Again, I will type along where I can and keep it at normal speed.
Classes vs Structures
So here is a simple structure (already typed out for me):
Classes vs Structures
So here is a simple structure (already typed out for me):
struct PersonStruct {
var firstName: String
var lastName: String
var fullName: String {
return "\(firstName) \(lastName)"
}
}
To make a class, it is simple enough. Same as above.
class PersonClass {
var firstName: String
var lastName: String
var fullName: String {
return "\(firstName) \(lastName)"
}
}
The difference of course is that inits are needed. I know all about this from before.
Ray mentions Heap vs Stack views. Functions will on the stack - will be used then gone again. Classes are on the heap, which means that they are still there...they are reference types.
var person1 = PersonStruct(firstName: "Kiran", lastName: "Johal")
var person2 = person1
person1.firstName = "Anvir"
person2.firstName
A good example of how structs are value types. In the above, person2.firstName still = Kiran!
var person3 = PersonClass(firstName: "Josh", lastName: "Gonet")
var person4 = person3
person4.firstName = "Bob"
person3.firstName
Interestingly, person3 equals Bob too! This is why it is a reference type. All fine.
So here is the key difference:
STRUCTURES are VALUE types - considered equal if values are equal
CLASSES are OBJECT types - these are unique
Speed is a big factor - for creating many instances and only exist for a short period of time
If unsure - start with structs, then covert to classes!
All that sounds clear to me.
Challenge!
## CLASSES VS STRUCTURES
Imagine you're writing a movie-viewing application in Swift. Users can create lists of movies and share those lists with other users.
Create a `User` and a `List` class that uses reference semantics to help maintain lists between users.
- `User` - Has a method `addList(_:)` which adds the given list to a dictionary of `List` objects (using the `name` as a key), and `list(forName:) -> List?` which will return the `List` for the provided name.
- `List` - Contains a name and an array of movie titles. A `print` method will print all the movies in the list.
- Create `jane` and `john` users and have them create and share lists. Have both `jane` and `john` modify the same list and call `print` from both users. Are all the changes reflected?
*/
// TODO: Write solution here
class List {
let name: String
let movieTitles: [String] = []
init(name: String) {
self.name = name
}
func printMovies() {
print("Movie List \(name)")
for movie in movieTitles {
print(movie)
}
print("\n")
}
}
class User {
var lists: [String: List] = [:]
func addList(_ list: List) {
lists[list.name] = list
}
func list(forName name: String) -> List? {
return lists[name]
}
}
This was MASSIVELY confusing! I got some but not a lot of it. Very hard to make sense of.
The next part of the challenge I am skipping over, as it's too complicated. Typical!
Your challenge here is to imagine a set of objects to support a t-shirt store. Decide if each object should be a class or a struct, and why. You don't need to write any code; just decide whether to use a class or a struct for each.
- `TShirt` - Represents a shirt style you can buy. Each `TShirt` has a size, color, price, and an optional image on the front.
- `User` - A registered user of the t-shirt store app. A user has a name, email, and a `ShoppingCart` (below).
- `Address` - Represents a shipping address, containing the name, street, city, and zip code.
- `ShoppingCart` - Holds a current order, which is composed of an array of `TShirt` that the `User` wants to buy, as well as a method to calculate the total cost. Additionally, there is an `Address` that represents where the order will be shipped.
OK, here goes!
TShirt - struct. Seems simple enough to have this as a value type.
User - I would go for class because people are more eomplex and the reference type makes more sense here
Address - struct. Simple, clear information lends itself to this.
ShoppingCart - class, just because there are so many different elements to it and it would make more sense to be a reference type....
Solution:
- TShirt: A TShirt can be thought of as a value, because it doesn't represent a real shirt, only a description of a shirt. For instance, a TShirt would represent "a large green shirt order" and not "an actual large green shirt". For this reason, TShirt can be a struct instead of a class.
- User: A User represents a real person. This means every user is unique so User is best implemented as a class.
- Address: Addresses group multiple values together and two addresses can be considered equal if they hold the same values. This means Address works best as a value type (struct).
- ShoppingCart: The ShoppingCart is a bit tricker. While it could be argued that it could be done as a value type, it's best to think of the real world semantics you are modeling. If you add an item to a shopping cart, would you expect to get a new shopping cart? Or put the new item in your existing cart? By using a class, the reference semantics help model real world behaviors. Using a class also makes it easier to share a single ShoppingCart object between multiple components of your application (shopping, ordering, invoicing, ...).
*/
So I basically aced that! I've got the key understanding it seems, but putting the code into practice is another story!
Inheritance
When one class inherits functionality from another.
Good examples here:
struct Grade {
var letter: Character
var points: Double
var credits: Double
}
class Person {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
class Student: Person {
var grades: [Grade] = []
func recordGrade(_ grade: Grade) {
grades.append(grade)
}
}
So student has everything from person plus whatever is added.
So SUBCLASS for the class that has inherited.
BASE CLASS or SUPERCLASS for the original/one that has been inherited from.
class BandMember: Student {
var minimumPracticeTime = 2
}
class OboePlayer: BandMember {
override var minimumPracticeTime: Int {
get {
return super.minimumPracticeTime
}
set {
super.minimumPracticeTime = newValue / 2
}
}
}
Good examples to show many classes and the override for one of the variables.
OboePlayer is CHILD of BandMember
BandMember is PARENT of OboePlayer
Polymorphism - treating objects differently based on context.
Downcasting - using as? as a way of unwrapping to see if a let/var is of a certain type - e.g. a superclass of its initial declaration.
So 'as' on its own is 'up-classing' - moving to a more parent class
As? would be downcast
As! would be FORCED downcast
Class hierarchy - makes sense
Initializers
I always found these tedious with the courses with Pasan as I found myself doing inits for everything, including subclasses which didn't even seem to change!
It's since been clarified that I don't need to do inits unless they have CHANGED from the parent class!
So anything without an initial value needs to be initialised. Fair enough.
Phase 1 - you must initialise all of the stored properties. You can't do any functions etc. until these have been initialised.
Phase 2 - call any methods once inits have been correctly set up
You can use 'required' as an alternative init. It's like override.
Convenience - shortcut. Another way to call an init.
Not too sure when to use these!
OK, Designated Init - must be called from the parent class
Create a class named `Animal` that has a single stored property named `name`, that is a `String`. It should have a required initializer that takes `name` as a parameter, and a function `speak()` that does nothing.
*/
// TODO: Write solution here
class Animal {
let name: String
required init(name: String) {
self.name = name
func speak() {
}
}
}
This was all good but I can't figure out the next bit.....
class Dog: Animal {
let numTricksLearned: Int
required init(name: String) {
numTricksLearned = 0
super.init(name: name)
speak()
}
override func speak() {
print("Bow Wow!")
}
}
Well I wasn't too far away!
In an extension, add a convenience initializer to `Dog` that defaults the dog's name to your favorite dog's name, with however many tricks the dog has learned.
*/
// TODO: Write solution here
extension Dog {
convenience init() {
self.init(name: "George", numTricksLearned: 4)
}
}
Again, a lot of this was familiarisation with the syntax etc.
When Should you Subclass?
Basically you can always choose this.
Things to keep in mind:
Each class should only have ONE concern
Type safety (could get confusing with lots)
Shared base classes - again a lot to monitor
Extensibility - not sure about this one
Protocols may be better! Linked to identity
Memory Management
Heap - Ray mentioned this before.
Reference count - OK so if the same value is referred to then the reference count goes up by one. A variable or a constant.
ARC - automatic reference counting
If you make the value of that referred to variable nil, then the reference count goes down.
Weak - must be an optional type. That links to what I had before with labels on Xcode! So it's to avoid the reference counting? Sort of get it.
There we go! That was generally better than Structures and MUCH easier than collections. I definitely have picked up some useful key points but am in a rush so will leave it here. At some point tomorrow - possibly on a train or in the evening before bed - I will be going on to the next Xcode project. Apparently it's with a guy called Brian! Now, I will have to be ruthless here and decide within a few lessons if it's worse continuing here, or giving another course a go with some more hands on practice. If I can't find decent Udemy ones that I can follow then I will go back to Angela's - she's been the best so far! I will leave Treehouse for now as I don't feel equipped enough to call myself an 'Intermediate!'.
Comments
Post a Comment