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.
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]
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
-
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. ↩︎