Streaming is available in most browsers,
and in the Developer app.
-
Analyze heap memory
Dive into the basis for your app's dynamic memory: the heap! Explore how to use Instruments and Xcode to measure, analyze, and fix common heap issues. We'll also cover some techniques and best practices for diagnosing transient growth, persistent growth, and leaks in your app.
Chapters
- 0:00 - Introduction
- 1:05 - Heap memory overview
- 3:45 - Tools for inspecting heap memory issues
- 7:40 - Transient memory growth overview
- 10:34 - Managing autorelease pool growth in Swift
- 13:57 - Persistent memory growth overview
- 16:00 - How the Xcode memory graph debugger works
- 20:15 - Reachability and ensuring memory is deallocated appropriately
- 21:54 - Resolving leaks of Swift closure contexts
- 24:13 - Leaks FAQ
- 26:51 - Comparing performance of weak and unowned
- 30:44 - Reducing reference counting overhead
- 32:06 - Cost of measurement
- 32:30 - Wrap up
Resources
Related Videos
WWDC24
WWDC22
WWDC21
WWDC18
-
DownloadArray
-
-
10:01 - ThumbnailLoader.makeThumbnail(from:) implementation
func makeThumbnail(from photoURL: URL) -> PhotoThumbnail { validate(url: photoURL) var coreImage = CIImage(contentsOf: photoURL)! let sepiaTone = CIFilter.sepiaTone() sepiaTone.inputImage = coreImage sepiaTone.intensity = 0.4 coreImage = sepiaTone.outputImage! let squareSize = min(coreImage.extent.width, coreImage.extent.height) coreImage = coreImage.cropped(to: CGRect(x: 0, y: 0, width: squareSize, height: squareSize)) let targetSize = CGSize(width:64, height:64) let scalingFilter = CIFilter.lanczosScaleTransform() scalingFilter.inputImage = coreImage scalingFilter.scale = Float(targetSize.height / coreImage.extent.height) scalingFilter.aspectRatio = Float(Double(coreImage.extent.width) / Double(coreImage.extent.height)) coreImage = scalingFilter.outputImage! let imageData = context.generateImageData(of: coreImage) return PhotoThumbnail(size: targetSize, data: imageData, url: photoURL) }
-
10:23 - ThumbnailLoader.loadThumbnails(with:), with autorelease pool growth issues
func loadThumbnails(with renderer: ThumbnailRenderer) { for photoURL in urls { renderer.faultThumbnail(from: photoURL) } }
-
10:33 - Simple autorelease example
print("Now is \(Date.now)") // Produces autoreleased .description String
-
11:08 - Autorelease pool growth in loop
autoreleasepool { // ... for _ in 1...1000 { // Autoreleases into single pool, causing growth as loop runs print("Now is \(Date.now)") } // ... }
-
11:50 - Autorelease pool growth in loop, managed by nested pool
autoreleasepool { // ... for _ in 1...1000 { autoreleasepool { // Autoreleases into nested pool, preventing outer pool from bloating print("Now is \(Date.now)") } } // ... }
-
12:16 - ThumbnailLoader.loadThumbnails(with:), with nested autorelease pool growth issues fixed
func loadThumbnails(with renderer: ThumbnailRenderer) { for photoURL in urls { autoreleasepool { renderer.faultThumbnail(from: photoURL) } } }
-
17:27 - C++ class with virtual method
class Coconut { Swallow *swallow; virtual void virtualMethod() {} };
-
17:40 - C++ class without virtual method
class Coconut { Swallow *swallow; };
-
18:41 - ThumbnailRenderer.faultThumbnail(from:), caching thumbnails incorrectly
func faultThumbnail(from photoURL: URL) { // Cache the thumbnail based on url + creationDate let timestamp = UInt64(Date.now.timeIntervalSince1970) // Bad - caching with wrong timestamp let cacheKey = CacheKey(url: photoURL, timestamp: timestamp) let thumbnail = cacheProvider.thumbnail(for: cacheKey) { return makeThumbnail(from: photoURL) } images.append(thumbnail.image) }
-
19:28 - ThumbnailRenderer.faultThumbnail(from:), caching thumbnails correctly
func faultThumbnail(from photoURL: URL) { // Cache the thumbnail based on url + creationDate let timestamp = cacheKeyTimestamp(for: photoURL) // Fixed - caching with correct timestamp let cacheKey = CacheKey(url: photoURL, timestamp: timestamp) let thumbnail = cacheProvider.thumbnail(for: cacheKey) { return makeThumbnail(from: photoURL) } images.append(thumbnail.image) }
-
22:19 - Code creating reference cycle with closure context
let swallow = Swallow() swallow.completion = { print("\(swallow) finished carrying a coconut") }
-
23:11 - PhotosView image loading code, with leak
// ... let renderer = ThumbnailRenderer(style: .vibrant) let loader = ThumbnailLoader(bundle: .main, completionQueue: .main) loader.completionHandler = { self.thumbnails = renderer.images // implicit strong capture of renderer causes strong reference cycle } loader.beginLoading(with: renderer) // ...
-
23:40 - PhotosView image loading code, with leak fixed
// ... let renderer = ThumbnailRenderer(style: .vibrant) let loader = ThumbnailLoader(bundle: .main, completionQueue: .main) loader.completionHandler = { [weak renderer] in guard let renderer else { return } self.thumbnails = renderer.images } loader.beginLoading(with: renderer) // ...
-
24:24 - Intentional leak of manually-managed allocation
let oops = UnsafeMutablePointer<Int>.allocate(capacity: 16) // intentional mistake: missing `oops.deallocate()`
-
25:12 - Loop over intentional leak of manually-managed allocations
for _ in 0..<100 { let oops = UnsafeMutablePointer<Int>.allocate(capacity: 16) // intentional mistake: missing `oops.deallocate()` }
-
26:11 - Nonreturning function which can see leaks of allocations owned by local variables
func beginServer() { let singleton = Server(delegate: self) dispatchMain() // __attribute__((noreturn)) }
-
26:22 - Fix for reported leak in nonreturning function
static var singleton: Server? func beginServer() { Self.singleton = Server(delegate: self) dispatchMain() }
-
27:21 - Weak reference example
weak var holder: Swallow?
-
27:43 - Unowned reference example
unowned let holder: Swallow
-
29:07 - Implicit use of self by method causes reference cycle
class ByteProducer { let data: Data private var generator: ((Data) -> UInt8)? = nil init(data: Data) { self.data = data generator = defaultAction // Implicitly uses `self` } func defaultAction(_ data: Data) -> UInt8 { // ... } }
-
29:25 - Break reference cycle cause day implicit use of self by method, using weak
class ByteProducer { let data: Data private var generator: ((Data) -> UInt8)? = nil init(data: Data) { self.data = data generator = { [weak self] data in return self?.defaultAction(data) } } func defaultAction(_ data: Data) -> UInt8 { // ... } }
-
29:41 - Break reference cycle cause day implicit use of self by method, using unowned
class ByteProducer { let data: Data private var generator: ((Data) -> UInt8)? = nil init(data: Data) { self.data = data generator = { [unowned self] data in return self.defaultAction(data) } } func defaultAction(_ data: Data) -> UInt8 { // ... } }
-
31:14 - Struct with non-trivial init/copy/deinit
struct Nontrivial { var number: Int64 var simple: CGPoint? var complex: String // Copy-on-write, requires non-trivial struct init/copy/destroy }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.