Swift

General Notes

There are NO semicolons in Swift. The language is designed to be concise and readable. Very pythonic in that sense.

Swift is a statically-typed language. This means that the type of a variable is known at compile time. This is in contrast to dynamically-typed languages like Python, where the type of a variable is determined at runtime.

Swift deals with memory by using Automatic Reference Counting (ARC). This means that the compiler automatically manages memory for you. This is in contrast to languages like C and C++, where you have to manually manage memory.

Swift is a multi-paradigm language. This means that it supports multiple programming paradigms, such as object-oriented programming, functional programming, and procedural programming. We'll get into this later.

Swift project

Creating a new project:

swift package init --type executable

--type flag specifies the type of the project. In this case, it is an executable. Other options include library and system-module.

Running the project (this will build and run the project):

swift run

Building the project:

swift build

This will create a .build directory in the root of the project. The executable will be in .build/debug/ directory (default filename is swift).

Package.swift

This file is the equivalent of requirements.txt in Python. It specifies the dependencies of the project.

E.g.

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "swift",
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "swift"),
    ]
)

To add a dependency, add the following to the Package.swift file:

dependencies: [
    .package(url: "
    https://whatever.com", from: "1.0.0")
]

To import the dependency in the code:

import Whatever

To update the dependencies:

swift package update

Foundation

Foundation is a library that provides basic functionality for Swift. It is similar to the stdlib in C.

To import Foundation:

import Foundation

To use a function from Foundation:

let date = Date()

To use a class from Foundation:

let url = URL(string: "https://www.google.com")

Etc. etc.

Syntax

// This is a comment

Variables are declared as follows:

var x = 5
let y = 10

Types are inferred by the compiler. However, you can specify the type explicitly:

var x: Int = 5
let y: Double = 10.0

Strings are declared as follows:

let str = "Hello, world!"

Arrays are declared as follows:

let arr = [1, 2, 3, 4, 5]

Dictionaries are declared as follows:

let dict = ["key1": "value1", "key2": "value2"]

Tuples are declared as follows:

let tuple = (1, "hello", 3.14)

To access elements of a tuple (unpacking):

let (a, b, c) = tuple
print(a) // 1
print(b) // hello
print(c) // 3.14

To access elements of an array:

print(arr[0]) // 1

To access elements of a dictionary:

print(dict["key1"]) // value1

To access the length of an array:

print(arr.count)

Functions, if-else, loops, etc.

Functions are defined as follows:

func functionName(arg1: Type, arg2: Type) -> ReturnType {
    // code
}

Tip

In Swift, the underscore (_) in function parameters signifies that the parameter name is omitted when calling the function. It essentially means "ignore the external name for this parameter," similar to the *args and **kwargs in Python.


For loops:

for i in 0..<10 { // Note that 0..<10 is a range, equivalent to range(10) in Python
    // code
}

While loops:

while condition {
    // code
}

Repeat-while loops (do-while in other languages):


repeat {
    // code
} while condition

If-else statements:

if condition {
    // code
} else if condition {
    // code
} else {
    // code
}

Switch statements:

switch value {
case 1:
    // code
case 2:
    // code
default:
    // code
}

A guard statement is used to transfer program control out of a scope if one or more conditions aren't met. It is similar to an if-else statement, but it is used to exit early if a condition is not met.

guard condition else {
    // code
}

Actual example:

guard let url = URL(string: "https://www.google.com") else {
    print("Invalid URL")
    return
}

In python, this would be:

if not url := URL("https://www.google.com"):
    print("Invalid URL")
    return

A guard statement is used to make the code more readable and to avoid the pyramid of doom.

Pyramid of doom

Try catch statements:

do {
    try someFunction()
} catch {
    // code
}

Throwing an error:

func someFunction() throws { // Java-esque throws keyword
    if condition {
        throw SomeError()
    }
}

A defer statement is used to execute a block of code just before the function returns. It is similar to the finally block in Python (outside of the context of exceptions).

func someFunction() {
    defer {
        // code
    }
    // code
}

Defer statements are executed in reverse order, i.e., the last defer statement is executed first.

Practical example:

func openFile() {
    let file = open("file.txt")
    defer {
        close(file)
    }
    // code
}

Var vs. let

var is used to declare a variable. It is mutable.

let is used to declare a constant. It is immutable.

var x = 5
x = 6 // valid

let y = 5
y = 6 // invalid

We love memory safety <3. This is exactly the same as const in JavaScript. On a sidenote, what is the difference between let and var in JavaScript? let is block-scoped, while var is function-scoped. Good segue to-

Language features

Pattern matching

OH YES! Pattern matching is a powerful feature in Swift. It is used to match values against patterns. The Python equivalent would probably be in or the re module.

let numbers = [10, 15, 20, 25, 30]

for num in numbers where num % 10 == 0 {
    print(num) // 10, 20, 30
}

Classes

Classes and structs are used to define custom data types.

class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func sayHello() {
        print("Hello, my name is \(name)")
    }
}

let person = Person(name: "Alice", age: 25)
person.sayHello()

Structs

Structs are similar to classes, but they are value types. This means that when you pass a struct to a function, a copy of the struct is passed, not a reference to the struct.

struct Point {
    var x: Int
    var y: Int
}

var point1 = Point(x: 1, y: 2)
var point2 = point1
point2.x = 3
print(point1.x) // 1
print(point2.x) // 3

Enums

Enums are used to define a group of related values. They are similar to enums in C and Java.

enum Direction {
    case north
    case south
    case east
    case west
}

let direction = Direction.north

// We also got associated values (which means that each case can have a value associated with it)
enum Result {
    case success(Int)
    case failure(String)
}

