Advanced-Swift

Course on Intermediate to Advanced swift techniques Udemy

Collections

iterating

let names = ["Alex", "John", "Mary"]

var nameIteratory = names.makeIterator()

while let name = nameIteratory.next() {
   print(name)
}

/*
for name in names {
   print(name)
}
*/

/*
----------------------------- Swift protocol
protocol IteratorProtocol {
   associatedtype Element
   
   mutating func next() -> Element?
}
*/

struct Countdown: Sequence {
   
   let start: Int
   
   func makeIterator() -> some IteratorProtocol {
       return CountdownIterator(self)
   }
}

struct CountdownIterator: IteratorProtocol {
   let countdown: Countdown
   var currentValue: Int = 0
   
   init(_ countdown: Countdown) {
       self.countdown = countdown
       self.currentValue = countdown.start
   }
   
   // if mutating a value
   mutating func next() -> Int? {
       if currentValue > 0 {
           let value = currentValue
           currentValue -= 1
           return value
       } else {
           return nil
       }
   }
}


let countdown = Countdown(start: 10)
for count in countdown {
   print(count)
}

filter

var names = ["Alex", "John", "Steven", "Mary"]

let finalNames = names.filter { name in
   return name.count > 4
}

struct Movie {
    let title: String
    let genre: String
}

var movies = [
    Movie(title: "Lord of the Rings", genre: "Fiction"),
    Movie(title: "ET", genre: "Fiction"),
    Movie(title: "Finding Nemo", genre: "Kids"),
    Movie(title: "Cars", genre: "Kids")
]

let movieToRemove = Movie(title: "Finding Nemo", genre: "Kids")

movies = movies.filter { movie in
    return movie.title != movieToRemove.title
}

let kidsMovies = movies.filter { movie in
    return movie.genre == "Kids"
}

forEach and enumerated

struct Movie {
   let title: String
   let genre: String
}

var movies = [
   Movie(title: "Lord of the Rings", genre: "Fiction"),
   Movie(title: "ET", genre: "Fiction"),
   Movie(title: "Finding Nemo", genre: "Kids"),
   Movie(title: "Cars", genre: "Kids")
]

movies.forEach { movie in
   addToFav(movie)
}


func addToFav(_ movie: Movie) { }

// Get Access to Index with enumerated
// ---------------------------returns a Tuple (a, b)
movies.enumerated().forEach { (index, movie) in
   print("Movie: index - \(index), title - \(movie.title)")
}

lazy iteration

when only needing access to small amounts of large amounts of data

let indexes = 1..<5000

let images = indexes.lazy.filter { index -> Bool in
   print("[filter]")
   // anything divisble by 2
   return index % 2 == 0
}.map { index -> String in
   print("[map]")
   return "image_\(index)"
}

let lastThreeImages = images.suffix(3)

//lastThreeImages.forEach { image in
//    print(image)
//}

lastThreeImages.forEach { print($0) }

reduce

will reduce array to 1 value

struct Item {
   let name: String
   let price: Double
}

struct Cart {
   // can only be set from with Cart struct
   private(set) var items: [Item] = []
   
   mutating func addItem(_ item: Item) {
       items.append(item)
   }
   
   // reduce - will reduce array to 1 value
   var total: Double {
       //---------- 0 init value -------- Result type
       items.reduce(0) { (value, item) -> Double in
           return value + item.price
       }
   }
}

var cart = Cart()
cart.addItem(Item(name: "Milk", price: 4.50))
cart.addItem(Item(name: "Bread", price: 2.50))
cart.addItem(Item(name: "Eggs", price: 12.00))

print(cart.total)

let items = [2.0,4.0,5.0,7.0]

let totalItems = items.reduce(0, +)
print(totalItems)

reduce(into: )

let ratings = [4, 8.5, 9.5, 2, 6, 3, 5.5, 7, 2.8, 9.8, 5.9, 1.5]

