Arimunandar ios performance
Skill JordanCoin/ios-skills-collection/skills/arimunandar--ios-performance
200+ iOS/Swift/Xcode agent skills from 28 authors with phase-aware routing. Claude Code plugin + Codex CLI + Mac App. Full lifecycle: ideation → design → develop → test → deploy → iterate.
npx -y skills add JordanCoin/ios-skills-collection --skill arimunandar--ios-performanceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Memory management, profiling, optimization techniques, and Instruments usage for high-performance iOS apps
SKILL.md
11.6 KB, as published. Nobody here has run it
iOS Performance Optimization
Comprehensive guide to building high-performance iOS applications with proper memory management, profiling, and optimization techniques.
Memory Management
ARC (Automatic Reference Counting)
Strong vs Weak vs Unowned
// STRONG (default) - Increases retain count
class Parent {
var child: Child? // Strong reference
}
// WEAK - Does NOT increase retain count, becomes nil when deallocated
class Child {
weak var parent: Parent? // Weak to prevent retain cycle
}
// UNOWNED - Does NOT increase retain count, crashes if accessed after deallocation
class Customer {
let card: CreditCard // Customer owns the card
init() {
card = CreditCard(holder: self)
}
}
class CreditCard {
unowned let holder: Customer // Card always has a holder
}
When to Use Each
| Reference Type | Use When | Risk |
|---|---|---|
strong | Object should stay alive | Retain cycles |
weak | Optional reference, may become nil | None |
unowned | Non-optional, same or longer lifetime | Crash if wrong |
Retain Cycle Detection
Common Retain Cycle Patterns
// ❌ RETAIN CYCLE - Closure captures self strongly
class ViewController: UIViewController {
var onComplete: (() -> Void)?
func setup() {
onComplete = {
self.doSomething() // Strong capture!
}
}
}
// ✅ FIXED - Weak capture
class ViewController: UIViewController {
var onComplete: (() -> Void)?
func setup() {
onComplete = { [weak self] in
self?.doSomething()
}
}
}
Delegate Retain Cycles
// ❌ RETAIN CYCLE
protocol MyDelegate {
func didComplete()
}
class Parent: MyDelegate {
var child: Child?
init() {
child = Child()
child?.delegate = self // Strong reference back!
}
}
class Child {
var delegate: MyDelegate? // Strong!
}
// ✅ FIXED - Weak delegate
protocol MyDelegate: AnyObject { // Must be AnyObject for weak
func didComplete()
}
class Child {
weak var delegate: MyDelegate? // Weak!
}
Timer Retain Cycles
// ❌ RETAIN CYCLE - Timer retains target
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self, // Timer retains self!
selector: #selector(tick),
userInfo: nil,
repeats: true
)
}
}
// ✅ FIXED - Use closure-based timer with weak capture
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.tick()
}
}
deinit {
timer?.invalidate()
}
}
Memory Leak Checklist
// Add to your ViewControllers to verify deallocation
deinit {
print("✅ \(Self.self) deallocated")
}
Common Leak Sources:
- Closures without
[weak self] - Delegates not marked
weak - NotificationCenter observers not removed
- Timers not invalidated
- DispatchWorkItem not cancelled
- Strong references in singletons
Performance Profiling
Instruments Usage
Time Profiler
# Launch Time Profiler
xcrun xctrace record --template "Time Profiler" --launch -- /path/to/app.app
What to look for:
- Functions taking > 16ms (60fps = 16.67ms per frame)
- Main thread blocking
- Excessive CPU usage
Allocations
# Track memory allocations
xcrun xctrace record --template "Allocations" --launch -- /path/to/app.app
What to look for:
- Memory growth over time (leaks)
- Transient allocations (excessive object creation)
- Large allocations
Leaks
# Detect memory leaks
xcrun xctrace record --template "Leaks" --launch -- /path/to/app.app
In-Code Profiling
// Measure execution time
func measureTime(_ label: String, block: () -> Void) {
let start = CFAbsoluteTimeGetCurrent()
block()
let end = CFAbsoluteTimeGetCurrent()
print("⏱ \(label): \((end - start) * 1000)ms")
}
// Usage
measureTime("Load Data") {
loadData()
}
// Using os_signpost for Instruments
import os.signpost
let log = OSLog(subsystem: "com.app", category: "Performance")
func loadData() {
os_signpost(.begin, log: log, name: "Load Data")
defer { os_signpost(.end, log: log, name: "Load Data") }
// ... work
}
UIKit Optimization
UITableView/UICollectionView
// ✅ OPTIMIZED TableView
final class OptimizedTableViewController: UIViewController {
private let tableView: UITableView = {
let tv = UITableView()
tv.rowHeight = UITableView.automaticDimension
tv.estimatedRowHeight = 60 // Provide estimate!
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
// Register cells for reuse
tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
// Prefetching for smooth scrolling
tableView.prefetchDataSource = self
}
}
extension OptimizedTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
// Preload images or data for upcoming cells
for indexPath in indexPaths {
imageLoader.prefetch(url: items[indexPath.row].imageURL)
}
}
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
// Cancel preloading for cells no longer needed
for indexPath in indexPaths {
imageLoader.cancel(url: items[indexPath.row].imageURL)
}
}
}
Cell Optimization
final class OptimizedCell: UITableViewCell {
// Use layer properties for shadows (GPU accelerated)
override func awakeFromNib() {
super.awakeFromNib()
// Rasterize complex views
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
// Opaque views are faster
backgroundColor = .white
isOpaque = true
}
override func prepareForReuse() {
super.prepareForReuse()
// Cancel any pending image loads
imageView?.image = nil
imageLoadTask?.cancel()
}
}
Image Optimization
// ✅ Downscale images to display size
extension UIImage {
func downscaled(to size: CGSize) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { _ in
draw(in: CGRect(origin: .zero, size: size))
}
}
}
// ✅ Load images asynchronously
final class AsyncImageLoader {
private let cache = NSCache<NSString, UIImage>()
private var tasks: [URL: URLSessionTask] = [:]
func load(url: URL, into imageView: UIImageView) {
// Check cache first
if let cached = cache.object(forKey: url.absoluteString as NSString) {
imageView.image = cached
return
}
// Load asynchronously
let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
guard let data = data, let image = UIImage(data: data) else { return }
// Cache the image
self?.cache.setObject(image, forKey: url.absoluteString as NSString)
DispatchQueue.main.async {
imageView.image = image
}
}
tasks[url] = task
task.resume()
}
func cancel(url: URL) {
tasks[url]?.cancel()
tasks[url] = nil
}
}
View Hierarchy Optimization
// ❌ SLOW - Deep view hierarchy
view.addSubview(container1)
container1.addSubview(container2)
container2.addSubview(container3)
container3.addSubview(label)
// ✅ FAST - Flat hierarchy
view.addSubview(label)
// ❌ SLOW - Transparent layers
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
// ✅ FAST - Opaque layers where possible
view.backgroundColor = .black
view.alpha = 0.5 // Or use solid color if acceptable
Concurrency Performance
Main Thread Protection
// ✅ Keep UI work on main thread
func updateUI(with data: Data) {
DispatchQueue.main.async {
self.label.text = String(data: data, encoding: .utf8)
}
}
// ✅ Heavy work off main thread
func processData() {
Task.detached(priority: .userInitiated) {
let result = await self.heavyComputation()
await MainActor.run {
self.updateUI(with: result)
}
}
}
Batch Operations
// ❌ SLOW - Individual updates
for item in items {
tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
// ✅ FAST - Batch updates
tableView.performBatchUpdates {
let indexPaths = items.indices.map { IndexPath(row: $0, section: 0) }
tableView.insertRows(at: indexPaths, with: .automatic)
}
Network Performance
Request Optimization
final class OptimizedNetworkClient {
private let session: URLSession
init() {
let config = URLSessionConfiguration.default
// Connection pooling
config.httpMaximumConnectionsPerHost = 6
// Caching
config.urlCache = URLCache(
memoryCapacity: 50_000_000, // 50MB memory
diskCapacity: 100_000_000 // 100MB disk
)
config.requestCachePolicy = .returnCacheDataElseLoad
// Timeouts
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 300
session = URLSession(configuration: config)
}
func fetch<T: Decodable>(_ url: URL) async throws -> T {
var request = URLRequest(url: url)
// Enable compression
request.setValue("gzip, deflate", forHTTPHeaderField: "Accept-Encoding")
let (data, _) = try await session.data(for: request)
return try JSONDecoder().decode(T.self, from: data)
}
}
Core Data Performance
// ✅ Batch fetching
let request = NSFetchRequest<Entity>(entityName: "Entity")
request.fetchBatchSize = 20 // Fetch in batches
request.returnsObjectsAsFaults = true // Lazy loading
// ✅ Background context for heavy operations
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.perform {
// Heavy operations here
try? backgroundContext.save()
}
// ✅ Batch delete (much faster than individual deletes)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
try context.execute(deleteRequest)
Performance Checklist
Launch Time
- Defer non-essential work with
DispatchQueue.main.async - Lazy-load view controllers
- Use static/let for constants
- Minimize work in
application(_:didFinishLaunchingWithOptions:)
Scrolling
- Reuse cells properly
- Use estimated heights
- Prefetch data
- Avoid transparent views
- Cache computed values
Memory
- Use
[weak self]in closures - Make delegates weak
- Invalidate timers
- Remove notification observers
- Profile with Instruments
Network
- Enable HTTP caching
- Use compression
- Batch requests where possible
- Cancel unnecessary requests
Quick Wins
| Issue | Solution |
|---|---|
| Slow scrolling | Enable cell reuse, prefetching |
| High memory | Downscale images, check leaks |
| Slow launch | Defer work, lazy load |
| UI freezes | Move work off main thread |
| Battery drain | Reduce background work, use efficient APIs |