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
149: Memory Graph Debugging
I’ve seen a lot of developers hit a wall when they notice their deinit methods aren't firing. Their immediate instinct is usually to go back to the source code and search for every instance of a strong reference, hoping to spot the cycle by eye. They assume that if they just look hard enough at the strong and weak keywords in their files, the leak will reveal itself.
Searching for "strong" in the code is enough to find leaks
The problem with this approach is that in Swift, everything is strong by default. You aren't writing the word strong next to every property; you're just writing var coordinator: Coordinator. When you have a complex app with objects passing references back and forth, the "cycle" isn't a single line of code—it's a path. It might go from ViewController to ViewModel, then to a NetworkService, then to a closure, and finally back to the ViewController.
Trying to trace that path mentally is a nightmare. I've spent hours doing this, only to realize the leak was caused by a closure I forgot to use [weak self] in, but the closure was wrapped inside a service I didn't even think was part of the loop. You can't "grep" your way out of a runtime memory leak.
Visualizing the runtime graph to find the hidden culprits
This is where the Memory Graph Debugger becomes your best friend. Instead of guessing, it lets you pause the app and see exactly who is holding onto whom at that precise moment in time. When you click the "Debug Memory Graph" button (those three connected circles in the debug bar), Xcode transforms your heap into a visual map.
Here is how I actually use it to kill leaks. Let's say we have a ChatSession that manages a User, and the User has a callback closure for when their status changes. If that closure captures the ChatSession strongly, neither will ever be freed.
class ChatSession {
let user = User()
deinit { print("Session freed") }
}
class User {
var onStatusChange: (() -> Void)?
}
// The leak happens here
let session = ChatSession()
session.user.onStatusChange = {
print("Session \(session) updated!")
}
// Even if we set session = nil, it stays in memory.
In the Memory Graph Debugger, you won't just see a list of objects. You'll see the ChatSession instance with a bold arrow pointing to the User, and a bold arrow pointing from the User's closure back to the ChatSession. Xcode often flags these automatically with a purple exclamation mark, explicitly telling you "Cycle Found."
The real power is in the Navigator on the left. You can select the leaked ChatSession and see every single reference pointing to it. If you see a reference coming from a malloc block or a Swift closure context, you know exactly where to look in your code: find the closures that interact with that object. I always look for the "thick" lines first—those represent strong references. If you see a loop of thick lines, you've found your leak.
📋 Practical Task
Exercise: Hunting the Playlist Retention Cycle
You are working on a music app. The MusicPlayer owns a Playlist, and the Playlist has an onTrackEnded closure that updates the MusicPlayer's UI. However, the MusicPlayer is never being deallocated when the user closes the player view.
Your Task:
- Create the following classes in a Swift Playground or Xcode project:
MusicPlayer: Has a propertyvar currentPlaylist: Playlist?and adeinitthat prints "Player Deallocated".Playlist: Has a propertyvar onTrackEnded: (() -> Void)?.
- In your setup code, instantiate a
MusicPlayer, create aPlaylist, and assign it to the player. - Assign a closure to
playlist.onTrackEndedthat referencesself(the player) to update a property. - Set the player reference to
niland observe that "Player Deallocated" is not printed. - Run the Memory Graph Debugger. Locate the
MusicPlayerinstance and trace the reference path to find the closure causing the cycle. - Fix the leak using a capture list and verify that the
deinitmessage now prints.
There are no comments for now.