724 lines
15 KiB
Markdown

<p align="center">
<img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/swift/swift-original.svg" alt="Swift" width="250"/>
</p>
# Introduction
I watched a Swift lecture by an apple guy at FOSDEM 2025. It was pretty cool. Inspired me to think about memory safety, and, needless to say, learn Swift.
## ToC
- [Introduction](#introduction)
- [ToC](#toc)
- [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. It deals with memory by using Automatic Reference Counting (ARC). This means that the compiler automatically manages memory for us.
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)
<!-- Footnotes -->
[^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.