All session list link: Here
Session Link: Here
Demo starts @ 25:30 in the video.
Download session slides: Here
This notes is written by Sheldon after watching WWDC18 session 416. You can find me with #iOSBySheldon in Github, Youtube, Facebook, etc.
Memory are stored in into pages
- Typically 16KB per page
- Page has three types:
- Clean: Allocated but not used or allocated and used by
readonly
objects - Dirty: Allocated and used by dynamic objects
- Compressed: Will be explanded in the following section
- Clean: Allocated but not used or allocated and used by
- App memory = number of pages * memory of each page (Dirty + Compressed, Clean memory is not considered being used)
int *array = malloc(20000 * sizeof(int))
array[0] = 32
array[19999] = 64
// page 0 and final page is called dirty others are clean
Note: readonly objects that stored into pages are always clean (except for the final page is not fully used and later used as dirty memory)
iOS doesn't have memory swap like macOS, but iOS have the mechanism to compress the memory of un-accessed (after a while) pages and will decompress pages upon access
Memory warning:
- App is not always the cause
- Memory Compressor makes it more complicated.
Example:
override func didReceiveMemoryWarning() {
cache.removeAllObjects()
// compressor may have to decompress first to use more memory
super.didReceiveMemoryWarning()
}
Note:
- Always remember compressor
- Prefer NSCache over Dictionary (purgable and thread safe)
Memory footprint means the memory usage/record that we can track. Memory footprint limit:
- Vary by device
- Apps have a fairly high limit
- Extensions have a much lower limit
- If exceed limit, it will throw a
EXC_RESOURCE_EXCEPTION
Note: Xcode 10 will catch EXC_RESOURCE_EXCEPTION
(turn on all exception breakpoints)
Tools for memory footprint:
- Xcode memory gage
- Instrument - Allocations
- Instrument - Leaks
- Instrument - VM Tracker (has info about dirty, compressed(swapped in macOS) pages)
- Instrument - Virtual memory trace (virtual memory performance, pages caches, etc)
- Xcode Memory debugger (upgraded a lot in Xcode 10), it can also export memory graph file
*.memgraph
.
Note: Tools 1-5 are more straight forward with friendly UI, but using terminal and *.memgraph
file, we can do some more.
- Xcode open memory graph debugging view
- Xcode menu -> file -> export memory graph file
*.memgraph
- Open terminal and prepare to use your memory graph file
The commands shown in the demo are:
$ vmmap
(as similar for instrument - virtual memory trace)$ leaks
(as similar for instrument - leaks)$ heap
(to check memory that are allocated dynamically)$ malloc_history
(to track allocation history)
Examples:
$ vmmap "filename.memgraph"
$ vmmap --summary "filename.memgraph"
Info like: Dirty, Swapped(compressed) labelling, in regions for heaps, etc)
$ vmmap --pages "filename" | grep '.dylib' | awk '{ sum += &6 } END { print "Total Dirty Pages: " sum }'
Output --- total dirty pages: 152.27
$ leaks "filename.memgraph"
Info about retain cycle, back track
$ heap "filename.memgraph"
$ heap "filename.memgraph" -sortBySize (default sort by count)
$ heap "filename.memgraph" -address all | <classes-pattern>
Info about Class names, numbers of them, memory usage in bytes for each class
Note: Address is working well with malloc_history
stack-trace logging, to turn it on, Scheme -> Run
tab -> Diagnostics
tab -> Logging
-> check Malloc Stack
$ malloc_history -callTrace "filename.memgraph" [address]
This can show the backtrace.
Dependent on your debugging purposes you can start with different tools, but generally, there are all pretty helpful.
- Object Creation:
malloc_history
- Reference:
leaks
- Size:
vmmap
,heap
The biggest concern of object related to memory management - IMAGE
Memory use if related the DIMENSION of the image, NOT file size.
The flow of image on iOS
Load (590KB) -> Decode (10MB) -> Redender
A pick of 590KB image (2048px by 1536px) takes 10MB(2048 * 1536 * 4) memory in default format.
Image formatting:
- SRGB format - default format with 4 dimension
- Wide format - takes 8 dimensions for larger and better devices
- Luminance and alpha format - 2 dimensions (grey and alpha)
- Alpha 8 format - just 1 dimension - masks, texts
Let system choose the better dimension for you and stop using UIGraphicsBeginImageContextWithOptions
, old example:
// Circle via UIGraphicsImageContext
let bounds = CGRect(x: 0, y: 0, width:300, height: 100)
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
// Drawing Code
UIColor.black.setFill()
let path = UIBezierPath(roundedRect: bounds,
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: CGSize(width: 20, height: 20))
path.addClip()
UIRectFill(bounds)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Now use UIGraphicsImageRenderer
- auto pick best format to render (iOS 10 and later)
// Circle via UIGraphicsImageRenderer
let bounds = CGRect(x: 0, y: 0, width:300, height: 100)
let renderer = UIGraphicsImageRenderer(size: bounds.size)
let image = renderer.image { context in
// Drawing Code
UIColor.black.setFill()
let path = UIBezierPath(roundedRect: bounds,
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: CGSize(width: 20, height: 20))
path.addClip()
UIRectFill(bounds)
}
// Make circle render blue, but stay at 1 byte-per-pixel image
let imageView = UIImageView(image: image)
imageView.tintColor = .blueImages
Note:
UIImage
is expensive for sizing and resizing (will decompress memory first, internal coordinate space transforms are expensive)- Use
ImageIO
, it will work with out dirty memory (also API is faster)
Old example with UIKit
:
import UIKit
// Getting image size
let filePath = “/path/to/image.jpg”
let image = UIImage(contentsOfFile: filePath)
let imageSize = image.size
// Resizing image
let scale = 0.2
let size = CGSize(image.size.width * scale, image.size.height * scale)
let renderer = UIGraphicsImageRenderer(size: size)
let resizedImage = renderer.image { context in
image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
}
Better solution with ImageIO
:
import ImageIO
let filePath = “/path/to/image.jpg”
let url = NSURL(fileURLWithPath: path)
let imageSource = CGImageSourceCreateWithURL(url, nil)
let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: 100,
kCGImageSourceCreateThumbnailFromImageAlways: true
]
let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options)
Off screen resources optimization:
Think about unload images when background the app or unload them from memory when tab bar controller disappear and load them when it's needed.
This could help system release unnecessary memory and have a better chance of not letting the system automatically release your app when user put it into background.