Swift
Completed
-
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
236: Build Time Optimization Techniques
I've been working on this project for a few weeks now, and I'm starting to notice something annoying. Every time I change a single string in a view, I'm waiting nearly 20 seconds for the build to finish. For a project of this size, that's a disaster. It breaks my flow. So, I'm going to set aside some time today to figure out where the bottleneck is and fix it.
The "Wait, why is this line slow?" Moment
First, I want to see if I can isolate a specific piece of code that's taxing the compiler. I suspect it's my NetworkConfiguration object. I've written this heavily nested dictionary that handles API endpoints, headers, and timeout settings all in one go. It looks like this:
let config: [String: Any] = [
"api": [
"version": "v1",
"endpoints": [
"user": ["path": "/user", "method": "GET", "timeout": 30],
"auth": ["path": "/auth", "method": "POST", "timeout": 60],
"upload": ["path": "/upload", "method": "POST", "timeout": 300]
],
"headers": ["Content-Type": "application/json", "Accept": "application/json"]
],
"retryPolicy": ["maxAttempts": 3, "delay": 2.0]
]
It looks clean to me, but the Swift compiler has to work incredibly hard to infer the types for every single nested element in that [String: Any] mess. I'm going to try a quick experiment. I'll replace this loosely typed dictionary with a set of nested structs. If the build time for this file drops, I know the type checker was struggling.
I rewrite it into a Configuration struct with nested types. I hit Cmd+B. The build is noticeably snappier. Why? Because I've moved the burden from the type inference engine (which has to guess and check) to the type checker (which just verifies). Explicit types are almost always faster to compile than complex inferred ones.
The "Everything" File Problem
Next, I'm looking at my project navigator. I noticed I have a file called AppUtils.swift that's nearly 2,000 lines long. It contains extensions for String, Date, UIView, and URLSession. I figured keeping them together was "organized," but I suspect it's doing the opposite for the compiler.
Whenever I change one tiny helper method in a String extension, the compiler has to re-evaluate the entire AppUtils.swift file. Since so many other files import these utilities, it can trigger a cascade of recompilations.
I'm going to split this. I'll create String+Utils.swift, Date+Utils.swift, and so on. Now, when I tweak a date formatter, only the date utility file is dirty. I'm observing that my incremental build times—the builds that happen after I've already launched the app—have dropped from 12 seconds to about 3 seconds. It's a huge win for a bit of tedious file moving.
Hunting the Silent Killers
I still feel like there's some bloat. I want to know exactly which functions are taking the longest to compile. Swift has some hidden flags that can tell us this, though they aren't exposed in the standard Xcode UI. I'm going to dive into the Build Settings and add a specific flag to Other Swift Flags:
-Xfrontend -warn-long-function-bodies=100
This tells the compiler: "If any function takes longer than 100ms to type-check, give me a warning." I trigger a build, and immediately, a warning pops up on a complex closure in my data mapping layer. The line is a massive chain of .map and .flatMap calls. The compiler is basically looping through a million permutations to figure out the return type of that chain.
The fix here is simple: I break the chain. Instead of one giant expression, I'll use intermediate variables and explicitly declare their types. It's slightly more verbose, but the compiler no longer has to "solve" the equation; I've given it the answers.
The Dependency Tax
Finally, I'm looking at my dependencies. I'm using a few large libraries, and I've noticed that every time I clean the build folder, it takes forever to get back to a runnable state. I realize I'm importing entire modules when I only need one or two classes.
While Swift doesn't have "selective imports" in the same way some languages do, I can optimize how my modules are structured. I've started moving my core logic into a separate local Swift Package. By isolating the stable business logic into its own module, Xcode can cache that module as a binary. Now, when I'm iterating on the UI, the core logic doesn't even need to be looked at by the compiler. It's already "baked."
📋 Practical Task
Exercise: Optimizing the "Heavy" Analytics Engine
You have been handed a legacy AnalyticsManager.swift file that is causing significant build delays. Your goal is to apply the optimization techniques discussed in this lesson to bring the build time down.
The Scenario:
- The Type Inference Trap: There is a
let eventDefaults: [String: Any]dictionary with 4 levels of nesting. Task: Refactor this into a nestedstructhierarchy to eliminate theAnytype and help the type checker. - The God File: The
AnalyticsManager.swiftfile also contains 500 lines ofStringandIntextensions used for formatting analytics keys. Task: Move these extensions into separate files (e.g.,String+Analytics.swift) to improve incremental build performance. - The Complex Chain: There is a function that processes a raw JSON array using a single, massive chain of
.compactMap { ... }.filter { ... }.sorted { ... }.map { ... }. Task: Break this chain into three distinct steps with explicit type annotations for the intermediate arrays.
Success Criteria: Your refactored code must compile without warnings, remove all instances of [String: Any] in the configuration, and separate the utility extensions from the main manager class.
There are no comments for now.