Revisiting Swift

Been several years since working with Swift. This is a refresher and some general notes, starting with the basics. This is focused on Swift v4.

 

Playground – an Xcode project of a single file that can be executed.

REPL – Read, Eval, Print, Loop; We can run the swift REPL by opening a terminal and typing ‘swift’. The swift REPL is part of the LLDB (the swift debugger). Due to this, the REPL is often a tool used for debugging applications.

Swift is a compiled language. It is not interpreted or intermediate, it must be compiled. There is no runtime environment needed.

The following are variable types

  • Int, UInt
  • String, Character
  • Bool
  • Double
  • Float
  • Array (collection)
  • Dictionary (collection)
  • Set (collection)

The ‘var’ keyword is used to define variables. All variables are defined using this keyword. Swift uses type inference, meaning as soon as the variable is set Swift will type it to that value. Swift is a type-safe language.

Whenever we use ‘var’, we must either assign the value to it (so swift can infer the type) or we must declare the type. It must have type annotation.

var str = "Hello, playground"
str = 22 // Compiler error
var score: Int // type annotation, will error if missing

In Swift the accepted style is to use camel case for variable names. The ‘let’ keyword is used to set constant variables. These variables should also use camel case and Swift will infer the data type. If a variable is not mutable, Swift will automatically alert you saying to use ‘let’ instead of ‘var’. We can also use ‘type’ keyword to get the type of a variable.

let pi = 3.14
type(of: pi) // Int.Type
let myvar = 1.0
let myvar2: Float? // Optional - this might be type of string or nil
let result: Int = pi + myvar // error - coercion not supported
let result2 = myvar + myvar2 // error - cannot use optionals without value

var unwrap = myvar2! // unwraps the value, but this will runtime error if nil

// optional binding
if let unwrap2 = myvar2 { // unwraps while avoiding runtime error if nil
 print (unwrap2)
}
let convertedResult: Int = Int(pi) + Int(myvar) // 4

All ‘var’ and ‘let’ variables must be initialized before used. Swift does not set default values (like an empty string or 0 for integer). Everything must be initialized first. The exception to this rule is using the “?” optional syntax, which declares the variable as perhaps not used. To unwrap optionals, use optional binding.

Swift does not do implicit conversion for types (coercion). Instead, we need to do conversions when working with different types (explicit conversion).

 

Collections

The following are core Swift collections:

  • Array – ordered collection; zero-based; type-safe, the array must be of same type; let – immutable, var – mutable
  • Set – unordered collection;
  • Dictionary – key/value pairs
var array1 = ["aaa", "bbb", "ccc"]
var array2 = [1, 2, 3,
            3, 4, 5]
var array3: [String] = []
array1.append("ddd")
var result2 = array1.removeFirst()

 

Flow Control

When working with conditional statements, we dont need parenthesis but curly braces are required. The switch statement is also different than other languages – particularly there is no automatic fall through, no need for break statements, and can use ranges.

let v1: Int = 3
switch v1 {
case 0:
    print("0")
case 1:
    print ("1")
case 2:
    print ("2")
case 3...5:
    print ("3-5")
default:
    break
}

// Switches using enum
var e: en = .m2
switch e {
case .m1:
    print("m1")
case .m2:
    print("m2")
case .m3:
    print("m3")
}

Loops the for, repeat-while and while.

for arr in array1 {
    print(arr)
}

var x = 1
repeat {
    x = x + 1
} while (x < 3)

while (x < 6) {
    x = x + 1
}

 

String Interpolation

var s1 = "Hello World"
print ("\(s1)")

 

Structs

All primative data types – Int, String, Float, etc are of type Struct in Swift (not Object as in other languages). Structs cannot exhibit inheritance. Structs are value types whereas classes are reference types.

struct Movie {
    var title: String
    var director: String
    var year: Int
    var genre: String
    
    func summary() -> String {
        return "\(title) - \(director)"
    }
}
var godfather = Movie(title: "Godfather", director: "Capola", year: 1970, genre: "Drama")
print (godfather.summary())

 

Dictionary

Dictionaries are key/value pairs. The keys must be of same value type, and values are of same type – but the key doesnt have to be the same as the value type.

var airlines = ["SWA": "Southwest Airlines", "BAW": "British Airlines", "BHA": "Buddha Air"]
let aa1 = airlines["SWA"]
airlines["BHA"] = "Bahama Air"
let aa2 = airlines["BHA"]
airlines["BAW"] = nil //removed
for aa3 in airlines {
    print (aa3) //(key: "BHA", value: "Bahama Air")
}
for (code,name) in airlines { //tuple
    print (name) //"Bahama Air"
}

 

Tuple

func album() -> (title: String, year: Int) {
    let title = "Galaxy"
    let year = 1999
    return (title, year)
}
let (albTitle, albYear) = album()

 

Closures

Closures are heavily used in Swift from general functions working with collections to animation, callbacks, handlers and even interface controls. Closures are reuseable code like functions (a function is actually a type of closure), but are more generic – it doesnt have a name. It is very similar to anonymous functions.

