Lynda Course - Swift 5 Essential Thinking (Part 7)

Last part of the course! I'm going spend around half an hour now on this and the rest tomorrow, if/when needed. Let's do this!

Start time - 21:00

Enumerations

All of the declaring and creating no problems. I want to see the use of assigning values etc. That bit I'm not so sure about.

// Declaring an enum
enum GameState {
    case Completed, Initialzing, LoadingData
}

// Storing and switching on an enum value

var currentState = GameState.Completed

switch currentState {
    
case .Completed:
    print("All tasks complete")
case .Initialzing:
    print("Init in process")
case .LoadingData:
    print("Data is loading")

}

Raw and Associated Values

You can do this - 

enum NonPlayableCharacters: String {
    case Villager
    case Blacksmith
    case Trader
}

But no other info - quite limited. Can be accessed with using .rawValue. 

*Paused for 10 minutes

enum NonPlayableCharacters: String {
    case Villager = "Focused on gathering resources"
    case Blacksmith = "Prepares weapons"
    case Trader = "Trades goods"
}

// Associated values
enum PlayerState {
    case Alive
    case Unconscious(level: Int)
    case Expired(debugError: String)
    
    func evaluateCase() {
        switch self {
        case .Alive:
            print("Still alive!")
        case .Unconscious(let restartLevel):
            print("Restart at \(restartLevel)level")
        case .Expired(let message):
            print(message)
        }
    }
}

PlayerState.Unconscious(level: 4).evaluateCase()

OK some good stuff there! A couple of key things - the argument label and type. And a switch statement within a function. 

You can add a type with the the case and with the 

Have to stop now. But will do more tomorrow. 

Stopped at 21:28 (18 minutes so far)

Restarted 2 days later at 12:53

OK I'm back! And ready to complete this course! At that point, I will look to see whether I should go with this subscription, or look more into Reinder's course... Anyway, last bit!

Introducing Protocols

I get the point of these - blueprints for behaviour; a contract that then something else has to fill...but have always wanted more practice. So here we go!

Protocols can be adopted by a class or struct independently.

// Declare a protocol
protocol Collectable {
    var name: String { get }
    var price: Int { get set }
    
    init(withName: String, startingPrice: Int)
    
    func collect() -> Bool
}

// Protocol adoption

class Item: Collectable {
    var name: String
    
    var price: Int
    
    required init(withName: String, startingPrice: Int) {
        self.name = withName
        self.price = startingPrice
    }
    
    func collect() -> Bool {
        print("Item collected!")
        return true
    }
    
    
}

let potion = Item(withName: "Mead", startingPrice: 33)


potion.collect()


The benefit of protocols has always been unclear to me. But I'm starting to see it... you can have different classes inheriting different aspects of behaviour, rather than having them all in the same class/subclass.

Using Extensions

Extending the Collectable protocol...

extension Collectable {
    
    var priceIncrease: Int {
        return self.price * 10
    }
    
    init(name: String) {
        self.init(withName: name, startingPrice: 100)
    }
    
    func collect() -> Bool {
        print("Could not be collected")
        return false
    }
    

}

So this could have been done earlier but the difference is all the implementation is done, which can't be done with the protocol blueprint. 

extension String {
    
    func fancyDebug() {
        print("This string has \(self.count) characters")
    }
    
}

antidote.name.fancyDebug()

Native types can be extended too. They can be extended with functions, computed properties and initialisers. 

Throwing Errors

// Error enum
enum DataError: Error {
    case EmptyPath
    case InvalidPath
}



// Throwing functions

func loadData(path: String) throws {
    
    guard path.contains("/") else {
        throw DataError.InvalidPath
    }
    
    guard !path.isEmpty else {
        throw DataError.EmptyPath
    }
}

There we go! The difference between throwing and handling errors is delegation - who does it and where. The above is using a specific function with a specific enum created to contain the errors following the error protocol. 

Error Handling

// Do-Catch statements
do {
    
try loadData(path: playerDataPath)
    print("Data successful!")

} catch is DataError {
    print("Invalid or empty path")
    
} catch {
    print("Unknown error")
    
}

So in error handling we have the do/try/catch, rather than the throws keyword. But we need to use the function that has already been made (loadData)

if let dataLoaded = try? loadData(path: playerDataPath) {
    print("Data fetch went well")
}



// Propagating errors
func propogateDataError() throws {
    try loadData(path: playerDataPath)
}

do {
   
    try propogateDataError()
    print("Data successful")
    
} catch DataError.EmptyPath {
    print("Empty path detected")
} catch DataError.InvalidPath {
    print("Invalid path detected")
} catch {
    print("Unknown error")
}


There we have it! Several different ways to handle errors. I am going to do the challenge in a bit, then before doing anything else, review this entire course. Practise certain aspects so that I can then be confident that I'm au fait with this confident

Stopped at 13:31 (38 minutes; total of 56)


Comments

Popular posts from this blog

*Xcode Project Entry 2* F1 Quiz - part 1

Angela Yu Course Part 10 (up to lesson 112)

Angela Yu Xcode 12 Course - Part 7 (lectures 74 to 79)