A comprehensive guide to the fundamental concepts of Swift programming.
Swift provides two ways to declare variables and constants:
Constants are declared with let and cannot be changed after their initial value is set.
let maximumLoginAttempts = 10
let welcomeMessage = "Hello, World!"
// This would cause an error:
// maximumLoginAttempts = 15 // Error: Cannot assign to valueVariables are declared with var and can be modified after initialization.
var currentLoginAttempt = 0
currentLoginAttempt = 1 // This is fine
currentLoginAttempt += 1 // Now it's 2Swift can infer types, but you can also explicitly declare them:
var message: String = "Hello"
var count: Int = 42
let pi: Double = 3.14159
// Type inference works automatically
var inferredMessage = "Hello" // Swift knows this is a String
var inferredCount = 42 // Swift knows this is an Int- Use
letby default - it's safer and helps the compiler optimize your code - Only use
varwhen you know the value will change - Prefer type inference when the type is obvious
Swift is a type-safe language with several built-in data types.
let smallNumber: Int8 = 127 // 8-bit signed integer
let regularNumber: Int = 42 // Platform-native integer (64-bit on modern devices)
let unsignedNumber: UInt = 100 // Unsigned integer (positive only)
// Integer bounds
let minInt = Int.min // -9223372036854775808
let maxInt = Int.max // 9223372036854775807let pi: Double = 3.14159265359 // 64-bit floating point (default)
let smallPi: Float = 3.14159 // 32-bit floating point
// Double has more precision, use it by default
let measurement = 42.5 // Inferred as Doublelet isSwiftFun: Bool = true
let isComplete = false
if isSwiftFun {
print("Learning Swift!")
}let greeting = "Hello, Swift!"
let multiline = """
This is a multi-line string.
It can span multiple lines
and preserves formatting.
"""
// String interpolation
let name = "Alice"
let age = 25
let message = "My name is \(name) and I'm \(age) years old"
// String operations
let length = greeting.count
let uppercase = greeting.uppercased()
let lowercase = greeting.lowercased()let letter: Character = "A"
let emoji: Character = "🎉"Ordered collections of values of the same type.
// Creating arrays
var numbers: [Int] = [1, 2, 3, 4, 5]
var names = ["Alice", "Bob", "Charlie"] // Type inferred as [String]
// Empty array
var emptyArray: [String] = []
var anotherEmpty = [Int]()
// Array operations
numbers.append(6)
numbers.insert(0, at: 0)
numbers.remove(at: 0)
let first = numbers.first // Optional: Int?
let count = numbers.count
// Accessing elements
let firstNumber = numbers[0]
for number in numbers {
print(number)
}Collections of key-value pairs.
// Creating dictionaries
var ages: [String: Int] = ["Alice": 25, "Bob": 30, "Charlie": 35]
var scores = ["Math": 95, "English": 88] // Type inferred as [String: Int]
// Empty dictionary
var emptyDict: [String: Int] = [:]
var anotherEmpty = [String: String]()
// Dictionary operations
ages["David"] = 28 // Add or update
ages["Alice"] = 26 // Update
ages.removeValue(forKey: "Bob")
// Accessing values
if let aliceAge = ages["Alice"] {
print("Alice is \(aliceAge) years old")
}Unordered collections of unique values.
var uniqueNumbers: Set<Int> = [1, 2, 3, 4, 5]
var colors: Set = ["Red", "Green", "Blue"]
// Set operations
uniqueNumbers.insert(6)
uniqueNumbers.remove(1)
let contains = uniqueNumbers.contains(3)
// Set operations
let setA: Set = [1, 2, 3, 4]
let setB: Set = [3, 4, 5, 6]
let union = setA.union(setB) // [1, 2, 3, 4, 5, 6]
let intersection = setA.intersection(setB) // [3, 4]
let difference = setA.subtracting(setB) // [1, 2]Group multiple values into a single compound value.
let person = ("Alice", 25, "Engineer")
let (name, age, job) = person
print("\(name) is \(age) years old and works as an \(job)")
// Named tuple elements
let employee = (name: "Bob", age: 30, role: "Designer")
print(employee.name)
print(employee.role)
// Function returning tuple
func getCoordinates() -> (x: Int, y: Int) {
return (10, 20)
}
let position = getCoordinates()
print("x: \(position.x), y: \(position.y)")Functions are self-contained chunks of code that perform a specific task.
func greet() {
print("Hello, World!")
}
greet() // Call the functionfunc greet(name: String) {
print("Hello, \(name)!")
}
greet(name: "Alice")
// Multiple parameters
func greet(name: String, age: Int) {
print("Hello, \(name)! You are \(age) years old.")
}
greet(name: "Bob", age: 30)// External and internal parameter names
func greet(to name: String, from hometown: String) {
print("Hello, \(name) from \(hometown)!")
}
greet(to: "Alice", from: "San Francisco")
// Omitting argument labels with underscore
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let sum = add(5, 3) // No need for argument labelsfunc add(a: Int, b: Int) -> Int {
return a + b
}
let result = add(a: 5, b: 3)
// Implicit return for single-expression functions
func multiply(a: Int, b: Int) -> Int {
a * b // No 'return' needed
}func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array {
if value < currentMin {
currentMin = value
}
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)")func greet(name: String, greeting: String = "Hello") {
print("\(greeting), \(name)!")
}
greet(name: "Alice") // Uses default "Hello"
greet(name: "Bob", greeting: "Hi") // Uses custom greetingfunc sum(numbers: Int...) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
let result1 = sum(numbers: 1, 2, 3, 4, 5)
let result2 = sum(numbers: 10, 20)func swap(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 5
var y = 10
swap(&x, &y)
print("x: \(x), y: \(y)") // x: 10, y: 5Optionals handle the absence of a value. They represent either a value or nil.
var optionalName: String? = "Alice"
var optionalAge: Int? = nil
// Optional is like a box that either contains a value or is emptylet name: String? = "Alice"
print(name!) // Force unwrap with ! - CRASHES if nil!
// Only use when you're 100% sure the value existslet optionalName: String? = "Alice"
if let name = optionalName {
print("Hello, \(name)!")
} else {
print("No name provided")
}
// Multiple optional bindings
let optionalAge: Int? = 25
if let name = optionalName, let age = optionalAge {
print("\(name) is \(age) years old")
}func greet(name: String?) {
guard let name = name else {
print("No name provided")
return
}
// name is available for the rest of the function
print("Hello, \(name)!")
}let optionalName: String? = nil
let name = optionalName ?? "Guest" // Use "Guest" if nil
print("Hello, \(name)!") // Hello, Guest!
// Useful for providing defaults
let userAge: Int? = nil
let age = userAge ?? 18struct Person {
var name: String
var address: Address?
}
struct Address {
var street: String
var city: String
}
let person = Person(name: "Alice", address: nil)
// Optional chaining with ?
let city = person.address?.city // Returns nil without crashing
print(city ?? "No city")
// Chaining multiple optionals
let streetLength = person.address?.street.count // Returns Optional<Int>// Use when you know a value will exist after initialization
var assumedString: String! = "Hello"
// Can be used like a non-optional
let greeting = assumedString // No need to unwrap
// But it's still an optional internally
if assumedString != nil {
print(assumedString!)
}
// Common in UIKit/SwiftUI outlets
// @IBOutlet var label: UILabel!let optionalNumber: Int? = 5
// Map transforms the value if it exists
let doubled = optionalNumber.map { $0 * 2 } // Optional(10)
// FlatMap is useful for chaining optional operations
let result = optionalNumber.flatMap { value -> Int? in
return value > 0 ? value * 2 : nil
}Swift provides several ways to control the flow of your code.
let temperature = 72
if temperature < 32 {
print("It's freezing!")
} else if temperature < 50 {
print("It's cold!")
} else if temperature < 75 {
print("Nice weather!")
} else {
print("It's hot!")
}
// No parentheses needed around conditions
// Braces are required even for single-line statementsSwift's switch statements are very powerful and must be exhaustive.
let character = "a"
switch character {
case "a":
print("The first letter")
case "z":
print("The last letter")
default:
print("Some other letter")
}
// No implicit fallthrough (no need for 'break')
// Multiple cases
switch character {
case "a", "e", "i", "o", "u":
print("Vowel")
default:
print("Consonant")
}
// Range matching
let age = 25
switch age {
case 0..<18:
print("Minor")
case 18..<65:
print("Adult")
case 65...:
print("Senior")
default:
break
}
// Tuple matching
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")
}
// 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("At (\(x), \(y))")
}
// 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("Just at (\(x), \(y))")
}// Range-based for loops
for i in 1...5 {
print(i) // 1, 2, 3, 4, 5
}
for i in 1..<5 {
print(i) // 1, 2, 3, 4
}
// Array iteration
let names = ["Alice", "Bob", "Charlie"]
for name in names {
print("Hello, \(name)!")
}
// Dictionary iteration
let ages = ["Alice": 25, "Bob": 30]
for (name, age) in ages {
print("\(name) is \(age) years old")
}
// Enumerated iteration (with index)
for (index, name) in names.enumerated() {
print("\(index + 1). \(name)")
}
// Stride for custom steps
for i in stride(from: 0, to: 10, by: 2) {
print(i) // 0, 2, 4, 6, 8
}
// Ignoring values with underscore
for _ in 1...3 {
print("Repeat this 3 times")
}var count = 0
while count < 5 {
print(count)
count += 1
}
// Repeat-while (like do-while in other languages)
var number = 0
repeat {
print(number)
number += 1
} while number < 5for i in 1...10 {
if i % 2 == 0 {
continue // Skip even numbers
}
print(i) // Only prints odd numbers
}for i in 1...10 {
if i == 5 {
break // Exit loop when i equals 5
}
print(i) // 1, 2, 3, 4
}outerLoop: for i in 1...3 {
innerLoop: for j in 1...3 {
if i == 2 && j == 2 {
break outerLoop // Break out of outer loop
}
print("i: \(i), j: \(j)")
}
}func processNumber(_ number: Int?) {
guard let num = number else {
print("No number provided")
return
}
guard num > 0 else {
print("Number must be positive")
return
}
// num is available here and guaranteed to be positive
print("Processing: \(num)")
}Structs are value types that encapsulate related properties and behaviors.
struct Person {
var name: String
var age: Int
}
// Creating instances
var person1 = Person(name: "Alice", age: 25)
var person2 = Person(name: "Bob", age: 30)
// Accessing properties
print(person1.name)
person1.age = 26 // Modify property// Swift automatically provides a memberwise initializer
struct Rectangle {
var width: Double
var height: Double
}
let rect = Rectangle(width: 10.0, height: 5.0)struct Rectangle {
var width: Double
var height: Double
// Computed property
var area: Double {
return width * height
}
// Getter and setter
var perimeter: Double {
get {
return 2 * (width + height)
}
set {
// newValue is the default name for the new value
let side = newValue / 4
width = side
height = side
}
}
}
var rect = Rectangle(width: 10.0, height: 5.0)
print(rect.area) // 50.0
rect.perimeter = 40 // Sets width and height to 10struct StepCounter {
var totalSteps: Int = 0 {
willSet {
print("About to set totalSteps to \(newValue)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
var counter = StepCounter()
counter.totalSteps = 100
counter.totalSteps = 150struct Counter {
var count: Int = 0
// Instance method
func describe() {
print("Current count: \(count)")
}
// Mutating method (required for modifying properties)
mutating func increment() {
count += 1
}
mutating func increment(by amount: Int) {
count += amount
}
mutating func reset() {
count = 0
}
}
var counter = Counter()
counter.increment()
counter.increment(by: 5)
counter.describe() // Current count: 6
counter.reset()struct MathHelper {
static let pi = 3.14159
static let e = 2.71828
static func degreesToRadians(_ degrees: Double) -> Double {
return degrees * pi / 180
}
static func radiansToDegrees(_ radians: Double) -> Double {
return radians * 180 / pi
}
}
// Access without creating an instance
print(MathHelper.pi)
let radians = MathHelper.degreesToRadians(90)struct Temperature {
var celsius: Double
// Custom initializer
init(celsius: Double) {
self.celsius = celsius
}
init(fahrenheit: Double) {
self.celsius = (fahrenheit - 32) / 1.8
}
init(kelvin: Double) {
self.celsius = kelvin - 273.15
}
}
let temp1 = Temperature(celsius: 25)
let temp2 = Temperature(fahrenheit: 77)
let temp3 = Temperature(kelvin: 298.15)struct Point {
var x: Int
var y: Int
}
var point1 = Point(x: 10, y: 20)
var point2 = point1 // Creates a copy
point2.x = 30
print(point1.x) // 10 (unchanged)
print(point2.x) // 30 (modified)
// Structs are copied, not referenced// Struct (value type)
struct StructPoint {
var x: Int
var y: Int
}
// Class (reference type)
class ClassPoint {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
// Struct behavior
var sp1 = StructPoint(x: 10, y: 20)
var sp2 = sp1
sp2.x = 30
print(sp1.x) // 10 (independent copy)
// Class behavior
var cp1 = ClassPoint(x: 10, y: 20)
var cp2 = cp1
cp2.x = 30
print(cp1.x) // 30 (shared reference)Use structs when:
- You want value semantics (copying instead of referencing)
- The data is relatively small and simple
- You don't need inheritance
- You want thread safety by default
Examples: geometric shapes, ranges, dates, simple models
struct Player {
enum Rank: Int {
case rookie = 1
case intermediate
case advanced
case expert
}
var name: String
var rank: Rank
}
let player = Player(name: "Alice", rank: .expert)
print(player.rank.rawValue) // 4struct Circle {
var radius: Double
}
// Extend functionality without modifying original definition
extension Circle {
var diameter: Double {
return radius * 2
}
var circumference: Double {
return 2 * .pi * radius
}
func area() -> Double {
return .pi * radius * radius
}
}
let circle = Circle(radius: 5.0)
print(circle.diameter) // 10.0
print(circle.circumference) // 31.415...
print(circle.area()) // 78.539...- Use
letovervar- Prefer immutability when possible - Leverage type inference - Let Swift infer types when obvious
- Use optionals properly - Prefer optional binding over force unwrapping
- Choose structs by default - Use classes only when you need reference semantics
- Use guard for early exits - Makes code more readable
- Leverage computed properties - Better than separate getter methods
- Use meaningful names - Clear, descriptive names improve code readability
- Keep functions focused - Each function should do one thing well
- Use extensions - Organize code and add functionality to existing types
- Take advantage of Swift's type safety - Let the compiler catch errors
Now that you understand the basics of Swift, consider exploring:
- Classes and Inheritance - Object-oriented programming in Swift
- Protocols and Extensions - Protocol-oriented programming
- Error Handling - Using
throw,try, andcatch - Closures - Anonymous functions and capturing values
- Generics - Writing flexible, reusable code
- Concurrency - Modern async/await patterns
- SwiftUI - Building user interfaces
- Combine - Reactive programming framework
Happy coding with Swift! 🚀