-
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
168: Practice Exercise: Building a Type-Safe Analytics Event Logger
I've spent way too many hours in my career staring at a Mixpanel or Firebase dashboard and wondering why our "Sign Up" conversion rate dropped by 40% overnight, only to realize some developer had typed "user_signed_up" in one place and "UserSignedUp" in another. When you rely on strings for analytics, you're essentially praying that every dev on your team has a perfect memory and a steady hand.
The "Stringly-Typed" Disaster
If we were doing this the quick-and-dirty way, we'd probably just write a function like this:
func logEvent(_ name: String, parameters: [String: Any]?) {
print("Logging event: \(name) with params: \(parameters ?? [:])")
}
// Usage
logEvent("button_clicked", parameters: ["screen": "home"])
logEvent("buton_cliked", parameters: ["screen": "settings"]) // Typo! The compiler doesn't care.
This is what I call "stringly-typed" code. It's flexible, sure, but it's a nightmare for maintenance. I can't search for every single place an event is fired easily, and there's no way to enforce that a "Purchase" event must include a price and a currency.
Trying to Tighten the Screws with Enums
My first instinct to fix this is usually to wrap everything in an enum. Enums are great because they force you to pick from a predefined list. Let's try that:
enum AnalyticsEvent {
case appLaunched
case buttonClicked(screen: String)
case itemPurchased(itemId: String, price: Double)
}
func log(_ event: AnalyticsEvent) {
switch event {
case .appLaunched:
print("Event: app_launched")
case .buttonClicked(let screen):
print("Event: button_clicked, screen: \(screen)")
case .itemPurchased(let id, let price):
print("Event: item_purchased, id: \(id), price: \(price)")
}
}
This is a massive improvement. Now, if I typo the event name, the code won't even compile. But here's where I hit a wall: scalability. In a real production app, you might have 200 different events. If I put them all in one enum, that switch statement becomes a thousand-line monster. It violates the Single Responsibility Principle; the logger now has to know the internal details of every single event in the entire app.
Designing a Protocol for Event Data
I want the type-safety of the enum, but I want the events to be decoupled from the logger itself. The way to do this in Swift is to define what it means to be an analytics event using a protocol. I'll define a protocol that requires a name and a way to export parameters.
protocol AnalyticsEvent {
var name: String { get }
var parameters: [String: Any] { get }
}
// Now, each event is its own little object.
struct PurchaseEvent: AnalyticsEvent {
let itemId: String
let price: Double
var name: String { "item_purchased" }
var parameters: [String: Any] {
["item_id": itemId, "price": price]
}
}
struct ScreenViewEvent: AnalyticsEvent {
let screenName: String
var name: String { "screen_view" }
var parameters: [String: Any] {
["screen": screenName]
}
}
Now, look at how clean the logger becomes. It doesn't need to know about PurchaseEvent or ScreenViewEvent specifically; it just needs something that conforms to AnalyticsEvent.
class AnalyticsLogger {
static let shared = AnalyticsLogger()
private init() {}
func log(_ event: AnalyticsEvent) {
print("Sending to server -> \(event.name) | Params: \(event.parameters)")
}
}
// Usage:
AnalyticsLogger.shared.log(PurchaseEvent(itemId: "swift_book_01", price: 29.99))
AnalyticsLogger.shared.log(ScreenViewEvent(screenName: "Settings"))
This is the "Pro" way to do it. If we need to add a new event, we just create a new struct. We don't touch the AnalyticsLogger class, and we don't touch any existing events. The compiler ensures that we provide the required data for each event, and the logger stays blissfully ignorant of the specifics.
📋 Practical Task
Exercise: Implementing a Type-Safe User-Action Tracker
You are tasked with extending the analytics system to track user engagement. Your goal is to build a type-safe system that prevents developers from logging "User Action" events without the necessary context.
Requirements:
- Create an
AnalyticsEventprotocol with aname: Stringandparameters: [String: Any]. - Implement a
UserActionEventstruct that conforms to the protocol. It must require ausername: Stringand anactionType: String(e.g., "like", "share", "follow") in its initializer. - Implement a
SystemErrorEventstruct that conforms to the protocol. It must require anerrorCode: Intand amessage: String. - Create an
EventDispatcherclass with a methoddispatch(_ event: AnalyticsEvent)that prints the event name and its parameters to the console. - In your main execution code, instantiate the
EventDispatcherand log oneUserActionEventand oneSystemErrorEvent.
There are no comments for now.