// -------------------------------------------------inout - results must/can be modified inside closure
let results = ratings.reduce(into: [:]) { (results: inout [String: Int], rating: Double) in
   
   switch rating {
       case 1..<4: results["Very Bad", default: 0] += 1
       case 4..<6: results["Ok", default: 0] += 1
       case 6..<8: results["Good", default: 0] += 1
       case 8..<11: results["Excellent", default: 0] += 1
       default: break
   }
   // returns the modified results object
}
print(results)

zip

let students = ["Alex", "Mary", "John", "Steven"]
let grades = [3.4, 2.8, 3.8, 4]
// will only return a pair that as an equal index in both sequences 
let pair = zip(students, grades)

pair.forEach { studentAndGrade in
   print(studentAndGrade.0) // 0 index = students
   print(studentAndGrade.1) // 1 index = grades
}

Functions

inout

in-out parameters

struct User {
    var userID: Int?
    let name: String
}


func saveUser(_ user: inout User) {
    // code to save user
    user.userID = 100
}

var user = User(name: "John Doe")
saveUser(&user) // & = copy of type

nested

functions inside another function

struct Pizza {
    let sauce: String
    let toppings: [String]
    let crust: String
}

class PizzaBuilder {
   
    func prepare() -> Pizza {
        
        func prepareSauce() -> String {
            return "Tomato Sauce"
        }
        
        func prepareToppings() -> [String] {
            return ["Chicken", "Pesto", "Mushroom"]
        }
        
        func prepareCrust() -> String {
            return "Hand tossed"
        }
        
        let sauce = prepareSauce()
        let toppings = prepareToppings()
        let crust = prepareCrust()
        
        return Pizza(sauce: sauce, toppings: toppings, crust: crust)
    }
    
}

let pizzaBuilder = PizzaBuilder()
let pizza = pizzaBuilder.prepare()

closures

functions as variables, passing functions to functions

/*
var hello = {
    print("Hello")
}
hello()
*/

/*
var hello: (String) -> () = { name in
    print("Hello \(name)")
}
hello("John")
 */

// If passed more than one parameter use $1, $2 etc
var hello: (String) -> () = { print("Hello \($0)") }

hello("John") // Hello John

var helloOnly: (String) -> () = {  _ in
    print("Hello") }

helloOnly("John") // Hello


// ----- number - times
let power: (Int, Int) -> Int = { number, times in
    number * times
}
let result = power(5, 3)
print(result)

let pow: (Int, Int) -> Int = { $0 * $1 }
let powResult = pow(5, 3)
print(powResult)

pass closure into a function – @escaping closures

func getPosts(complete: @escaping ([String]) -> ()) {

    var posts: [String] = []

    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        posts = ["Hello World", "Introduction to Closures"]
        complete(posts)
    }

}

getPosts { posts in
    print(posts)
}

Enums

replacing structs with enums

enum Session {
   case keynote(title: String, speaker: String, date: Date, isRecorded: Bool)
   case normal(title: String, speaker: String, date: Date)
   case workshop(title: String, speaker: String, date: Date, isRecorded: Bool)
   case joint(title: String, speakers: [String], date: Date)
}

let keynote = Session.keynote(title: "WWDC 2022", speaker: "Tim Cook", date: Date(), isRecorded: true)

func displaySession(session: Session) {
   switch session {
   case let .keynote(title: title, speaker: speaker, date: date, isRecorded: isRecorded):
       print("\(title) - \(speaker) - \(date) - \(isRecorded)")
   case let .normal(title: title, speaker: speaker, date: date):
       print("\(title) - \(speaker) - \(date)")
   case let .workshop(title: title, speaker: speaker, date: date, isRecorded: isRecorded):
       print("\(title) - \(speaker) - \(date) - \(isRecorded)")
   case let .joint(title: title, speakers: speakers, date: date):
       print("\(title) - \(speakers) - \(date)")
   }
}

hiding types

struct Teacher {
   let name: String
   let courses: [String]
}

struct Student {
   let name: String
   let courses: [String]
   var grade: String?
}


let teacher = Teacher(name: "John Doe", courses: ["Math", "Science"])
let student = Student(name: "John Doe", courses: ["Math", "Science"])

