Swift
Getting Started
Variable {.row-span-2}
var score = 0 // Variablelet pi = 3.14 // Constant
var greeting = "Hello"var numberOfToys = 8var isMorning = true
var numberOfToys: Int = 8numberOfToys += 1
print(numberOfToys)// prints "9"
Type annotations
var greeting: String = "Hello"var numberOfToys: Int = 8var isMorning: Bool = truevar price: Double = 8.99
Arithmetic operators {.row-span-3}
+
Add-
Subtraction*
Multiplication/
Division%
Remainder
{.cols-2 .marker-none}
var x = 0x = 4 + 2 // x is now 6x = 4 - 2 // x is now 2x = 4 * 2 // x is now 8x = 4 / 2 // x is now 2x = 4 % 2 // x is now 0
+=
Adds and assigns sums-=
subtract and assign the difference*=
Multiplication and assignment/=
Divide and assign quotient%=
Divide and assign remainder
{.marker-none}
Compound Assignment Operators
var numberOfDogs = 100numberOfDogs += 1print("There are \(numberOfDogs) Dalmatians!")
// print: There are 101 Dalmatians!
String interpolation
var apples = 6print("I have \(apples) apples!")
// print: I have 6 apples!
Multi-line string
let myLongString = """Swift?This is my favorite language!Yeah!"""
Code Comments
// This line represents a comment in Swift.
/*This is all commented out.None will run!*/
Form a tuple {.col-span-2}
let player = ("Maya", 5, 150)
print(player) // ("Maya", 5, 150)print("\(player.0): level \(player.1), \(player.2) pts") // Maya: level 5, 150 pts
Decompose tuple
let player = (name: "Maya", level: 5)let (currentName, curLevel) = playerprint("\(currentName): level \(curLevel)")// print: Maya: level 5
Special comment syntax (MARK)
// MARK: -view settings
MARK
can be used to display comments in the column
Special comment syntax (TODO)
// TODO: update logic to accommodate data changes
TODO
is used to display reminders of things that need to be done
Special Comment Syntax (FIXME)
// FIXME: Fix buggy behavior when making changes to existing entries
FIXME
is used to display reminders about things that need to be fixed
Variable
Variable declaration
Variables are declared with var
:
var greeting = "Hello"var numberOfToys = 8var isMorning = true
For clarity, variable declarations can contain type annotations:
var greeting: String = "Hello"var numberOfToys: Int = 8var isMorning: Bool = true
Variables are mutable. Their values be changed:
var numberOfToys: Int = 8numberOfToys += 1
print(numberOfToys)// print "9"
Constants
Constants are declared with let
:
let greeting = "Hello"let numberOfToys = 8let isMorning = true
For clarity, constant declarations can contain type annotations:
let greeting: String = "Hello"let numberOfToys: Int = 8let isMorning: Bool = true
Constants are immutable. Their values be changed:
let numberOfToys: Int = 8numberOfToys += 1// Error: numberOfToys is immutable
Computed variables (get and set) {.row-span-3}
import Foundation
let df = DateFormatter()df.dateFormat = "d MMMM yyyy"
guard var birth = df.date(from: "5 June 1999") else { print("Date is not valid") return}
var age: Int { Calendar.current .dateComponents([.year], from: birth, to: Date()).year!}
print(age) // 23guard let birth2 = df.date(from: "5 June 2002") else { print("Date is not valid") return}birth = birth2print(age) // 20
In the example below, distanceInFeet has a getter
and a setter
. Because of the setter
, the getter
requires the
keyword get
:
var distanceInMeters: Float = 100
var distanceInFeet: Float { get { distanceInMeters *3.28 } set(newDistance) { distanceInMeters = newDistance /3.28 }}
print(distanceInMeters) // 100.0print(distanceInFeet) // 328.0
distanceInFeet = 250print(distanceInMeters) // 76.21951print(distanceInFeet) // 250.0
distanceInMeters = 800print(distanceInMeters) // 800.0print(distanceInFeet) // 2624.0
willSet {.row-span-2}
var distance = 5 { willSet { print("The distance will be set") }}
distance = 10 // print: distance will be set
The new value can be accessed in willSet
:
var distance = 5 { willSet(newDistance) { print("The distance will be set \(newDistance)") }}
distance = 10 // print: distance will be set to 10
willSet
can be used to execute some code before setting the variable value
didSet
var distance = 5 { didSet { print("The distance is set to \(distance)") print("Its old value is: \(oldValue)") }}distance = 10 // print: distance will be set to 10 // print: its old value is: 5
willSet and didSet
var distance = 5 { willSet(newDistance) { print("The distance will be set to \(newDistance)") } didSet { print("The distance is set to \(distance)") print("Its old value is: \(oldValue)") }}distance = 10
Conditions
if statement
var halloween = trueif halloween { print("Trick or treat!")}// print: Trick or treat!if 5 > 3 { print("5 is greater than 3")} else { print("5 is not more than 3")}// output: "5 is greater than 3"
else statement
var turbulence = false
if turbulence { print("Please sit down.")} else { print("You are free to move around.")}// print: You are free to move around.
else if statement
var weather = "rainy"if weather == "sunny" { print("Get some sunscreen")} else if weather == "rainy" { print("Take an umbrella")} else if weather == "snowing" { print("Put on your snow boots")} else { print("Invalid weather")}// print: take an umbrella
Comparison Operators
5 > 1 // true6 < 10 // true2 >= 3 // false3 <= 5 // true"A" == "a" // false"B" != "b" // true
-<
less than
->
greater than
-<=
less than or equal to
->=
greater than or equal to
-==
is
equal to
-!=
is not equal to
Range Operators
a...b // numbers between a and b (including both a and b)a..<b // numbers between a and b (including a but excluding b)...b // numbers till b (including b)
-a...b
Closed Range
-a..<b
Half-Open Range
-...b
One-Sided Range
Ternary conditional operator
var driverLicense = true
driverLicense ? print("driver seat") : print("passenger seat")// print: driver's seat
switch statement
var secondaryColor = "green"
switch secondaryColor { case "orange": print("A mixture of red and yellow") case "purple": print("A mix of red and blue") default: print("This may not be a secondary color")}// print: mix of blue and yellow
switch statement: interval matching
let year = 1905var artPeriod: String
switch year { case 1860...1885: artPeriod = "Impressionism" case 1886...1910: artPeriod = "Post-Impressionism" default: artPeriod = "Unknown"}// print: post-impressionism
switch statement: composite case
let service = "Seamless"
switch service {case "Uber", "Lyft": print("travel") case "DoorDash", "Seamless", "GrubHub": print("Restaurant delivery") case "Instacart", "FreshDirect": print("Grocery Delivery") default: print("Unknown service")}// print: restaurant takeaway
switch statement: where clause
let num = 7
switch num { case let x where x % 2 == 0: print("\(num) is even") case let x where x % 2 == 1: print("\(num) odd number") default: print("\(num) is invalid")}
// print: 7 odd
Logical Operators
!true // false!false //true
Logical Operators &&
true && true // truetrue && false // falsefalse && true // falsefalse && false // false
Logical operators ||
true || true // truetrue || false // truefalse || true // truefalse || false // false
Combined Logical Operators
!false && true || false // true
!false && true
first evaluates and returns true
Then, the expression, true
|| false
evaluates and returns the
final result true
false || true && false // false
true && false
first evaluates to return false
Then, the expression, false
|| false
evaluates and returns the
final result false
Control the order of execution
// without parentheses:true || true && false || false//----> true
// with brackets:(true || true) && (false || false)//----> false
Simple guards
func greet(name: String?) { guard let unwrapped = name else { print("Hello guest!") return } print("Hello \(unwrapped)!")}greet(name: "Asma") // output: Hello Asma!greet(name: nil) // output: Hello guest!
cycle
scope
let zeroToThree = 0...3//zeroToThree: 0, 1, 2, 3
stride() function
for oddNum in stride(from: 1, to: 5, by: 2) { print(oddNum)}// print: 1// print: 3
for-in loop
for char in "hehe" { print(char)}// print: h// print: e// print: h// print: e
continue keyword
for num in 0...5 { if num % 2 == 0 { continue } print(num)}// print: 1// print: 3// print: 5
The continue
keyword will force the loop to continue for the next iteration
break keyword
for char in "supercalifragilistic" {if char == "c" { break } print(char)}// print: s// print: u// print: p// print: e// print: r
Use underscores
for _ in 1...3 { print("Ole")}// print: Ole// print: Ole// print: Ole
while loop
var counter = 1var stopNum = Int.random(in: 1...10)
while counter < stopNum { print(counter) counter += 1}// loop to print until the stop condition is met
A while
loop accepts a condition and keeps executing its body code while the provided condition is true
. If the
condition is never false, the loop will keep running and the program will get stuck in an infinite loop
Arrays and collections
Array
var scores = [Int]()// array is empty: []
.count property
var grocery = ["🥓", "🥞", "🍪", "🥛", "🍊"]print(grocery.count)// print: 5
index {.row-span-2}
The index refers to the item’s position in the ordered list, and a single element is retrieved from the array using the
subscript syntax array[index]
.
var vowels = ["a", "e", "i", "o", "u"]
print(vowels[0]) // prints: aprint(vowels[1]) // prints: eprint(vowels[2]) // print: iprint(vowels[3]) // prints: oprint(vowels[4]) // prints: u
Note: Swift arrays are zero-indexed, meaning the first element has index 0.
Initialize with array literal
// use type inference:var snowfall = [2.4, 3.6, 3.4, 1.8, 0.0]// explicit type:var temp: [Int] = [33, 31, 30, 38, 44]
Initialize with default value
var teams = [Int](repeating: 0, count: 3)print(teams) // prints: [0, 0, 0]// or with Array typevar sizes = Array<Int>(repeating: 0, count: 3)print(sizes) // prints: [0, 0, 0]
.append() method and += operator
var gymBadges = ["Boulder", "Cascade"]gymBadges.append("Thunder")gymBadges += ["Rainbow", "Soul"]// ["Boulder", "Cascade", "Thunder",// "Rainbow", "Soul"]
.insert() and .remove() methods
var moon = ["🌖", "🌗", "🌘", "🌑"]moon.insert("🌕", at: 0)// ["🌕", "🌖", "🌗", "🌘", "🌑"]
moon.remove(at: 4)// ["🌕", "🌖", "🌗", "🌘"]
Iterate over an array
var employees = ["Peter", "Denial", "Jame"]for person in employees { print(person)}// print: Peter// print: Denial// print: Jam
Collection (Set)
var paintingsInMOMA: Set = [ "The Dream", "The Starry Night", "The False Mirror"]
We can use a collection (Set
) to store unique
elements of the same data type
Empty collection (Set)
var team = Set<String>()
print(team)// print: []
Populate the collection
var vowels: Set = ["a", "e", "i", "o","u"]
To create a set filled with values, use the Set
keyword before the assignment operator.
.insert()
var cookieJar: Set = [ "Chocolate Chip", "Oatmeal Raisin"]// add a new elementcookieJar.insert("Peanut Butter Chip")
.remove() and .removeAll() methods
var oddNumbers: Set = [1, 2, 3, 5]
// remove existing elementoddNumbers.remove(2)// remove all elementsoddNumbers.removeAll()
.contains()
var names: Set = ["Rosa", "Doug", "Waldo"]print(names.contains("Lola")) // print: false
if names.contains("Waldo"){ print("There's Waldo!")} else { print("Where's Waldo?")}// print: There's Waldo!
.isEmpty property
var emptyList = [String]()print(emptyList.isEmpty) // print: true
var populatedList: [Int] = [1, 2, 3]print(populatedList.isEmpty) // print: false
Iterate over a collection
var recipe: Set = ["Egg", "Flour", "Sugar"]
for ingredient in recipe { print ("Include \(ingredient) in the recipe")}
.isEmpty property
var emptySet = Set<String>()print(emptySet.isEmpty) // print: true
var populatedSet: Set = [1, 2, 3]print(populatedSet.isEmpty) // print: false
.count property
var band: Set = ["Peter", "Denial", "Jame"]
print("The band has \(band.count) players.")// print: Band has 4 players.
.intersection() Intersection
var setA: Set = ["A", "B", "C", "D"]var setB: Set = ["C", "D", "E", "F"]
var setC = setA.intersection(setB)print(setC) // print: ["D", "C"]
.union()
var setA: Set = ["A", "B", "C", "D"]var setB: Set = ["C", "D", "E", "F"]
var setC = setA.union(setB)print(setC)// print: ["B", "A", "D", "F", "C", "E"]
.symmetricDifference() Symmetric difference
var setA: Set = ["A", "B", "C", "D"]var setB: Set = ["C", "D", "E", "F"]
var setC = setA.symmetricDifference(setB)print(setC)// print: ["B", "E", "F", "A"]
.subtracting() Subtraction
var setA: Set = ["A", "B", "C", "D"]var setB: Set = ["C", "D"]
var setC = setA.subtracting(setB)print(setC)// print: ["B", "A"]
dictionary
Base Dictionary
var dictionaryName = [ "Key1": "Value1", "Key2": "Value2", "Key3": "Value3"]
An unordered
collection of pairs of data or key-value pairs
Keys
var fruitStand = [ "Coconuts": 12, "Pineapples": 12, "Papaya": 12]
Each key
is unique
even if they all contain the same value
Type Consistency
var numberOfSides = [ "triangle": 3, "square": 4, "rectangle": 4]
Contains only String
keys and Int
values
Initialize and populate the dictionary
var employeeID = [ "Hamlet": 1367, "Horatio": 8261, "Ophelia": 9318]
Initialize an empty dictionary
// initializer syntax:var yearlyFishPopulation = [Int: Int]()
// Empty dictionary literal syntax:var yearlyBirdPopulation: [Int: Int] = [:]
add to dictionary
var pronunciation = [ "library": "lai·breh·ree", "apple": "a·pl"]// new key: "programming", new value: "prow gra"pronunciation["programming"] = "prow·gra"
Delete key-value pair {.row-span-2}
var bookShelf = [ "Goodnight": "Margaret Wise Brown", "The BFG": "Roald Dahl", "Falling Up": "Shel Silverstein", "No, David!": "David Shannon"]// remove value by setting key to nilbookShelf["The BFG"] = nil
// remove value using .removeValue()bookShelf.removeValue(forKey: "Goodnight")
// remove all valuesbookShelf.removeAll()
Modify the key-value pair {.row-span-2}
var change = [ "Quarter": 0.29, "Dime": 0.15, "Nickel": 0.05]
// Change the value using subscript syntaxchange["Quarter"] = .25
// Change the value using .updateValue()change.updateValue(.10, forKey: "Dime")
To change the value of a key-value pair, use the .updateValue()
method or the subscript syntax by appending brackets
[ ]
with the existing keys within to the name of the dictionary, then adding the assignment operator (=
) followed
by the modified value
.isEmpty property
var bakery = [String:Int]()
// check if the dictionary is emptyprint(bakery.isEmpty) // prints truebakery["Cupcakes"] = 12// check if the dictionary is emptyprint(bakery.isEmpty) // print false
.count property
var fruitStand = [ "Apples": 12, "Oranges", 17]print(fruitStand.count) // print: 2
Assigning values to variables
var hex = [ "red": "#ff0000", "yellow": "#ffff00", "blue": "#0000ff",]
print("Blue hexadecimal code \(hex["blue"])")// print: blue hex code Optional("#0000ff")
if let redHex = hex["red"] { print("red hexadecimal code \(redHex)")}// print: red hex code #ff0000
Assigning the value of a key-value pair to a variable will return an optional value. To extract values, use the optional expansion
Traversing the dictionary
var emojiMeaning = [ "🤔": "Thinking Face", "😪": "Sleepy Face", "😵": "Dizzy Face"]// loop through keys and valuesfor (emoji, meaning) in emojiMeaning { print("\(emoji) is called '\(meaning)Emoji'")}// iterate through keys onlyfor emoji in emojiMeaning.keys { print(emoji)}// iterate through values onlyfor meaning in emojiMeaning.values { print(meaning)}
function
Basic functions
func washCar() -> Void { print("Soap") print("Scrub") print("Rinse") print("Dry")}
Call functions
func greetLearner() { print("Welcome to CheatSheets.zip!")}// function call:greetLearner()// print: Welcome to CheatSheets.zip!
return value
let birthYear = 1994var currentYear = 2020
func findAge() -> Int { return currentYear-birthYear}
print(findAge()) // prints: 26
Multiple parameters {.col-span-2}
func convertFracToDec(numerator: Double, denominator: Double) -> Double { return numerator / denominator}
let decimal = convertFracToDec(numerator: 1.0, denominator: 2.0)print(decimal) // prints: 0.5
Omit parameter labels
func findDiff(_ a: Int, b: Int) -> Int { return a -b}
print(findDiff(6, b: 4)) // prints: 2
return multiple values {.col-span-2}
func smartphoneModel() -> (name: String, version: String, yearReleased: Int) { return ("iPhone", "8 Plus", 2017)}let phone = smartphoneModel()
print(phone.name) // print: iPhoneprint(phone.version) // print: 8 Plusprint(phone.yearReleased) // print: 2017
Parameters & Arguments
func findSquarePerimet(side: Int) -> Int { return side *4}
let perimeter = findSquarePerimet(side: 5)print(perimeter) // print: 20
// Parameter: side// Argument: 5
Implicit return
func nextTotalSolarEclipse() -> String { "April 8th, 2024 🌎"}
print(nextTotalSolarEclipse())// print: April 8th, 2024 🌎
Default parameters
func greet(person: String = "guest") { print("Hello \(person)")}greet() // Hello guestgreet(person: "Aliya") // Hello Aliya
Input and output parameters {.row-span-2}
var currentSeason = "Winter"
func season(month: Int, name: inout String) { switch month { case 1...2: name = "Winter ⛄️" case 3...6: name = "Spring 🌱" case 7...9: name = "Summer ⛱" case 10...11: name = "Autumn 🍂" default: name = "Unknown" }}season(month: 4, name: ¤tSeason)
print(currentSeason) // Spring 🌱
variable parameter
func totalStudent(data: String...) -> Int { let numStudents = data.count return numStudents}
print(totalStudent(data: "Denial", "Peter"))// print: 2
Optional parameters
func getFirstInitial(from name: String?) -> String? { return name?.first}
Functions can accept optional types and return optional types. When a function cannot return a reasonable instance of
the requested type, it should return nil
structure
Structure Creation
struct Building { var address: String var floors: Int init(address: String, floors: Int) { self.address = address self.floors = floors }}
Structs or structs are used to programmatically represent real-life objects in code. A structure is created using the
struct
keyword, followed by its name, followed by a body containing its properties and methods
Default property values
struct Car { var numOfWheels = 4 var topSpeed = 80}
var reliantRobin = Car(numOfWheels: 3)
print(reliantRobin.numOfWheels) // prints: 3print(reliantRobin.topSpeed) // print: 80
Structural instance creation
struct Person { var name: String var age: Int
init(name: String, age: Int) { self.name = name self.age = age }}
// Person instance:var morty = Person(name: "Peter", age: 14)
init() method {.row-span-2}
struct TV { var size: Int var type: String
init(size: Int, type: String) { self.size = size self.type = type }}
Using the TV
class
var newTV = TV(size: 65, type: "LED")
Check type
print(type(of: "abc")) // print: Stringprint(type(of: 123)) // print: 123
Mutation method (mutating) {.row-span-2}
struct Menu { var menuItems = ["Fries", "Burgers"] mutating func addToMenu(dish: String) { self.menuItems.append(dish) }}
Using the Menu
class
var dinerMenu = Menu()dinerMenu.addToMenu(dish: "Toast")print(dinerMenu.menuItems)// prints: ["Fries", "Burgers", "Toast"]
Structural methods
struct Dog { func bark() { print("Woof") }}let fido = Dog()fido.bark() // prints: Woof
class
reference type (class) {.row-span-2}
class Player { var name: String
init(name: String) { self.name = name }}
var player1 = Player(name: "Tomoko")var player2 = player1player2.name = "Isabella"
print(player1.name) // Isabellaprint(player2.name) // Isabella
instance of the class
class Person { var name = "" var age = 0}
var sonny = Person()// sonny is now an instance of Person
init() method {.row-span-2}
class Fruit { var hasSeeds = true var color: String
init(color: String) { self.color = color }}
Using the Fruit class
let apple = Fruit(color: "red")
A class can be initialized using the init()
method and the corresponding initialization properties. In the init()
method, the self
keyword is used to refer to the actual instance of the class assigning property values
Class Attributes
var ferris = Student()
ferris.name = "Ferris Bueller"ferris.year = 12ferris.gpa = 3.81ferris.honors = false
Inherit {.row-span-2}
Suppose we have a BankAccount class:
class BankAccount { var balance = 0.0 func deposit(amount: Double) { balance += amount } func withdraw(amount: Double) { balance -= amount }}
SavingsAccount
extends BankAccount
class
class SavingsAccount: BankAccount { var interest = 0.0
func addInterest() { let interest = balance *0.005 self.deposit(amount: interest) }}
The new SavingsAccount
class (subclass) automatically gets all the characteristics of the BankAccount
class
(superclass). Additionally, the SavingsAccount
class defines an .interest
property and an .addInterest()
method.
Example
use data type
class Student { var name: String var year: Int var gpa: Double var honors: Bool}
Use default property values
class Student { var name = "" var gpa = 0.0 var honors = false}
This is an example of a struct definition and a class definition
struct Resolution { var width = 0 var height = 0}class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 var name: String?}
The Resolution
structure definition and the VideoMode
class definition only describe the appearance of Resolution
or VideoMode
, create an instance of the structure or class:
let resolution = Resolution(width: 1920)let someVideoMode = VideoMode()
Enumerate
Define the enumeration
enum Day { case monday case tuesday case wednesday case thursday case friday case saturday case sunday}
let casualWorkday: Day = .friday
Switch statement
enum Dessert { case cake(flavor: String) case vanillaIceCream(scoops: Int) case brownie}
let customerOrder: Dessert = .cake(flavor: "Red Velvet")switch customerOrder { case let .cake(flavor): print("You ordered a \(flavor) cake") case .brownie: print("You ordered a chocolate cake")}// prints: "You ordered a red velvet cake"
CaseIterable
enum Season: CaseIterable { case winter case spring case summer case falls}
for season in Season.allCases { print(season)}
Add conformance to the CaseIterable
protocol to access the allCases
property, which returns an array of all cases of
the enumeration
Original value
enum Beatle: String { case john paul george ringo}
print("The Beatles are \(Beatle.john.rawValue).")// print: The Beatles are john.
Related values
enum Dessert { case cake(flavor: String) case vanillaIceCream(scoops: Int) case brownie}
let order: Dessert = .cake(flavor: "Red Velvet")
instance method {.row-span-2}
enum Traffic { case light case heavy
mutating func reportAccident() { self = .heavy }}
var currentTraffic: Traffic = .light
currentTraffic.reportAccident()// currentTraffic is now .heavy
Just like classes and structs, enumerations can have instance methods. If an instance method mutates the value of the
enum, it needs to be marked mutating
Initialize from primitive value
enum Hello: String { case english = "Hello" case japanese = "Hello!" case emoji = "👋"}let hello1 = Hello(rawValue: "Hello!")let hello2 = Hello(rawValue: "Привет")print(hello1) // Optional(Hello.japanese)print(hello2) // nil
Computed properties
enum ShirtSize: String { case small = "S" case medium = "M" case large = "L" case extraLarge = "XL" var description: String { return "The size of this shirt is \(self.rawValue)" }}
Extensions
What are extensions?
Extensions is a way to add new add new functionality to existing classes, structures, enumerations, or protocol types. This includes adding new methods, properties, initializers, and more.
Why use extensions?
Extensions are particularly useful for organizing and modularizing our code without needing to modify the original type, especially when we don’t have access to the original source code.
Extension syntax
extension SomeType { // New functionalities to be added}
Computed properties
extension Int { var isEven: Bool { self % 2 == 0 }}
print(4.isEven) // Outputs: trueprint(7.isEven) // Outputs: false
Methods
extension String { func reverse() -> String { String(self.reversed()) }}
print("abc".reverse()) // Output: cba
Mutating methods
extension Int { mutating func square() { self = self * self }}
var number = 5number.square()print(number) // Output: 25
Initializers
extension Date { init?(timestamp: Double) { self.init(timeIntervalSince1970: timestamp) }}
let timestamp = 1693982400.0 // Unix timestamp for 2023-09-06 06:40:00if let date = Date(timestamp: timestamp) { print(date) // Output: 2023-09-06 06:40:00 +0000}
Subscripts
extension String { subscript(index: Int) -> Character { self[self.index(startIndex, offsetBy: index)] }}
print("Swift"[0]) // Output: Sprint("Swift"[1]) // Output: wprint("Swift"[2]) // Output: iprint("Swift"[3]) // Output: fprint("Swift"[4]) // Output: t
Protocol extensions {.row-span-2}
It works pretty much like abstract classes when regarding a functionality we want to be available in all the classes that implements some protocol (without having to inherit from a base common class).
// Define a protocolprotocol Describable { func describe() -> String}
// Provide a default implementation using a protocol extensionextension Describable { func describe() -> String { "This is a generic description" }}
// Define a struct that conforms Describable protocolstruct Person: Describable { var name: String var age: Int
// Overriding the default implementation func describe() -> String { "My name is \(name) and I am \(age) years old." }}
struct Employee: Describable { var name: String var age: Int
// Using the default implementation}
// By just implementing the protocol the describe() method is available
let person = Person(name: "Ivan", age: 21)let employee = Employee(name: "Saul", age: 25)
print(person.describe()) // Output: My name is Ivan and I am 21 years old.print(employee.describe()) // Output: This is a generic description
Constraints for extensions
This is especially useful when we want to add functionality to a type that conforms to a specific protocol or has certain conditions.
extension Array where Element: Numeric { func sum() -> Element { reduce(0, +) }}
let numbers = [1, 2, 3, 4, 5]print(numbers.sum()) // Output: 15
let doubles = [1.5, 2.5, 3.5]print(doubles.sum()) // Output: 7.5
// This will not work because String is not Numeric// let strings = ["a", "b", "c"]// print(strings.sum()) // Error: Cannot invoke 'sum' with an array of strings
Organizing code with extensions
Extensions are not limited to adding functionality; they are also handy for code organization. We can group related methods, properties or views in separate extensions.
import SwiftUI
struct HomeView: View { var body: some View { ScrollView { header // Add other views } }}
extension HomeView { private var header: some View { Text("Header ...") }}
#Preview { HomeView()}
Generics
What are generics?
Generics in Swift are a feature that allows us to create functions, classes, structures, and protocols that can work with any data type.
Why use generics?
Generics enable us to write clear and concise code that works with any data type. By using placeholders (like T
), this reduces the risk of introducing bugs.
Type parameters {.row-span-2}
func foo<T, U>(a: T, b: U) { // ...}
struct Foo<T, U> { var a: T // ...}
The placeholders T
is an example of a type parameter, are written inside angle brackets(such as <T>
).
Generic Data Structures
struct Box<T> { var value: T}let intBox = Box(value: 10)let stringBox = Box(value: "Hello")
print(intBox.value) // Output: 10print(stringBox.value) // Output: "Hello"
Generic Functions {.row-span-2}
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var a = 10var b = 20swapValues(&a, &b)print(a) // Output: 20print(b) // Output: 10
var c = "Hello"var d = "World"swapValues(&c, &d)print(c) // Output: "World"print(d) // Output: "Hello"
Constraints on Generics
func sum<T: Numeric>(_ array: [T]) -> T { array.reduce(0, +)}
print(sum([1, 1.5, 2])) // Output: 4.5
// This will not work because String is not Numeric// print(sum(["a", "b", "c"]))// Error: function 'sum' requires that 'String' conform to 'Numeric'
Associated Types
protocol Foo { associatedtype T func foo() -> T}
Associated types are used in protocols to define a placeholder for a type that will be specified later. They act as a generic placeholder. The exact type isn’t defined in the protocol itself; instead, it’s determined when a class, struct, or enum conforms to the protocol.
Generic Protocols {.row-span-2}
protocol Storage { associatedtype Item func store(item: Item) func retrieve() -> Item?}
class SimpleStorage<T>: Storage { private var items: [T] = []
func store(item: T) { items.append(item) }
func retrieve() -> T? { return items.isEmpty ? nil : items.removeLast() }}
let intStorage = SimpleStorage<Int>()intStorage.store(item: 42)print(intStorage.retrieve() ?? "Empty") // Output: 42
Generic Typealiases
Generic typealiases allow us to create a new name for an existing type (i.e., they would not introduce a new type).
typealias StringDictionary<T> = [String: T]typealias IntFunction<T> = (Int) -> Inttypealias Vector<T> = (T, T, T)
Also See
- Swift Documentation (Official) (swift.or)
- Swift Programming Language (Official) (swift.or)
- One-Stop Quick Reference for Swift Developers (swiftly.dev)