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
79: NSCache for In-Memory Caching
I was working on a small photo gallery app recently, and I noticed something frustrating. Every time the user scrolled back up to a photo they'd already seen, the app would flicker for a millisecond while it re-loaded the image from the disk or the network. It felt janky. My first instinct was to just throw everything into a dictionary and call it a day.
The dictionary trap
I started with a simple dictionary. It seemed obvious: use the image URL as the key and the UIImage as the value. Here is how that looked in my head:
var imageCache: [URL: UIImage] = [:]
func getImage(url: URL) -> UIImage? {
if let cachedImage = imageCache[url] {
return cachedImage
}
// ... fetch image from network ...
// imageCache[url] = downloadedImage
return nil
}
For the first five minutes, it worked perfectly. The flickering stopped. But then I started testing with a larger dataset—hundreds of high-resolution photos. I opened the Memory Graph in Xcode and saw a vertical line climbing straight up. Because a Dictionary is a strong reference, it holds onto every single image I've ever downloaded until I manually remove them. On a device with limited RAM, this is a one-way ticket to an EXC_RESOURCE crash.
Switching to NSCache
I need something that acts like a dictionary but is "smarter" about the device's health. That's where NSCache comes in. It's basically a dictionary that knows how to purge itself when the system is running low on memory. I tried to swap it in, but I immediately hit a wall with Swift's type system.
// This doesn't compile!
let cache = NSCache<URL, UIImage>()
The compiler complained that URL doesn't conform to AnyObject. NSCache is an Objective-C legacy class, and it requires both the key and the value to be classes (objects), not structs. Since URL in Swift is a struct, it won't work. I had to use NSURL instead. It's a bit clunky, but it's the price of admission here.
let imageCache = NSCache<NSURL, UIImage>()
func getImage(url: URL) -> UIImage? {
let nsUrl = url as NSURL
if let cachedImage = imageCache.object(forKey: nsUrl) {
return cachedImage
}
// ... fetch image ...
// imageCache.setObject(downloadedImage, forKey: nsUrl)
return nil
}
Tuning the eviction policy
Now the app won't crash under memory pressure, but I noticed that NSCache is a bit of a black box. It decides when to evict items based on its own internal logic. Sometimes it clears too much, and I'm back to that flickering problem. I wanted more control.
I found two properties that are actually quite useful: countLimit and totalCostLimit. The countLimit is the easy one—it just caps the number of objects. If I set it to 100, the 101st image will likely push out the oldest one.
let imageCache = NSCache<NSURL, UIImage>()
imageCache.countLimit = 100
But images aren't all the same size. A tiny thumbnail takes up way less room than a 4K wallpaper. That's where totalCostLimit comes in. It allows me to tell the cache, "Don't use more than 50MB of RAM." To make this work, I have to provide a "cost" whenever I save an object.
let imageCache = NSCache<NSURL, UIImage>()
imageCache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
// When saving, I calculate the approximate byte size of the image
let bytes = downloadedImage.pngData()?.count ?? 0
imageCache.setObject(downloadedImage, forKey: nsUrl, cost: bytes)
By using totalCostLimit, I'm no longer guessing how many images will fit. I'm managing the actual memory footprint. It's a much more professional way to handle assets that are "nice to have" in memory but aren't critical to the app's basic survival.
📋 Practical Task
Implementing a Profile Picture Memory Cache
Build a ProfileImageManager class that handles the caching of user profile pictures. Your implementation must meet the following requirements:
- Use
NSCacheto store the images, usingNSURLas the key. - Set a
countLimitof 50 images to prevent the cache from growing indefinitely. - Implement a method
fetchImage(for url: URL) -> UIImage?that first checks the cache before returning nil (simulating a cache miss). - Implement a method
saveImage(_ image: UIImage, for url: URL)that calculates the cost of the image (usingimage.pngData()?.count) and stores it in the cache.
There are no comments for now.