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
151: Diagnosing Retain Cycles with Memory Graph
Imagine two people, Alex and Sam, standing in a room. They’ve decided to hold onto each other's wrists in a firm, locked grip. Now, imagine the rule of the room is that you can only leave once you aren't being held by anyone. Alex won't let go until Sam does, and Sam won't let go until Alex does. They're stuck. Even if the rest of the party has gone home and the lights are turned off, Alex and Sam are still there, occupying space, simply because they refuse to be the first one to let go.
In Swift, this is exactly what happens during a retain cycle. Your objects are Alex and Sam, and the "grip" is a strong reference. ARC (Automatic Reference Counting) is the room manager; it sees that both objects still have a reference count of at least one, so it assumes they're still needed. It can't "clean the room" because neither object is truly free.
When Code Becomes a Deadlock
You've likely encountered this if you've used delegates or closures. Let's look at a common scenario: a ChatSession that needs to notify a ChatViewController when a message arrives. If the controller owns the session, and the session holds a strong reference back to the controller as its delegate, you've just built a deadlock.
class ChatSession {
var delegate: ChatViewController? // Strong reference by default!
func receiveMessage() {
delegate?.didReceiveMessage("Hello!")
}
}
class ChatViewController {
var session: ChatSession?
func setupChat() {
session = ChatSession()
session?.delegate = self
}
}
At first glance, this looks fine. But the moment you dismiss that ChatViewController, it doesn't actually leave memory. The ChatSession is still holding onto it, and the controller is still holding onto the session. They're just standing there in the dark, wasting RAM.
Visualizing the Mess with the Memory Graph
You could hunt these down using deinit prints, but that's like trying to find a leak in a pipe by listening for drips. It's tedious. Instead, I want you to use the Memory Graph Debugger. It's a lifesaver when your app's memory usage starts climbing for no apparent reason.
While your app is running in Xcode, look at the debug bar (just above the console). There's a small icon that looks like three connected circles. Click that. Xcode will pause your app and generate a visual map of every object currently living in memory and who is holding onto whom.
Here is what I look for when I'm diagnosing a leak:
- The Bold Lines: In the graph, bold lines usually represent strong references. If you see a path of bold lines that forms a circle (A → B → A), you've found your culprit.
- The Leaked Icon: Xcode is often smart enough to flag suspected leaks with a purple exclamation mark. If you see one of those, start your investigation there.
- The Navigator: On the left sidebar, you can filter by class name. I usually search for my ViewControllers. If I see five instances of
ChatViewControllerin the list, but I've only ever opened one chat window, I know I have a retain cycle.
Breaking the Grip
Once the Memory Graph shows you the cycle, the fix is almost always the same: someone has to be willing to let go. In our analogy, either Alex or Sam needs to loosen their grip. In Swift, we do this by marking the reference as weak.
By changing var delegate: ChatViewController? to weak var delegate: ChatViewController?, you're telling ARC: "I want to know about this object, but I don't want to be the reason it stays alive." The moment the ChatViewController is dismissed, the strong reference from the owner is gone, the count hits zero, and the whole cycle collapses and clears out of memory. It's a clean exit.
📋 Practical Task
Debugging the UserProfileManager Leak
You have been handed a small project where the UserProfileManager is causing a massive memory leak. The UserProfileViewController creates a manager, and the manager stores a closure to handle profile updates. However, the closure captures self strongly, creating a retain cycle.
Your Goal:
- Run the project and navigate to the Profile screen, then go back to the Home screen three times.
- Open the Memory Graph Debugger and verify that three instances of
UserProfileViewControllerare still residing in memory. - Identify the strong reference cycle between the
UserProfileViewControllerand theUserProfileManager's update closure. - Fix the leak using a capture list (
[weak self]) and verify in the Memory Graph that the controllers are now being properly deallocated when you leave the screen.
There are no comments for now.