-
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
57: Common Swift Interview Questions on ARC and Memory
Imagine you're running a small community library. Every time someone borrows a book, you put a sticky note on the back of it. If three people want the same reference book, you keep three notes there. As long as there is at least one sticky note on that book, it stays on the active shelf. The moment the last person returns the book and you peel off that final note, you clear the book from the shelf to make room for something else. That's exactly how ARC (Automatic Reference Counting) works in Swift. The "sticky notes" are strong references, and the "librarian" is the Swift runtime keeping track of the count.
The problem starts when you have two books that somehow refer to each other. Imagine Book A has a note saying, "See Book B for more info," and Book B has a note saying, "See Book A for context." If you try to clear the shelf, you can't remove Book A because Book B is still holding a note for it. But you can't remove Book B because Book A is holding a note for it. They're stuck in a deadlock. In software engineering, we call this a retain cycle, and it's the primary cause of memory leaks in Swift apps.
Breaking the Cycle: Weak vs. Unowned
When an interviewer asks you how to fix a retain cycle, they aren't just looking for the word "weak." They want to know if you understand when to use weak versus unowned. I've seen a lot of developers use weak for everything just to be safe, but that's not always the right architectural choice.
class Person {
let name: String
var apartment: Apartment?
init(name: String) { self.name = name }
deinit { print("\(name) is being deallocated") }
}
class Apartment {
let unit: String
var tenant: Person? // If this is strong, we have a cycle!
init(unit: String) { self.unit = unit }
deinit { print("Apartment \(unit) is being deallocated") }
}
In the example above, if both apartment and tenant are strong references, neither object will ever be freed from memory. To break this, we make one of them weak. A weak reference doesn't increase the reference count. Crucially, because a weak reference doesn't keep the object alive, it must be an optional. If the Person is deleted, ARC automatically sets the tenant variable in the Apartment to nil.
Now, what about unowned? Use unowned when you know the other object has the same lifetime or a longer lifetime than the current object. It's like a weak reference, but it's non-optional. The catch? If you try to access an unowned reference after the object it points to has been deallocated, your app will crash. It's a "trust me, this will still be here" contract. Use it sparingly.
The 'self' Trap in Closures
This is the most common "gotcha" in Swift interviews. Closures are reference types. If you assign a closure to a property of a class, and that closure captures self, you've just created a retain cycle. The class owns the closure, and the closure owns the class.
I always tell my juniors: whenever you write self. inside a closure that is stored as a property, your "memory leak alarm" should go off. You fix this using a capture list.
class DataFetcher {
var onCompletion: ((String) -> Void)?
func startFetch() {
onCompletion = { [weak self] data in
// self is now an optional inside here
print("Received \(data) for \(self?.name ?? "Unknown")")
}
}
}
By adding [weak self], you're telling the closure: "You can use self if it's still around, but don't hold onto it so tightly that it can't be deleted."
Talking Through the Memory Audit
If an interviewer asks you, "How do you find a memory leak in a real project?" don't just say "I use the debugger." Be specific. Tell them about the Memory Graph Debugger in Xcode. Mention how you look for those bold lines indicating strong references that form a loop. Mention Instruments (Leaks instrument) for tracking down allocations that aren't being freed.
The goal isn't to memorize definitions; it's to show you understand the ownership flow. When in doubt, ask yourself: "Who owns whom?" If the answer is "They own each other," you've found your leak.
📋 Practical Task
Fixing the Memory Leak in the Social Media Profile Manager
You are reviewing a piece of code for a social media app. The UserProfile object handles user data, and the ProfileSettings object handles the user's preferences. Currently, they have a strong bidirectional relationship, which is causing a memory leak—the objects are never deallocated when the user logs out.
Your Task: Modify the provided code to break the retain cycle using the most appropriate reference type (weak or unowned). Ensure that when the UserProfile is set to nil, both the profile and its settings are removed from memory.
class UserProfile {
let username: String
var settings: ProfileSettings?
init(username: String) {
self.username = username
}
deinit {
print("UserProfile \(username) deallocated")
}
}
class ProfileSettings {
var theme: String = "Dark"
var owner: UserProfile? // Problem area!
init(owner: UserProfile) {
self.owner = owner
}
deinit {
print("ProfileSettings deallocated")
}
}
// Test Code
var user: UserProfile? = UserProfile(username: "swift_dev")
var settings: ProfileSettings? = ProfileSettings(owner: user!)
user?.settings = settings
print("Setting user to nil...")
user = nil
// At this point, you should see both deinit messages in the console.
// If you don't, the retain cycle is still there.There are no comments for now.