let result = Result.success(42)
let result2 = Result.success("Error") // invalid

OOP stuff

Inheritance is done using the : operator.

class Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func makeSound() {
        print("Animal sound")
    }
}

class Dog: Animal {
    override func makeSound() { // Method overriding, similar to Java
        print("Woof")
    }
}

let dog = Dog(name: "Rex")
dog.makeSound()

Extensions are used to add new functionality to existing classes, structs, and protocols. This is a way better way to do this than reflection in Java. Safe and concise.

extension Int {
    func squared() -> Int {
        return self * self
    }
}

let x = 5
print(x.squared()) // 25

POP (Protocol-Oriented Programming)

What's this? It's a paradigm that combines the best of OOP and functional programming. It is similar to interfaces in Java.

protocol Animal {
    var name: String { get set } // Getters and setters (Lombok - @Data)
    func makeSound()
}

class Dog: Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func makeSound() {
        print("Woof")
    }
}

The reason why POP is so powerful is that it allows you to define default implementations for methods in protocols. This is similar to abstract classes in Java.

protocol Animal {
    var name: String { get set }
    func makeSound()
}

extension Animal {
    func makeSound() {
        print("Animal sound")
    }
}

class Dog: Animal {
    var name: String

    init(name: String) {
        self.name = name
    }
}

let dog = Dog(name: "Rex")
dog.makeSound() // Animal sound

Functional stuffies

Swift has a couple of higher-order functions that are similar to Python/Haskell.

map is used to apply a function to each element of an array.

let arr = [1, 2, 3, 4, 5]
let newArr = arr.map { $0 * 2 } // $0 is the current element, equv  to lambda in Python
print(newArr) // [2, 4, 6, 8, 10]

filter is used to filter elements of an array based on a condition.

let arr = [1, 2, 3, 4, 5]
let newArr = arr.filter { $0 % 2 == 0 }
print(newArr) // [2, 4]

reduce is used to combine all elements of an array into a single value.

let arr = [1, 2, 3, 4, 5]
let sum = arr.reduce(0) { $0 + $1 }
print(sum) // 15

sorted is used to sort an array.

let arr = [5, 3, 1, 4, 2]
let sortedArr = arr.sorted()

print(sortedArr) // [1, 2, 3, 4, 5]

forEach is used to iterate over an array.


let arr = [1, 2, 3, 4, 5]
arr.forEach { print($0) }

compactMap is used to remove nil values from an array.

let arr = [1, nil, 2, nil, 3, nil]
let newArr = arr.compactMap { $0 }
print(newArr) // [1, 2, 3]

And so on and so forth.

Scope and shizz

Swift has block scoping. Variables declared inside a block are not accessible outside of it.

if true {
    let a = 10
    var b = 20
    print(a) // valid
    print(b) // valid
}

// print(a) // invalid, a is out of scope
// print(b) // invalid, b is out of scope

Function scope vs. global scope:

func doSomething() {
    let localVar = 42
    print(localVar) // valid
}

// print(localVar) // invalid, localVar is out of scope

Global variables are declared outside of any function or block. They are accessible from anywhere in the program.

let globalVar = 42

func doSomething() {
    print(globalVar) // valid
}

Shadowing1 is allowed.

Optionals

Memory safety is a big deal in Swift. Optionals are used to handle the absence of a value. An optional is a type that can hold either a value or nil.

var optionalInt: Int? = 5
optionalInt = nil

The question mark ? is used to denote an optional type. The ! is used to force unwrap an optional. This is dangerous because if the optional is nil, the program will crash.

var optionalInt: Int? = 5
print(optionalInt!) // prints 5
optionalInt = nil
print(optionalInt!) // crashes

To safely unwrap an optional, use an if-let statement:

var optionalInt: Int? = 5
if let unwrappedInt = optionalInt {
    print(unwrappedInt)
}

Optional chaining is used to access properties and methods of an optional that might be nil. If the optional is nil, the chain will short-circuit and return nil.

var str: String? = "Hello, world!"
let count = str?.count

Nil coalescing operator ?? is used to provide a default value for an optional if it is nil. It's like a get method in Python.

var optionalInt: Int? = nil
let unwrappedInt = optionalInt ?? 0

Closures

Closures are self-contained blocks of code that can be passed around and used in your code. They are similar to lambda functions in Python.

let closure = {
    print("Hello, world!")
}

closure()

Aint that nifty?

Other stuff

Naming conventions

  • Use camelCase for variable names.
  • Use PascalCase for type names.
  • Use snake_case for function names.
  • _ is used as a wildcard in Swift. It is similar to the _ in Python/Haskell.

"Main" function? if __name__ == "__main__":?

In Swift, the entry point of a program is the main.swift file.

print("Hello, world!")

There is no main function. Swift is smart enough to figure out that this is the entry point of the program.

IF you'd like to EXPLICITLY define the entry point, you can do so by annotating the file with @main.

@main
struct MyProgram {
    static func main() {
        print("Hello, world!")
    }
}

Note the struct keyword. This is because @main is an attribute that can only be applied to a struct or a class.

Important

The @main attribute is only applicable to libraries, if you are creating an executable, you don't need to use it.

Docstrings

Docstrings are used to document functions, classes, and modules. They are enclosed in triple quotes.

/**
 This is a docstring.
*/
func someFunction() {
    // code
}

Project-specific Notes

Most likely teammates for Software engineering


  1. Shadowing is the practice of using the same name for a variable in an inner scope as in an outer scope. The inner variable "shadows" the outer variable. This is useful when you want to use the same name for a variable in different scopes. However, it can lead to confusion and bugs if not used carefully. ↩︎