// Closures
func compareAirlines(a1: Airline, a2: Airline) -> Bool {
    if a1.Name <= a2.Name { return true } else { return false } } let sortedAirlines1 = airlines.sorted(by: compareAirlines) let sortedAirlines2 = airlines.sorted(by: { (a1: Airline, a2: Airline) -> Bool // redundant
    in
    if a1.Name <= a2.Name {
        return true
    } else {
        return false
    }
})
let sortedAirlines3 = airlines.sorted(by: {
    if $0.Name <= $1.Name { // implicit reference of Element
        return true
    } else {
        return false
    }
})
let sortedAirlines4 = airlines.sorted { // trailing closure
    $0.Name <= $1.Name
}
let filteredAirlines = airlines.filter { $0.Year < 2010 }

 

Classes and Objects

Swift uses ARC – Automatic Reference Counting – automatically deallocates objects in memory. If there is a ‘deinit’ function it will be called when the object is out of scope.

class Appliance {
    var name: String = ""
    var mode: String = ""
    var capacity: Int?
    
    init() {
        self.name = "defualt"
        self.mode = "default"
        self.capacity = 1
    }
    
    init (name: String) {
        self.name = name
        self.mode = "defualt"
        self.capacity = 1
    }
    
    deinit {
        // Perform cleanup, close network or file IO
    }
    
    func getDetails() -> String {
        var message = "This applicance \(self.name) of mode \(self.mode)"
        if self.capacity != nil && self.capacity! > 0 {
            message += " and capcity \(self.capacity ?? 0)"
        }
        return message
    }
}

var fridge = Appliance()
fridge.name = "Fridger"
fridge.mode = "Kitchen"
fridge.capacity = 5
print (fridge.getDetails()) // This applicance Fridger of mode Kitchen and capcity 5

class Toaster: Appliance {
    var count: Int
    
    override init() {
        self.count = 0
        super.init()
    }
    
    func toast() {
        print ("Toasting... \(self.name)")
    }
}
var toasterz = Toaster()
toasterz.toast() // Toasting...default

Note that classes are very similar to structs but can exhibit inheritance. But more importantly, structs are passed by value whereas classes are pass by reference. Therefore when a class is modified it is modified across all parts of the application where it referneced that object. Structs are always copied so modifying it only does so within that scope.

If the class has ‘final’ keyword in front of it, then it cannot be inherited. This can apply to methods as well.

 

Extensions and Computed Properties

extension String {
    func removeSpaces() -> String {
        let filteredChars = self.filter { $0 != " " }
        return String(filteredChars)
    }
}
let t1 = "This is a test of removing spaces"
print(t1.removeSpaces()) // Thisisatestofremovingspaces

// Computered Properties
class Player {
    var name: String
    var livesLeft: Int
    var kills: Int
    var deaths: Int
    
    init() {
        self.name = "John"
        self.livesLeft = 3
        self.kills = 0
        self.deaths = 0
    }
    
    // computed property
    var score: Int {
        get {
            return (kills * 100 + livesLeft * 10 - deaths * 5)
        }
    }
}
var playerS1 = Player()
print (playerS1.score) // 30

 

Protocols

Swift is a Protocol-Oriented Language. Instead of objects, Swift focuses on protocols. Protocols can be used for general collections, data interactions and UI components. Protocols dont have implementations, only definitions like interfaces. You must implement them. You dont need to write protocols but rather interact with existing ones that are part of the standard library.

Also note that Swift classes allow single class inheritance. Classes, structs and enums also allow multiple protocols. This is how we can make classes adopt methods, properties from other classes.

Adopt a protocol means you are declaring to inherit from an existing protocol. Conform to a protocol means we have implemented the required properties and methods that we are adopting.

protocol myProtocol {
    var name: String { get set }
    func showMessage()
}

struct myStruct: myProtocol { // Adopt the protocol
    var name: String { // conforming
        return "MyName"
    }
    func showMessage() { // conforming
        print ("Struct \(self.name) is now conforming...")
    }
}

enum ServerError: Error { // adopting the Error protocol
    case noConnection
    case serverNotFound
    case authenticationFailed
    case unauthorized
}
func status(num: Int) throws -> String {
    switch num {
    case 1:
        throw ServerError.noConnection
    case 2:
        throw ServerError.authenticationFailed
    case 3:
        throw ServerError.unauthorized
    default:
        throw ServerError.serverNotFound
    }
    return "Success!"
}
do {
    print ("1")
} catch ServerError.noConnection {
    print ("2")
} catch ServerError.unauthorized {
    print ("3")
}

 

Guard and Defer

if let searchController.isActive = optionalSearchValue .. // isActive can be nil
guard searchController.isActive else { return }

// Defer
func someFunction() {
    ...
    defer { // This is always executed, even on error
        someIO.close()
    }
}

 

Xcode

Some useful Xcode hints

  • CMD + / Automatic code commenting

 

References

Swift Fundamentals
https://…

CocoaPods.org – Dependency Manager for Swift
https://cocoapods.org/

 

eof