enum User {
   case teacher(Teacher)
   case student(Student)
}

let allUsers = [User.teacher(teacher), User.student(student)]

for user in allUsers {
   switch user {
   case .teacher(let teacher):
       print(teacher.courses)
   case .student(let student):
       print(student.grade ?? "")
   }
}

subclassing with enums

enum Ticket {
   case economy(Economy)
   case firstClass(FirstClass)
   case bussiness(Business)
   case international(International)
}

struct Economy {
   let departure: String
   let arrival: String
}

struct FirstClass {
   let departure: String
   let arrival: String
   let meal: Bool
}

struct Business {
   let departure: String
   let arrival: String
   let meal: Bool
   let chargingPorts: Bool
}

struct International {
   let departure: String
   let arrival: String
   let meal: Bool
   let chargingPorts: Bool
   let baggageAllowed: Bool
}

let ticket = Ticket.bussiness(Business(departure: "Houston", arrival: "Denver", meal: true, chargingPorts: true))

func checkIn(ticket: Ticket) {
   switch ticket {
   case .economy(let economy):
       print("\(economy)")
   case .firstClass(let firstClass):
       print("\(firstClass)")
   case .bussiness(let bussiness):
       print("\(bussiness)")
   case .international(let international):
       print("\(international)")
   }
}

sub classing 2

struct Student {
    let name: String
    let courses: [String]
    let isFullTime: Bool
}

struct Teacher {
    let name: String
    let courses: [String]
    let isFullTime: Bool
}

struct Staff {
    let name: String
    let isFullTime: Bool
}

enum User {
    case student(Student)
    case teacher(Teacher)
    case staff(Staff)
}

func updateProfile(user: User) {
    switch user {
    case .student(let student):
        print(student)
    case .teacher(let teacher):
        print(teacher)
    case .staff(let staff):
        print(staff)
    }
}

updateProfile(user: User.student(Student(name: "John Doe", courses: ["Math", "Science"], isFullTime: true)))

raw values

scenerio 1

enum NetworkError: Error {
    case badURL
    case decodingError
}

//enum TemperatureUnit: String {
//    case imperial = "fahrenheit"
//    case metric = "celsius"
//}

// Variables updated by third party - scenerio
enum TemperatureUnit: String {
    case imperial = "F"
    case metric = "C"
}

private func getWeatherURL(unit: TemperatureUnit) -> URL? {
    switch unit {
    case .imperial:
        return URL(string: "www.weather.com/?unit=fahrenheit")
    case .metric:
        return URL(string: "www.weather.com/?unit=celsius")
    }
}

func getWeather(unit: TemperatureUnit) throws {
    guard let url = getWeatherURL(unit: unit) else {
        throw NetworkError.badURL
    }
    print(url)
    // code to call weather api
}

/*
func getWeather(unit: TemperatureUnit) throws {
    guard let url = URL(string: "www.weather.com/?unit=\(unit.rawValue)") else {
        throw NetworkError.badURL
    }
    print(url)
    // code to call weather api
}
*/

do {
    try getWeather(unit: .imperial)
} catch {
    print(error)
}

scenerio 2

enum ImageType: String {
    case jpg
    case bmp
    case png
    
    init?(rawValue: String) {
        switch rawValue.lowercased() {
            case "jpg", "jpeg": self = .jpg
            case "bmp", "bitmap": self = .bmp
            case "png": self = .png
            default: return nil
        }
    }
}

func iconName(for fileExtension: String) -> String {
    guard let imageType = ImageType(rawValue: fileExtension) else { return "assetUnknown" }
    
    switch imageType {
        case .jpg:
            return "assetJPG"
        case .bmp:
            return "assetBMP"
        case .png:
            return "assetPNG"
    }
}


iconName(for: "jpg")
iconName(for: "jpeg")

Properties

lazy stored properties

will only be initalized once / and accessible immediately after first init (ideal for api call)

enum Level {
    case easy
    case medium
    case hard
}

struct Exam {
    var level: Level
    
