Swift Foundations
The environment, variables, types, closures, protocols, and generics.
Part 1: Swift Foundations
The Bedrock of Modern Apple Development
Why Does This Exist?
Swift was created to replace Objective-C with a safer, faster, and more modern language. It exists to give developers the power of a low-level language with the syntax and safety of a high-level language.
The Problem Before the Solution
Before Swift, Apple developers used Objective-C, which had complex syntax, manual memory management (pre-ARC), and lacked modern features like type inference, optionals, and closures, leading to frequent runtime crashes due to null pointers.
Mental Model
Think of Swift as a heavily typed, safety-first language that catches your mistakes at compile-time rather than runtime. It treats optionals like boxes that may or may not contain a value, and requires you to explicitly unwrap them.
Internal Working
Swift compiles to LLVM bitcode and then to machine code. It uses Automatic Reference Counting (ARC) to manage memory, ensuring objects are deallocated when no longer needed without a garbage collector pausing execution.
Syntax
// Variables and Types
var greeting: String = "Hello"
let pi = 3.14159 // Type inference
// Optionals
var name: String? = nil
// Control Flow
if let unwrappedName = name {
print(unwrappedName)
}
Tiny Example
struct User {
let id: UUID
var username: String
}
let user = User(id: UUID(), username: "swift_dev")
Walkthrough
1. Swift environment & Xcode: You start by opening Xcode and creating a new Swift package or iOS project. Xcode provides the compiler, debugger, and interface builder.
2. Project structure: A typical project has an App entry point, Views/Controllers, and Models.
3. Variables, constants, types, type inference: Use var for mutable, let for immutable. Swift infers types automatically.
4. Functions & Closures: Functions are first-class citizens. Closures are self-contained blocks of functionality.
5. Collections: Arrays, Dictionaries, and Sets.
6. Structs, Classes, Enums: Value types (Structs/Enums) vs Reference types (Classes).
7. Protocols & Extensions: Define blueprints (Protocols) and add functionality to existing types (Extensions).
8. Error Handling & Optionals: Use try/catch and ? / ! for safety.
9. Codable: Easily convert between JSON and Swift types.
10. Access control, modules, packages: Control visibility using public, private, internal. Organize code with SPM.
Break It
var name: String? = nil
print(name!) // Fatal error: Unexpectedly found nil while unwrapping an Optional value
Debug It
Use optional binding (if let or guard let) instead of force-unwrapping (!) to safely handle optionals.
Mini Project (20-30 min)
Create a simple CLI contact manager using Structs to represent contacts, an Array for the collection, and simple functions to add and list contacts. You should implement a protocol for printable contacts and add basic error handling for empty names.
View Solution
Here is a basic implementation:
struct Contact {
var name: String
var phone: String
}
class ContactManager {
private var contacts: [Contact] = []
func addContact(name: String, phone: String) {
let newContact = Contact(name: name, phone: phone)
contacts.append(newContact)
}
func listContacts() {
for contact in contacts {
print("\(contact.name): \(contact.phone)")
}
}
}
Bigger Project (1-2 hours)
Build a Task Management library. Use a class to represent the TaskManager which holds an array of Task structs. Implement a custom error enum conforming to Error to handle cases like "Task Not Found" or "Invalid Task Name". Use closures to filter tasks (e.g., finding all completed tasks). Expose methods to toggle task completion using optional binding where necessary.
View Solution
Here is an architectural starting point:
enum TaskError: Error {
case taskNotFound
case invalidName
}
struct Task {
let id: UUID
var title: String
var isCompleted: Bool
}
class TaskManager {
var tasks: [Task] = []
func addTask(title: String) throws {
guard !title.isEmpty else { throw TaskError.invalidName }
tasks.append(Task(id: UUID(), title: title, isCompleted: false))
}
func completeTask(id: UUID) throws {
guard let index = tasks.firstIndex(where: { $0.id == id }) else {
throw TaskError.taskNotFound
}
tasks[index].isCompleted = true
}
}
Interview Questions
Easy: What is the difference between var and let?
var is mutable (can be changed), let is immutable (constant). Using let wherever possible allows the compiler to optimize your code and prevents unintended side effects.
Medium: What is the difference between a Struct and a Class?
Structs are value types (copied on assignment and passed by value), Classes are reference types (shared on assignment and passed by reference). Structs do not support inheritance, whereas classes do. Structs get a free memberwise initializer.
Hard: How does Automatic Reference Counting (ARC) work?
ARC tracks the number of strong references to a class instance and deallocates it when the count reaches zero. Strong reference cycles require weak or unowned references to break the cycle and prevent memory leaks. ARC only applies to reference types (classes).
Revision Sheet
Remember: var/let, Value vs Reference types, Optionals (?), force-unwrapping is evil (!), guard for early return, Protocols for abstractions, Extensions for additions.
Connections
Swift foundations directly connect to SwiftUI (which heavily uses structs and protocols) and Combine (which relies on generics and closures).