Swift

- [General Notes](#general-notes) - [Swift project](#swift-project) - [Package.swift](#packageswift) - [Foundation](#foundation) - [Syntax](#syntax) - [Functions, if-else, loops, etc.](#functions-if-else-loops-etc) - [Var vs. let](#var-vs-let) - [Language features](#language-features) - [Pattern matching](#pattern-matching) - [Classes](#classes) - [Structs](#structs) - [Enums](#enums) - [OOP stuff](#oop-stuff) - [POP (Protocol-Oriented Programming)](#pop-protocol-oriented-programming) - [Functional stuffies](#functional-stuffies) - [Scope and shizz](#scope-and-shizz) - [Optionals](#optionals) - [Closures](#closures) - [Other stuff](#other-stuff) - [Naming conventions](#naming-conventions) - ["Main" function? `if __name__ == "__main__":`?](#main-function-if-__name__--__main__) - [Docstrings](#docstrings) - [Project-specific Notes](#project-specific-notes) - [Most likely teammates for Software engineering](#most-likely-teammates-for-software-engineering) # 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: ```bash 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): ```bash swift run ``` Building the project: ```bash 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 // 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: ```swift dependencies: [ .package(url: " https://whatever.com", from: "1.0.0") ] ``` To import the dependency in the code: ```swift import Whatever ``` To update the dependencies: ```bash 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: ```swift import Foundation ``` To use a function from Foundation: ```swift let date = Date() ``` To use a class from Foundation: ```swift let url = URL(string: "https://www.google.com") ``` Etc. etc. ## Syntax ```swift // This is a comment ``` Variables are declared as follows: ```swift var x = 5 let y = 10 ``` Types are inferred by the compiler. However, you can specify the type explicitly: ```swift var x: Int = 5 let y: Double = 10.0 ``` Strings are declared as follows: ```swift let str = "Hello, world!" ``` Arrays are declared as follows: ```swift let arr = [1, 2, 3, 4, 5] ``` Dictionaries are declared as follows: ```swift let dict = ["key1": "value1", "key2": "value2"] ``` Tuples are declared as follows: ```swift let tuple = (1, "hello", 3.14) ``` To access elements of a tuple (unpacking): ```swift let (a, b, c) = tuple print(a) // 1 print(b) // hello print(c) // 3.14 ``` To access elements of an array: ```swift print(arr[0]) // 1 ``` To access elements of a dictionary: ```swift print(dict["key1"]) // value1 ``` To access the length of an array: ```swift print(arr.count) ``` ### Functions, if-else, loops, etc. Functions are defined as follows: ```swift 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: ```swift for i in 0..<10 { // Note that 0..<10 is a range, equivalent to range(10) in Python // code } ``` While loops: ```swift while condition { // code } ``` Repeat-while loops (do-while in other languages): ```swift repeat { // code } while condition ``` --- If-else statements: ```swift if condition { // code } else if condition { // code } else { // code } ``` Switch statements: ```swift 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. ```swift guard condition else { // code } ``` Actual example: ```swift guard let url = URL(string: "https://www.google.com") else { print("Invalid URL") return } ``` In python, this would be: ```python 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](../writeups/assets/pyramid.png) Try catch statements: ```swift do { try someFunction() } catch { // code } ``` Throwing an error: ```swift 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). ```swift func someFunction() { defer { // code } // code } ``` Defer statements are executed in reverse order, i.e., the last defer statement is executed first. Practical example: ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift 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. ```swift let arr = [1, 2, 3, 4, 5] let sum = arr.reduce(0) { $0 + $1 } print(sum) // 15 ``` `sorted` is used to sort an array. ```swift 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. ```swift let arr = [1, 2, 3, 4, 5] arr.forEach { print($0) } ``` `compactMap` is used to remove `nil` values from an array. ```swift let arr = [1, nil, 2, nil, 3, nil] let newArr = arr.compactMap { $0 } print(newArr) // [1, 2, 3] ``` [And so on and so forth.](https://www.appcoda.com/higher-order-functions-swift/) ### Scope and shizz Swift has block scoping. Variables declared inside a block are not accessible outside of it. ```swift 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: ```swift 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. ```swift let globalVar = 42 func doSomething() { print(globalVar) // valid } ``` Shadowing[^1] 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`. ```swift 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. ```swift var optionalInt: Int? = 5 print(optionalInt!) // prints 5 optionalInt = nil print(optionalInt!) // crashes ``` To safely unwrap an optional, use an if-let statement: ```swift 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`. ```swift 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. ```swift 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. ```swift 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. ```swift 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`. ```swift @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. ```swift /** This is a docstring. */ func someFunction() { // code } ``` # Project-specific Notes ## [Most likely teammates for Software engineering](practical/01_mostLikelyTeams/README.md) [^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.