    // lazy = will fetch quetions the 2nd time instantly without api call
    // private(set) = can only set within Exam structure not later
    lazy private(set) var questions: [String] = {
        
        // mock - api fetch time span
        sleep(5)
        
        switch level {
        case .easy:
            return ["What is 1+2", "What is 1+2", "What is 2+2"]
        case .medium:
            return ["What is 11+22", "What is 11+22", "What is 32+42"]
        case .hard:
            return ["What is 122+332", "What is 441+255", "What is 266+250"]
        }
    }()
}


var exam = Exam(level: .easy) // must be mutable - var
print(exam.questions)
print("Wait for 1 second")
sleep(1)
print(exam.questions) // returned instantly

// exam.questions = ["What is the capital of US?"] = NOT Allowed with private(set) property wrapper

computed properties

struct Workout {
    let startTime: Date
    let endTime: Date
    
    var timeElapsed: TimeInterval {
        endTime.timeIntervalSince(startTime)
    }
}

let start = Date()
sleep(5)
let end = Date()

let workout = Workout(startTime: start, endTime: end)
//print(workout.endTime.timeIntervalSince(workout.startTime))
print(workout.timeElapsed)

struct CartItem {
    let name: String
    let price: Double
}

struct Cart {
    let items: [CartItem]
    
    var total: Double {
        items.reduce(0) {
            return $0 + $1.price
        }
    }
}

let items = [
    CartItem(name: "Bread", price: 4.50),
    CartItem(name: "Milk", price: 3.50),
    CartItem(name: "Pizza", price: 10.95)
]

let cart = Cart(items: items)
print(cart.total)

property observers

struct Website {
    
    init(url: String) {
        
        // defer = will get called when init exits - will run didSet when executed 
    
        defer { self.url = url }
        
        self.url = url
    }
    
    var url: String {
        
        willSet {
            // fired before new value is give to property
            // newValue - to get new value of property
        }
        
        // didSet is not called when creating an instance
        
        didSet {
            url = url.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? url
        }
    }
}

var website = Website(url: "www.movies.com/?search=Lord of the Rings")
print(website) // Website(url: "www.movies.com%2F%3Fsearch=Lord%20of%20the%20Rings")

Initializers

in structs

struct Student {
    let firstname: String
    let lastname: String
    let grade: String
    
    
}

extension Student {
    
    // will allow you to keep the default initializer 
    
    init(firstname: String, lastname: String) {
        self.firstname = firstname
        self.lastname = lastname
        self.grade = ""
    }
}

let student = Student(firstname: "", lastname: "")
let otherStudent = Student(firstname: "", lastname: "", grade: "")

convience initializers

class Car {
    var make: String
    var model: String
    var color: String
    
    init(make:String, model: String, color: String) {
        self.make = make
        self.model = model
        self.color = color
    }
    
    convenience init(make: String, model: String) {
        self.init(make: make, model: model, color: "White")
    }
}

let car = Car(make: "Honda", model: "Accord")

subclassing

class Tesla: Car {
    var range: Double
    
     init(make: String, model: String, color: String, range: Double) {
        self.range = range
        super.init(make: make, model: model, color: color)
    }
    
     override init(make: String, model: String, color: String) {
       self.range = 300
       super.init(make: make, model: model, color: color)
   }
}

let tesla = Tesla(make: "", model: "", color: "", range: 0)

subclassing-screenshot

required initializers

protocol CarType {
    init(make: String, model: String)
}


//MARK: - always mark as Final until you know if inheriting
final class Car: CarType {
    var make: String
    var model: String
    var color: String
    
    init(make:String, model: String, color: String) {
        self.make = make
        self.model = model
        self.color = color
    }
    
    required convenience init(make: String, model: String) {
        self.init(make: make, model: model, color: "White")
    }
    
    /*
    class func makeCar(make: String, model: String) -> Self {
        let car = self.init(make: make, model: model)
        // setup engine
        // setup fuel
        // setup tires
        return car
    }
     */
}

let car = Car(make: "Honda", model: "Accord")

GitHub

View Github