Swift Cheatsheet¶
Swift - Apple's Powerful and Intuitive Programming Language
Swift is a powerful and intuitive programming language for iOS, macOS, watchOS, and tvOS. Writing Swift code is interactive and fun, the syntax is concise yet expressive, and Swift includes modern features developers love.
Table of Contents¶
- Installation
- Basic Syntax
- Variables and Constants
- Data Types
- Operators
- Control Flow
- Functions
- Closures
- Classes and Structures
- Properties
- Methods
- Inheritance
- Protocols
- Extensions
- Generics
- Error Handling
- Memory Management
- Concurrency
- Collections
- Optionals
- Best Practices
Installation¶
Xcode Installation¶
# Install Xcode from Mac App Store
# Xcode includes Swift compiler and runtime
# Verify Swift installation
swift --version
# Swift REPL (Read-Eval-Print Loop)
swift
# Swift Package Manager
swift package init
swift build
swift run
Swift on Linux¶
# Download Swift for Linux from swift.org
# Extract and add to PATH
# Ubuntu/Debian
sudo apt-get install clang libicu-dev
# Install Swift
tar xzf swift-5.8-RELEASE-ubuntu20.04.tar.gz
export PATH=/path/to/swift-5.8-RELEASE-ubuntu20.04/usr/bin:$PATH
# Verify installation
swift --version
Basic Syntax¶
Hello World¶
// Simple print statement
print("Hello, World!")
// Multi-line string
let multilineString = """
This is a
multi-line string
in Swift
"""
print(multilineString)
// String interpolation
let name = "Swift"
let version = 5.8
print("Hello, \(name) \(version)!")
// Comments
// This is a single-line comment
/*
This is a
multi-line comment
*/
/// This is a documentation comment
/// - Parameter name: The name to greet
/// - Returns: A greeting string
func greet(name: String) -> String {
return "Hello, \(name)!"
}
Semicolons and Line Breaks¶
// Semicolons are optional
let a = 1
let b = 2
// Multiple statements on one line require semicolons
let x = 1; let y = 2
// Line breaks are used to separate statements
let firstName = "John"
let lastName = "Doe"
let fullName = firstName + " " + lastName
Variables and Constants¶
Declaration¶
// Variables (mutable)
var variableName = "I can change"
var age = 25
var height: Double = 5.9
// Constants (immutable)
let constantName = "I cannot change"
let pi = 3.14159
let maxUsers: Int = 100
// Type annotation
var explicitString: String = "This is a string"
var explicitInt: Int = 42
var explicitDouble: Double = 3.14
// Multiple variable declaration
var x = 0.0, y = 0.0, z = 0.0
// Deferred initialization
let deferredConstant: String
if someCondition {
deferredConstant = "Value A"
} else {
deferredConstant = "Value B"
}
Naming Conventions¶
// Use camelCase for variables and functions
var userName = "john_doe"
var isLoggedIn = true
func calculateTotalPrice() -> Double { return 0.0 }
// Use PascalCase for types
class UserManager { }
struct DatabaseConnection { }
enum NetworkError { }
// Use SCREAMING_SNAKE_CASE for constants
let MAX_RETRY_COUNT = 3
let API_BASE_URL = "https://api.example.com"
// Use descriptive names
var currentUserAge = 25 // Good
var a = 25 // Bad
// Boolean variables should be questions
var isVisible = true
var hasPermission = false
var canEdit = true
Data Types¶
Basic Types¶
// Integer types
let smallInt: Int8 = 127
let mediumInt: Int16 = 32767
let normalInt: Int32 = 2147483647
let bigInt: Int64 = 9223372036854775807
let unsignedInt: UInt = 42
// Floating-point types
let floatNumber: Float = 3.14159
let doubleNumber: Double = 3.141592653589793
// Boolean
let isTrue: Bool = true
let isFalse: Bool = false
// Character and String
let singleCharacter: Character = "A"
let stringValue: String = "Hello, Swift!"
// Type inference
let inferredInt = 42 // Int
let inferredDouble = 3.14 // Double
let inferredString = "Text" // String
let inferredBool = true // Bool
String Manipulation¶
// String creation
let emptyString = ""
let anotherEmptyString = String()
// String interpolation
let name = "Alice"
let age = 30
let message = "My name is \(name) and I'm \(age) years old."
// Multi-line strings
let poem = """
Roses are red,
Violets are blue,
Swift is awesome,
And so are you!
"""
// String operations
let greeting = "Hello"
let world = "World"
let combined = greeting + ", " + world + "!"
// String properties and methods
let text = "Swift Programming"
print(text.count) // 17
print(text.isEmpty) // false
print(text.uppercased()) // SWIFT PROGRAMMING
print(text.lowercased()) // swift programming
print(text.hasPrefix("Swift")) // true
print(text.hasSuffix("ing")) // true
// String indexing
let str = "Hello"
let firstChar = str[str.startIndex] // H
let lastChar = str[str.index(before: str.endIndex)] // o
let secondChar = str[str.index(str.startIndex, offsetBy: 1)] // e
// Substring
let range = str.index(str.startIndex, offsetBy: 1)..<str.index(str.startIndex, offsetBy: 4)
let substring = str[range] // "ell"
Collections Overview¶
// Array
var numbers = [1, 2, 3, 4, 5]
var strings: [String] = ["apple", "banana", "cherry"]
var emptyArray: [Int] = []
// Set
var uniqueNumbers: Set<Int> = [1, 2, 3, 3, 4] // {1, 2, 3, 4}
var emptySet: Set<String> = []
// Dictionary
var ages = ["Alice": 30, "Bob": 25, "Charlie": 35]
var emptyDict: [String: Int] = [:]
// Tuple
let coordinates = (x: 10, y: 20)
let httpStatus = (404, "Not Found")
let person = (name: "John", age: 30, isEmployed: true)
Operators¶
Arithmetic Operators¶
let a = 10
let b = 3
// Basic arithmetic
let sum = a + b // 13
let difference = a - b // 7
let product = a * b // 30
let quotient = a / b // 3
let remainder = a % b // 1
// Unary operators
let positive = +a // 10
let negative = -a // -10
// Compound assignment
var x = 5
x += 3 // x is now 8
x -= 2 // x is now 6
x *= 2 // x is now 12
x /= 3 // x is now 4
x %= 3 // x is now 1
Comparison Operators¶
let a = 5
let b = 10
// Comparison
let isEqual = (a == b) // false
let isNotEqual = (a != b) // true
let isGreater = (a > b) // false
let isLess = (a < b) // true
let isGreaterOrEqual = (a >= b) // false
let isLessOrEqual = (a <= b) // true
// String comparison
let str1 = "apple"
let str2 = "banana"
let result = str1 < str2 // true (alphabetical order)
// Tuple comparison
let tuple1 = (1, "zebra")
let tuple2 = (2, "apple")
let tupleResult = tuple1 < tuple2 // true (compares first elements)
Logical Operators¶
let a = true
let b = false
// Logical NOT
let notA = !a // false
// Logical AND
let andResult = a && b // false
// Logical OR
let orResult = a || b // true
// Short-circuit evaluation
let result = a || expensiveFunction() // expensiveFunction() not called if a is true
Range Operators¶
// Closed range (includes both endpoints)
let closedRange = 1...5 // 1, 2, 3, 4, 5
// Half-open range (excludes upper bound)
let halfOpenRange = 1..<5 // 1, 2, 3, 4
// One-sided ranges
let oneSidedRange1 = 2... // 2 to end of collection
let oneSidedRange2 = ...2 // beginning to 2
let oneSidedRange3 = ..<2 // beginning to 2 (excluding 2)
// Using ranges
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let subset = numbers[2...5] // [3, 4, 5, 6]
for i in 1...5 {
print(i) // Prints 1, 2, 3, 4, 5
}
Nil-Coalescing Operator¶
let optionalName: String? = nil
let defaultName = "Anonymous"
// Nil-coalescing operator
let name = optionalName ?? defaultName // "Anonymous"
// Equivalent to:
let name2 = optionalName != nil ? optionalName! : defaultName
// Chaining nil-coalescing
let a: String? = nil
let b: String? = nil
let c: String? = "Hello"
let result = a ?? b ?? c ?? "Default" // "Hello"
Control Flow¶
Conditional Statements¶
// if statement
let temperature = 25
if temperature > 30 {
print("It's hot!")
} else if temperature > 20 {
print("It's warm.")
} else {
print("It's cool.")
}
// Ternary operator
let message = temperature > 25 ? "Warm" : "Cool"
// guard statement
func processUser(name: String?) {
guard let userName = name, !userName.isEmpty else {
print("Invalid name")
return
}
print("Processing user: \(userName)")
}
// if let (optional binding)
let optionalNumber: Int? = 42
if let number = optionalNumber {
print("Number is \(number)")
} else {
print("Number is nil")
}
// Multiple optional binding
let optionalName: String? = "Alice"
let optionalAge: Int? = 30
if let name = optionalName, let age = optionalAge, age >= 18 {
print("\(name) is an adult")
}
Switch Statements¶
let character = "a"
switch character {
case "a", "e", "i", "o", "u":
print("Vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("Consonant")
default:
print("Not a letter")
}
// Switch with ranges
let score = 85
switch score {
case 90...100:
print("A")
case 80..<90:
print("B")
case 70..<80:
print("C")
case 60..<70:
print("D")
default:
print("F")
}
// Switch with tuples
let point = (1, 1)
switch point {
case (0, 0):
print("Origin")
case (_, 0):
print("On x-axis")
case (0, _):
print("On y-axis")
case (-2...2, -2...2):
print("Inside the box")
default:
print("Outside the box")
}
// Switch with value binding
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("On x-axis at x = \(x)")
case (0, let y):
print("On y-axis at y = \(y)")
case let (x, y):
print("Point at (\(x), \(y))")
}
// Switch with where clause
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("On the line x == y")
case let (x, y) where x == -y:
print("On the line x == -y")
case let (x, y):
print("Point at (\(x), \(y))")
}
Loops¶
// for-in loop
let numbers = [1, 2, 3, 4, 5]
for number in numbers {
print(number)
}
// for-in with range
for i in 1...5 {
print("Count: \(i)")
}
// for-in with stride
for i in stride(from: 0, to: 10, by: 2) {
print(i) // 0, 2, 4, 6, 8
}
// for-in with enumerated
let fruits = ["apple", "banana", "cherry"]
for (index, fruit) in fruits.enumerated() {
print("\(index): \(fruit)")
}
// while loop
var count = 0
while count < 5 {
print("Count: \(count)")
count += 1
}
// repeat-while loop (do-while equivalent)
var number = 0
repeat {
print("Number: \(number)")
number += 1
} while number < 3
// Loop control
for i in 1...10 {
if i == 3 {
continue // Skip this iteration
}
if i == 8 {
break // Exit the loop
}
print(i)
}
// Labeled statements
outerLoop: for i in 1...3 {
innerLoop: for j in 1...3 {
if i == 2 && j == 2 {
break outerLoop
}
print("i: \(i), j: \(j)")
}
}
Functions¶
Basic Functions¶
// Simple function
func greet() {
print("Hello!")
}
greet()
// Function with parameters
func greet(name: String) {
print("Hello, \(name)!")
}
greet(name: "Alice")
// Function with return value
func add(a: Int, b: Int) -> Int {
return a + b
}
let sum = add(a: 5, b: 3)
// Function with multiple return values
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("Min: \(bounds.min), Max: \(bounds.max)")
Function Parameters¶
// External and internal parameter names
func greet(person name: String, from hometown: String) -> String {
return "Hello \(name)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Omitting external parameter names
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let result = add(5, 3) // No parameter labels
// Default parameter values
func greet(name: String, greeting: String = "Hello") -> String {
return "\(greeting), \(name)!"
}
print(greet(name: "Alice")) // Uses default greeting
print(greet(name: "Bob", greeting: "Hi")) // Uses custom greeting
// Variadic parameters
func average(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
print(average(1, 2, 3, 4, 5)) // 3.0
// In-out parameters
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt: \(someInt), anotherInt: \(anotherInt)")
Function Types¶
// Function as a type
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
// Variable of function type
var mathFunction: (Int, Int) -> Int = addTwoInts
print(mathFunction(2, 3)) // 5
mathFunction = multiplyTwoInts
print(mathFunction(2, 3)) // 6
// Function as parameter
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Function as return type
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
let moveNearerToZero = chooseStepFunction(backward: true)
print(moveNearerToZero(5)) // 4
Nested Functions¶
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
Closures¶
Basic Closures¶
// Closure expression syntax
let numbers = [1, 2, 3, 4, 5]
// Full closure syntax
let doubled = numbers.map({ (number: Int) -> Int in
return number * 2
})
// Inferring type from context
let doubled2 = numbers.map({ number in
return number * 2
})
// Implicit returns from single-expression closures
let doubled3 = numbers.map({ number in number * 2 })
// Shorthand argument names
let doubled4 = numbers.map({ $0 * 2 })
// Trailing closure syntax
let doubled5 = numbers.map { $0 * 2 }
// Multiple trailing closures
func loadPicture(from server: String, completion: (String) -> Void, onFailure: (String) -> Void) {
// Implementation
}
loadPicture(from: "server.com") { picture in
print("Loaded: \(picture)")
} onFailure: { error in
print("Failed: \(error)")
}
Capturing Values¶
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen()) // 10
print(incrementByTen()) // 20
print(incrementByTen()) // 30
let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementBySeven()) // 7
print(incrementByTen()) // 40 (still independent)
Escaping Closures¶
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure() // Called before function returns
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x) // 200
completionHandlers.first?()
print(instance.x) // 100
Autoclosures¶
// Autoclosure delays evaluation
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: "Alex") // String is automatically wrapped in closure
// Autoclosure with escaping
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders("Barry")
collectCustomerProviders("Daniella")
print("Collected \(customerProviders.count) closures.")
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
Classes and Structures¶
Basic Syntax¶
// Structure
struct Resolution {
var width = 0
var height = 0
// Computed property
var area: Int {
return width * height
}
// Method
mutating func scale(by factor: Int) {
width *= factor
height *= factor
}
}
// Class
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
// Initializer
init(name: String) {
self.name = name
}
// Method
func describe() -> String {
return "VideoMode: \(name ?? "Unknown")"
}
}
// Usage
var someResolution = Resolution(width: 1920, height: 1080)
print(someResolution.area) // 2073600
let someVideoMode = VideoMode(name: "HD")
someVideoMode.resolution = someResolution
someVideoMode.frameRate = 30.0
Initializers¶
struct Celsius {
var temperatureInCelsius: Double
// Designated initializer
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
// Default initializer
init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
let bodyTemperature = Celsius(37.0)
// Class initializers
class Person {
let name: String
var age: Int
// Designated initializer
init(name: String, age: Int) {
self.name = name
self.age = age
}
// Convenience initializer
convenience init(name: String) {
self.init(name: name, age: 0)
}
}
// Failable initializers
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
let someCreature = Animal(species: "Giraffe")
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
Value vs Reference Types¶
// Structures are value types
struct Point {
var x = 0.0, y = 0.0
}
var point1 = Point(x: 1.0, y: 2.0)
var point2 = point1 // Copy is made
point2.x = 3.0
print(point1.x) // 1.0 (unchanged)
print(point2.x) // 3.0
// Classes are reference types
class Size {
var width = 0.0, height = 0.0
}
let size1 = Size()
size1.width = 10.0
let size2 = size1 // Same reference
size2.width = 20.0
print(size1.width) // 20.0 (changed)
print(size2.width) // 20.0
// Identity operators for reference types
if size1 === size2 {
print("size1 and size2 refer to the same instance")
}
if size1 !== size2 {
print("size1 and size2 refer to different instances")
}
Properties¶
Stored Properties¶
struct FixedLengthRange {
var firstValue: Int
let length: Int
// Lazy stored property
lazy var expensiveProperty: String = {
// Expensive computation
return "Computed value"
}()
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
rangeOfThreeItems.firstValue = 6
// Property observers
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
stepCounter.totalSteps = 360
Computed Properties¶
struct Circle {
var radius: Double = 0.0
var area: Double {
get {
return Double.pi * radius * radius
}
set(newArea) {
radius = sqrt(newArea / Double.pi)
}
}
// Read-only computed property
var circumference: Double {
return 2.0 * Double.pi * radius
}
}
var circle = Circle(radius: 5.0)
print(circle.area) // 78.54
circle.area = 100.0
print(circle.radius) // 5.64
Property Wrappers¶
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height) // 0
rectangle.height = 10
print(rectangle.height) // 10
rectangle.height = 24
print(rectangle.height) // 12
// Property wrapper with parameters
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
struct NarrowRectangle {
@SmallNumber(maximum: 5) var height: Int = 2
@SmallNumber(maximum: 4) var width: Int = 3
}
Type Properties¶
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
print(SomeStructure.storedTypeProperty) // "Some value."
print(SomeEnumeration.computedTypeProperty) // 6
print(SomeClass.overrideableComputedTypeProperty) // 107
Methods¶
Instance Methods¶
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter = Counter()
counter.increment()
counter.increment(by: 5)
print(counter.count) // 6
counter.reset()
// Mutating methods for structures
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
mutating func moveToOrigin() {
self = Point(x: 0.0, y: 0.0)
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("Point is now at (\(somePoint.x), \(somePoint.y))")
Type Methods¶
class SomeClass {
class func someTypeMethod() {
print("Type method called")
}
static func anotherTypeMethod() {
print("Static type method called")
}
}
SomeClass.someTypeMethod()
SomeClass.anotherTypeMethod()
struct LevelTracker {
static var highestUnlockedLevel = 1
var currentLevel = 1
static func unlock(_ level: Int) {
if level > highestUnlockedLevel {
highestUnlockedLevel = level
}
}
static func isUnlocked(_ level: Int) -> Bool {
return level <= highestUnlockedLevel
}
@discardableResult
mutating func advance(to level: Int) -> Bool {
if LevelTracker.isUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
Inheritance¶
Basic Inheritance¶
// Base class
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// Do nothing - an arbitrary vehicle doesn't necessarily make a noise
}
}
// Subclass
class Bicycle: Vehicle {
var hasBasket = false
override var description: String {
return "Bicycle: \(super.description)"
}
}
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
override var description: String {
return "Tandem: \(super.description) with \(currentNumberOfPassengers) passengers"
}
}
// Usage
let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print(bicycle.description)
let tandem = Tandem()
tandem.currentSpeed = 22.0
tandem.currentNumberOfPassengers = 2
print(tandem.description)
Overriding¶
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
override var currentSpeed: Double {
didSet {
print("Train speed changed to \(currentSpeed)")
}
}
}
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
override func makeNoise() {
print("Vroom Vroom")
}
}
let train = Train()
train.makeNoise() // "Choo Choo"
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print(car.description) // "traveling at 25.0 miles per hour in gear 3"
Preventing Overrides¶
class FinalVehicle {
final var maxSpeed = 100.0
final func startEngine() {
print("Engine started")
}
}
// This would cause a compile error:
// class FastCar: FinalVehicle {
// override func startEngine() { } // Error!
// }
// Final class cannot be subclassed
final class ImmutableClass {
let value = 42
}
// This would cause a compile error:
// class SubClass: ImmutableClass { } // Error!
Protocols¶
Basic Protocols¶
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
func someMethod()
static func someTypeMethod()
}
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
let john = Person(fullName: "John Appleseed")
let ncc1701 = Starship(name: "Enterprise", prefix: "USS")
Method Requirements¶
protocol Togglable {
mutating func toggle()
}
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle() // lightSwitch is now .on
Protocol Inheritance¶
protocol InheritingProtocol: SomeProtocol {
func anotherMethod()
}
protocol PrettyTextRepresentable: CustomStringConvertible {
var prettyTextualDescription: String { get }
}
extension Person: PrettyTextRepresentable {
var description: String {
return fullName
}
var prettyTextualDescription: String {
return "Person: \(fullName)"
}
}
Class-Only Protocols¶
protocol SomeClassOnlyProtocol: AnyObject {
func someMethod()
}
class SomeClass: SomeClassOnlyProtocol {
func someMethod() {
print("Method implemented")
}
}
// This would cause an error:
// struct SomeStruct: SomeClassOnlyProtocol { } // Error!
Protocol Composition¶
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct PersonStruct: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = PersonStruct(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
Optional Protocol Requirements¶
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
Protocol Extensions¶
extension Collection {
var isNotEmpty: Bool {
return !isEmpty
}
}
let numbers = [1, 2, 3]
print(numbers.isNotEmpty) // true
// Protocol extension with constraints
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
let equalNumbers = [1, 1, 1]
print(equalNumbers.allEqual()) // true
Extensions¶
Basic Extensions¶
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters") // 0.0254 meters
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters") // 0.914399970739201 meters
Adding Methods¶
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
mutating func square() {
self = self * self
}
}
3.repetitions {
print("Hello!")
}
var someInt = 3
someInt.square() // someInt is now 9
Adding Initializers¶
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
Adding Subscripts¶
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0] // 5
746381295[1] // 9
746381295[2] // 2
746381295[8] // 7
Adding Nested Types¶
extension Int {
enum Kind {
case negative, zero, positive
}
var kind: Kind {
switch self {
case 0:
return .zero
case let x where x > 0:
return .positive
default:
return .negative
}
}
}
func printIntegerKinds(_ numbers: [Int]) {
for number in numbers {
switch number.kind {
case .negative:
print("- ", terminator: "")
case .zero:
print("0 ", terminator: "")
case .positive:
print("+ ", terminator: "")
}
}
print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7]) // + + - 0 - 0 +
Generics¶
Generic Functions¶
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
print("someInt: \(someInt), anotherInt: \(anotherInt)")
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
print("someString: \(someString), anotherString: \(anotherString)")
Generic Types¶
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
var isEmpty: Bool {
return items.isEmpty
}
var count: Int {
return items.count
}
}
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
let fromTheTop = stackOfStrings.pop() // "cuatro"
Type Constraints¶
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// Generic type with multiple constraints
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
if someContainer.count != anotherContainer.count {
return false
}
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
return true
}
Associated Types¶
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
struct IntStack: Container {
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container protocol conformance
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
extension Stack: Container {
// No need to declare typealias Item = Element
// Swift can infer that Element is the appropriate type for Item
mutating func append(_ item: Element) {
self.push(item)
}
subscript(i: Int) -> Element {
return items[i]
}
}
Generic Where Clauses¶
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
if someContainer.count != anotherContainer.count {
return false
}
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
return true
}
// Extensions with generic where clauses
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
Error Handling¶
Defining Errors¶
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
// Custom error with localized description
struct ValidationError: Error, LocalizedError {
let message: String
var errorDescription: String? {
return message
}
}
Throwing Functions¶
func canThrowErrors() throws -> String {
// Function implementation
return "Success"
}
func cannotThrowErrors() -> String {
// Function implementation
return "Success"
}
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.outOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("Dispensing \(name)")
}
}
Handling Errors¶
let vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
// do-catch
do {
try vendingMachine.vend(itemNamed: "Candy Bar")
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
print("Unexpected error: \(error).")
}
// Multiple catch patterns
do {
try vendingMachine.vend(itemNamed: "Candy Bar")
} catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds {
print("Invalid selection or insufficient funds.")
} catch {
print("Other error: \(error)")
}
// Converting errors to optional values
func someThrowingFunction() throws -> Int {
return 42
}
let x = try? someThrowingFunction()
// x is of type Int? and equals 42
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
// Disabling error propagation
let z = try! someThrowingFunction()
// If someThrowingFunction() throws an error, you'll get a runtime error
Defer Statements¶
func processFile(filename: String) throws {
let file = openFile(named: filename)
defer {
closeFile(file)
}
// Work with the file
if someCondition {
return // closeFile(file) is called here
}
if anotherCondition {
throw SomeError.errorCondition // closeFile(file) is called here too
}
// closeFile(file) is called here as well
}
// Multiple defer statements
func deferExample() {
defer { print("First defer") }
defer { print("Second defer") }
defer { print("Third defer") }
print("End of function")
}
// Output:
// End of function
// Third defer
// Second defer
// First defer
Result Type¶
enum NetworkError: Error {
case badURL
case requestFailed
case unknown
}
func fetchData(from urlString: String) -> Result<Data, NetworkError> {
guard let url = URL(string: urlString) else {
return .failure(.badURL)
}
// Simulate network request
let success = Bool.random()
if success {
return .success(Data())
} else {
return .failure(.requestFailed)
}
}
// Using Result
let result = fetchData(from: "https://example.com")
switch result {
case .success(let data):
print("Received data: \(data)")
case .failure(let error):
print("Error: \(error)")
}
// Result with map and flatMap
let transformedResult = result
.map { data in
return String(data: data, encoding: .utf8) ?? ""
}
.mapError { error in
return "Network error: \(error)"
}
Memory Management¶
Automatic Reference Counting (ARC)¶
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed")
// Prints "John Appleseed is being initialized"
reference2 = reference1
reference3 = reference1
// Strong reference count is now 3
reference1 = nil
reference2 = nil
// Strong reference count is now 1
reference3 = nil
// Strong reference count is now 0
// Prints "John Appleseed is being deinitialized"
Strong Reference Cycles¶
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
unit4A = nil
// Neither deinitializer is called - memory leak!
Weak References¶
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // Weak reference
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// Prints "John Appleseed is being deinitialized"
print(unit4A!.tenant) // nil
unit4A = nil
// Prints "Apartment 4A is being deinitialized"
Unowned References¶
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer // Unowned reference
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"
Closures and Strong Reference Cycles¶
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = { [unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil
// Prints "p is being deinitialized"
// Capture list with weak reference
class SomeClass {
var value = 0
func doSomething() {
let closure = { [weak self] in
guard let self = self else { return }
print("Value: \(self.value)")
}
closure()
}
}
Concurrency¶
Async/Await (iOS 15+)¶
// Async function
func fetchUserID(from server: String) async -> Int {
let userID = 501
return userID
}
func fetchUsername(from server: String) async -> String {
let username = "Taylor"
return username
}
// Calling async functions
func connectUser(to server: String) async {
async let userID = fetchUserID(from: server)
async let username = fetchUsername(from: server)
let greeting = await "Hello \(username), user ID \(userID)"
print(greeting)
}
// Async sequence
func fetchData() async throws -> [String] {
// Simulate network delay
try await Task.sleep(nanoseconds: 1_000_000_000)
return ["Item 1", "Item 2", "Item 3"]
}
// Using async in SwiftUI
struct ContentView: View {
@State private var data: [String] = []
var body: some View {
List(data, id: \.self) { item in
Text(item)
}
.task {
do {
data = try await fetchData()
} catch {
print("Failed to fetch data: \(error)")
}
}
}
}
Tasks¶
// Creating tasks
func performWork() async {
let task1 = Task {
return await fetchUserID(from: "server1")
}
let task2 = Task {
return await fetchUsername(from: "server2")
}
let userID = await task1.value
let username = await task2.value
print("User: \(username) (\(userID))")
}
// Task cancellation
func cancellableWork() async {
let task = Task {
for i in 1...10 {
if Task.isCancelled {
print("Task was cancelled")
return
}
print("Working on step \(i)")
try await Task.sleep(nanoseconds: 1_000_000_000)
}
}
// Cancel after 3 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
task.cancel()
}
await task.value
}
// Task groups
func fetchMultipleUsers() async -> [String] {
await withTaskGroup(of: String.self) { group in
let servers = ["server1", "server2", "server3"]
for server in servers {
group.addTask {
return await fetchUsername(from: server)
}
}
var usernames: [String] = []
for await username in group {
usernames.append(username)
}
return usernames
}
}
Actors¶
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Bool {
if balance >= amount {
balance -= amount
return true
}
return false
}
func getBalance() -> Double {
return balance
}
}
// Using actors
func bankingExample() async {
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
let success = await account.withdraw(amount: 200)
let balance = await account.getBalance()
print("Withdrawal successful: \(success), Balance: \(balance)")
}
// MainActor for UI updates
@MainActor
class ViewModel: ObservableObject {
@Published var data: [String] = []
func loadData() async {
let newData = await fetchDataFromNetwork()
// This runs on the main actor automatically
self.data = newData
}
}
func fetchDataFromNetwork() async -> [String] {
// Network operation
return ["Data 1", "Data 2", "Data 3"]
}
AsyncSequence¶
// Custom AsyncSequence
struct Counter: AsyncSequence {
typealias Element = Int
let howHigh: Int
struct AsyncIterator: AsyncIteratorProtocol {
let howHigh: Int
var current = 1
mutating func next() async -> Int? {
guard current <= howHigh else {
return nil
}
let result = current
current += 1
return result
}
}
func makeAsyncIterator() -> AsyncIterator {
return AsyncIterator(howHigh: howHigh)
}
}
// Using AsyncSequence
func useAsyncSequence() async {
for await number in Counter(howHigh: 5) {
print(number)
}
}
// Built-in AsyncSequence operations
func processAsyncData() async {
let numbers = Counter(howHigh: 10)
let evenNumbers = numbers.filter { $0 % 2 == 0 }
let doubled = evenNumbers.map { $0 * 2 }
for await number in doubled {
print("Even doubled: \(number)")
}
}
Collections¶
Arrays¶
// Array creation
var someInts: [Int] = []
var threeDoubles = Array(repeating: 0.0, count: 3)
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
var sixDoubles = threeDoubles + anotherThreeDoubles
// Array literals
var shoppingList: [String] = ["Eggs", "Milk"]
var shoppingList2 = ["Eggs", "Milk"] // Type inferred
// Array operations
shoppingList.append("Flour")
shoppingList += ["Baking Powder"]
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
let firstItem = shoppingList[0]
shoppingList[0] = "Six eggs"
shoppingList[4...6] = ["Bananas", "Apples"]
shoppingList.insert("Maple Syrup", at: 0)
let mapleSyrup = shoppingList.remove(at: 0)
let apples = shoppingList.removeLast()
// Array iteration
for item in shoppingList {
print(item)
}
for (index, value) in shoppingList.enumerated() {
print("Item \(index + 1): \(value)")
}
// Array methods
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
let evens = numbers.filter { $0 % 2 == 0 }
let sum = numbers.reduce(0, +)
let first = numbers.first
let last = numbers.last
let contains = numbers.contains(3)
Sets¶
// Set creation
var letters = Set<Character>()
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// Set operations
favoriteGenres.insert("Jazz")
if let removedGenre = favoriteGenres.remove("Rock") {
print("\(removedGenre)? I'm over it.")
} else {
print("I never much cared for that.")
}
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
} else {
print("It's too funky in here.")
}
// Set iteration
for genre in favoriteGenres {
print("\(genre)")
}
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
// Set operations
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
let union = oddDigits.union(evenDigits).sorted()
let intersection = oddDigits.intersection(evenDigits).sorted()
let subtracting = oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
let symmetricDifference = oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// Set membership and equality
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]
houseAnimals.isSubset(of: farmAnimals) // true
farmAnimals.isSuperset(of: houseAnimals) // true
farmAnimals.isDisjoint(with: cityAnimals) // true
Dictionaries¶
// Dictionary creation
var namesOfIntegers: [Int: String] = [:]
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
var airports2 = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
// Dictionary operations
airports["LHR"] = "London"
airports["LHR"] = "London Heathrow"
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport is not in the airports dictionary.")
}
airports["APL"] = "Apple International"
airports["APL"] = nil
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary does not contain a value for DUB.")
}
// Dictionary iteration
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
for airportCode in airports.keys {
print("Airport code: \(airportCode)")
}
for airportName in airports.values {
print("Airport name: \(airportName)")
}
let airportCodes = [String](airports.keys)
let airportNames = [String](airports.values)
Optionals¶
Optional Basics¶
// Optional declaration
var optionalString: String? = "Hello"
var optionalInt: Int? = nil
// Optional binding
if let actualString = optionalString {
print("The string is \(actualString)")
} else {
print("The string is nil")
}
// Multiple optional binding
if let string = optionalString, let int = optionalInt {
print("Both values exist: \(string), \(int)")
}
// Guard statement
func processOptional(_ value: String?) {
guard let unwrappedValue = value else {
print("Value is nil")
return
}
print("Processing: \(unwrappedValue)")
}
// Nil coalescing operator
let defaultName = "Anonymous"
let username = optionalString ?? defaultName
// Optional chaining
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
john.residence = Residence()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
}
Implicitly Unwrapped Optionals¶
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // Requires an exclamation point
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // No need for an exclamation point
// Still can be treated as optional
if assumedString != nil {
print(assumedString!)
}
if let definiteString = assumedString {
print(definiteString)
}
Optional Map and FlatMap¶
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// Optional map
let doubled = convertedNumber.map { $0 * 2 }
print(doubled) // Optional(246)
// Optional flatMap
func doubleIfEven(_ number: Int) -> Int? {
return number % 2 == 0 ? number * 2 : nil
}
let result = convertedNumber.flatMap(doubleIfEven)
print(result) // nil (because 123 is odd)
// Chaining optionals
let numbers = ["1", "2", "three", "4"]
let validNumbers = numbers.compactMap { Int($0) }
print(validNumbers) // [1, 2, 4]
Best Practices¶
Code Style¶
// Use meaningful names
var userAge = 25 // Good
var a = 25 // Bad
func calculateTotalPrice() -> Double // Good
func calc() -> Double // Bad
// Use type inference when possible
let name = "John" // Good
let name: String = "John" // Unnecessary
// Use guard for early returns
func processUser(_ user: User?) {
guard let user = user else { return }
guard user.isActive else { return }
// Process user
}
// Use trailing closures
numbers.map { $0 * 2 } // Good
numbers.map({ $0 * 2 }) // Less preferred
// Use computed properties for simple calculations
struct Circle {
var radius: Double
var area: Double { // Good
return .pi * radius * radius
}
func getArea() -> Double { // Less preferred
return .pi * radius * radius
}
}
Performance¶
// Use lazy properties for expensive computations
class DataProcessor {
lazy var expensiveData: [String] = {
// Expensive computation
return processLargeDataSet()
}()
private func processLargeDataSet() -> [String] {
// Implementation
return []
}
}
// Use value types when possible
struct Point { // Good - value type
var x: Double
var y: Double
}
class PointClass { // Use only when reference semantics needed
var x: Double
var y: Double
init(x: Double, y: Double) {
self.x = x
self.y = y
}
}
// Use copy-on-write for large value types
struct LargeDataStructure {
private var _data: NSMutableArray
private var data: NSMutableArray {
mutating get {
if !isKnownUniquelyReferenced(&_data) {
_data = _data.mutableCopy() as! NSMutableArray
}
return _data
}
}
init() {
_data = NSMutableArray()
}
}
Error Handling¶
// Use specific error types
enum ValidationError: Error {
case emptyInput
case invalidFormat
case tooShort(minimumLength: Int)
case tooLong(maximumLength: Int)
}
// Provide meaningful error messages
extension ValidationError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyInput:
return "Input cannot be empty"
case .invalidFormat:
return "Input format is invalid"
case .tooShort(let minimumLength):
return "Input must be at least \(minimumLength) characters"
case .tooLong(let maximumLength):
return "Input cannot exceed \(maximumLength) characters"
}
}
}
// Use Result type for better error handling
func validateInput(_ input: String) -> Result<String, ValidationError> {
guard !input.isEmpty else {
return .failure(.emptyInput)
}
guard input.count >= 3 else {
return .failure(.tooShort(minimumLength: 3))
}
return .success(input)
}
Memory Management¶
// Use weak references to avoid retain cycles
class Parent {
var children: [Child] = []
func addChild(_ child: Child) {
child.parent = self
children.append(child)
}
}
class Child {
weak var parent: Parent?
}
// Use capture lists in closures
class ViewController {
var completion: (() -> Void)?
func setupCompletion() {
completion = { [weak self] in
self?.dismiss()
}
}
func dismiss() {
// Implementation
}
}
// Use unowned when reference will never be nil
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
}
Summary¶
Swift is a powerful, modern programming language designed for safety, performance, and expressiveness. Key features include:
- Type Safety: Strong type system prevents many common programming errors
- Memory Safety: Automatic Reference Counting (ARC) manages memory automatically
- Performance: Compiled language with optimizations for speed
- Expressiveness: Clean, readable syntax that's easy to learn and maintain
- Interoperability: Seamless integration with Objective-C and C libraries
- Modern Features: Optionals, generics, closures, and protocol-oriented programming
- Concurrency: Built-in async/await and actor model for safe concurrent programming
- Open Source: Available on multiple platforms beyond Apple's ecosystem
Swift combines the performance and efficiency of compiled languages with the simplicity and interactivity of popular scripting languages, making it an excellent choice for iOS, macOS, watchOS, tvOS, and server-side development.