-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
58: Practice Exercise: Building a Command-Line Contact Manager
How should I structure the data so it's easy to manage?
When I'm building something like this, I always start by separating the "thing" (the data) from the "logic" (how we handle that data). You don't want your main loop cluttered with the details of how a contact is stored. I'd recommend a simple struct for the Contact and a class to act as your manager.
struct Contact: Codable {
let name: String
let phone: String
let email: String
}
class ContactManager {
var contacts: [Contact] = []
func add(_ contact: Contact) {
contacts.append(contact)
}
func remove(at index: Int) {
guard index >= 0 && index < contacts.count else { return }
contacts.remove(at: index)
}
}
Using a struct for the contact makes it a value type, which is exactly what you want for a data record. The ContactManager class then keeps the state of your list alive throughout the session.
What's the cleanest way to handle the user input loop?
This is where a lot of people get tangled up. You need a loop that stays open until the user explicitly tells it to quit. I usually go with a while true loop combined with a switch statement. It's readable and makes it trivial to add new commands later.
One thing to watch out for: readLine() returns an optional string, so you'll want to handle that. I like using a guard or a simple optional binding to keep things tidy.
var manager = ContactManager()
var shouldExit = false
while !shouldExit {
print("\n1. Add Contact\n2. List Contacts\n3. Exit")
print("Choose an option: ", terminator: "")
guard let choice = readLine() else { continue }
switch choice {
case "1":
print("Enter name: ", terminator: "")
let name = readLine() ?? "Unknown"
print("Enter phone: ", terminator: "")
let phone = readLine() ?? "Unknown"
manager.add(Contact(name: name, phone: phone, email: ""))
case "2":
for (index, contact) in manager.contacts.enumerated() {
print("\(index): \(contact.name) - \(contact.phone)")
}
case "3":
shouldExit = true
default:
print("Invalid option, try again.")
}
}
How do I stop the contacts from disappearing every time I restart the app?
Since we already made the Contact struct conform to Codable, we're halfway there. To make this a "real" tool, you need to write that data to the disk. In a command-line tool, the easiest path is saving a JSON file to the local directory.
I've found that creating a helper method within the manager to handle saving and loading keeps the main loop clean. You'll use JSONEncoder to turn your array into data and Data.write(to:) to put it on the hard drive.
extension ContactManager {
func saveToDisk() {
let url = URL(fileURLWithPath: "contacts.json")
do {
let data = try JSONEncoder().encode(contacts)
try data.write(to: url)
} catch {
print("Save failed: \(error)")
}
}
func loadFromDisk() {
let url = URL(fileURLWithPath: "contacts.json")
guard let data = try? Data(contentsOf: url) else { return }
if let decoded = try? JSONDecoder().decode([Contact].self, from: data) {
self.contacts = decoded
}
}
}
Just remember to call loadFromDisk() when the app starts and saveToDisk() whenever a contact is added or removed. It's a simple pattern, but it's the foundation of almost every data-driven app you'll ever write.
📋 Practical Task
Exercise: Build the Persistent Contact CLI Tool
Now it's your turn to put these pieces together. Your goal is to build a fully functional Command-Line Contact Manager that survives a program restart.
- Requirement 1: Implement a
Contactstruct and aContactManagerclass. - Requirement 2: Build a menu system that allows the user to:
- Add a new contact (Name, Phone, and Email).
- List all existing contacts with an index number.
- Delete a contact by providing their index number.
- Exit the program.
- Requirement 3: Integrate
Codableso that the contact list is automatically loaded fromcontacts.jsonupon startup and saved whenever the list is modified. - Requirement 4: Add basic validation to ensure the user doesn't crash the program by entering a non-numeric value when deleting a contact.
There are no comments for now.