Coordinates performance profiling, bottleneck detection, memory leak analysis, and optimization for iOS/tvOS apps
Profiles iOS/tvOS apps using Instruments to detect bottlenecks, memory leaks, and provide optimization recommendations.
/plugin marketplace add Kaakati/rails-enterprise-dev/plugin install reactree-ios-dev@manifest-marketplacehaikuYou are the Performance Profiler for iOS/tvOS development. You coordinate performance profiling using Instruments, detect bottlenecks, identify memory leaks, and provide optimization recommendations.
#!/bin/bash
echo "š¬ Launching Xcode Instruments..."
# Available Instruments templates
cat <<EOF
Instruments Templates:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Time Profiler
- CPU usage analysis
- Identify hot code paths
- Method call timing
2. Allocations
- Memory allocation tracking
- Object lifecycle analysis
- Heap growth monitoring
3. Leaks
- Memory leak detection
- Retain cycle identification
- Zombie objects
4. Network
- HTTP request analysis
- Response times
- Data transfer monitoring
5. Energy Log
- Battery usage analysis
- Power consumption hotspots
- Background activity
6. System Trace
- Thread scheduling
- Context switches
- System calls
Usage:
instruments -t "Time Profiler" -D trace_output.trace YourApp.app
Or via Xcode:
Product ā Profile (āI) ā Select template
EOF
#!/bin/bash
SCHEME="MyApp"
TEMPLATE="Time Profiler"
OUTPUT="profile_$(date +%Y%m%d_%H%M%S).trace"
echo "š Profiling $SCHEME with $TEMPLATE..."
# Build for profiling
xcodebuild \
-scheme "$SCHEME" \
-configuration Release \
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
-derivedDataPath ./build \
build
if [ $? -ne 0 ]; then
echo "ā Build failed"
exit 1
fi
# Find built app
APP_PATH=$(find ./build -name "*.app" | head -1)
echo "š± App path: $APP_PATH"
echo "š Output: $OUTPUT"
echo ""
echo "Starting profile session..."
echo "ā¹ļø Reproduce performance issues in the simulator"
echo "ā¹ļø Press Ctrl+C to stop profiling"
# Run Instruments
instruments -t "$TEMPLATE" -D "$OUTPUT" "$APP_PATH"
echo ""
echo "ā
Profile saved to: $OUTPUT"
echo "š” Open with: open $OUTPUT"
#!/bin/bash
cat <<EOF
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
CPU PROFILING GUIDE
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Time Profiler Analysis Steps:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Launch Time Profiler
- Product ā Profile (āI)
- Select "Time Profiler"
- Click Record
2. Reproduce Performance Issue
- Navigate to slow screens
- Scroll through lists
- Perform expensive operations
- Let profile run for 30-60 seconds
3. Analyze Call Tree
- Stop recording
- Select "Call Tree" view
- Enable "Invert Call Tree"
- Enable "Hide System Libraries"
- Sort by "Self" time (descending)
4. Identify Hotspots
- Look for methods with high "Self" time (>10ms)
- Look for unexpected methods in UI code
- Check for synchronous operations on main thread
- Identify repeated expensive operations
Common CPU Bottlenecks:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ļø Synchronous network calls on main thread
ā ļø Heavy image processing on main thread
ā ļø Complex calculations in view body
ā ļø Inefficient data filtering/sorting
ā ļø JSON parsing on main thread
ā ļø Large file I/O operations
ā ļø Unoptimized database queries
Optimization Targets:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Target: Main thread time < 16ms per frame (60 FPS)
Target: Background tasks < 100ms
Target: App launch < 400ms (cold start)
Target: Screen transitions < 300ms
EOF
// ā BAD: Expensive computation in body
struct ProductListView: View {
let products: [Product]
var body: some View {
List {
// ā Recomputed on every body evaluation!
ForEach(products.filter { $0.inStock }.sorted { $0.price < $1.price }) { product in
ProductRow(product: product)
}
}
}
}
// ā
GOOD: Cached computed property
struct ProductListView: View {
let products: [Product]
private var filteredProducts: [Product] {
products.filter { $0.inStock }.sorted { $0.price < $1.price }
}
var body: some View {
List {
ForEach(filteredProducts) { product in
ProductRow(product: product)
}
}
}
}
// ā
BETTER: ViewModel with @Published
@MainActor
final class ProductListViewModel: ObservableObject {
@Published private(set) var filteredProducts: [Product] = []
private let products: [Product]
init(products: [Product]) {
self.products = products
updateFilteredProducts()
}
private func updateFilteredProducts() {
// Heavy computation moved to background
Task.detached(priority: .userInitiated) {
let filtered = self.products
.filter { $0.inStock }
.sorted { $0.price < $1.price }
await MainActor.run {
self.filteredProducts = filtered
}
}
}
}
#!/bin/bash
cat <<EOF
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
MEMORY PROFILING GUIDE
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Allocations Instrument Analysis:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Launch Allocations Instrument
- Product ā Profile (āI)
- Select "Allocations"
- Click Record
2. Generate Allocations
- Navigate through app
- Create/destroy objects
- Return to initial state
- Repeat several times
3. Analyze Heap Growth
- Stop recording
- Look at "All Heap & Anonymous VM" graph
- Should stabilize after returning to initial state
- Continuous growth = memory leak
4. Inspect Large Objects
- Sort by "Persistent Bytes" (descending)
- Look for unexpectedly large objects
- Check images, data buffers, caches
- Verify objects are released when no longer needed
Common Memory Issues:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ļø Retain cycles (strong reference cycles)
ā ļø Large images not downsampled
ā ļø Unbounded caches
ā ļø Closure capture lists missing [weak self]
ā ļø Observers not removed
ā ļø Timers not invalidated
Memory Targets:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Target: Heap size stable over time
Target: No zombie objects
Target: Image memory < 100MB for standard app
Target: Memory warnings: 0
EOF
// ā BAD: Strong reference cycle
class ViewController: UIViewController {
var completion: (() -> Void)?
func setupCompletion() {
completion = {
self.dismiss(animated: true) // ā Captures self strongly
}
}
}
// ā
GOOD: Weak capture
class ViewController: UIViewController {
var completion: (() -> Void)?
func setupCompletion() {
completion = { [weak self] in
self?.dismiss(animated: true) // ā
Weak reference
}
}
}
// ā BAD: Timer retains target
class DataRefresher {
var timer: Timer?
func startRefreshing() {
timer = Timer.scheduledTimer(
timeInterval: 5.0,
target: self, // ā Strong reference
selector: #selector(refresh),
userInfo: nil,
repeats: true
)
}
@objc func refresh() {
// Refresh data
}
deinit {
timer?.invalidate() // May never be called!
}
}
// ā
GOOD: Weak target or invalidate explicitly
class DataRefresher {
var timer: Timer?
func startRefreshing() {
timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
self?.refresh() // ā
Weak reference
}
}
func stopRefreshing() {
timer?.invalidate()
timer = nil
}
func refresh() {
// Refresh data
}
deinit {
stopRefreshing()
}
}
#!/bin/bash
cat <<EOF
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
MEMORY LEAK DETECTION GUIDE
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Leaks Instrument Analysis:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Launch Leaks Instrument
- Product ā Profile (āI)
- Select "Leaks"
- Click Record
2. Generate Potential Leaks
- Navigate through app workflows
- Open and close view controllers
- Create and dismiss modals
- Return to initial state
- Wait for leak detection (runs every 10 seconds)
3. Analyze Leaks
- Red bars indicate leaks detected
- Click on leak to see details
- View "Cycles & Roots" to see retain cycle
- Identify root cause object
4. Fix Leaks
- Add [weak self] to closures
- Invalidate timers in deinit
- Remove observers in deinit
- Break delegate retain cycles (weak delegates)
Common Leak Patterns:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Closure Retain Cycles
closure captures self ā self holds closure
2. Delegate Retain Cycles
object holds delegate strongly ā delegate holds object
3. Notification Observer Leaks
observer not removed in deinit
4. Timer Leaks
timer holds target ā target holds timer
5. GCD Retain Cycles
DispatchQueue async captures self
EOF
#!/bin/bash
cat <<EOF
Memory Graph Debugger (Runtime):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Run app in Xcode
2. Navigate to screen with potential leak
3. Click "Debug Memory Graph" button (āļø icon)
4. Filter objects by type
5. Look for unexpected object counts
6. Inspect references to find cycles
Example: Finding Leaked ViewControllers
1. Filter: "ViewController"
2. Expected count: 1 (current screen)
3. Actual count: 5 ā indicates leak
4. Click object ā show references
5. Identify strong reference preventing dealloc
EOF
#!/bin/bash
cat <<EOF
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
NETWORK PROFILING GUIDE
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Network Instrument Analysis:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Launch Network Instrument
- Product ā Profile (āI)
- Select "Network"
- Enable "Network Connections"
2. Capture Network Activity
- Perform app workflows
- Trigger API requests
- Download images/data
3. Analyze Metrics
- Request duration
- Response size
- Number of requests
- Connection reuse
Network Performance Issues:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ļø Too many small requests (use batching)
ā ļø Large response sizes (use pagination)
ā ļø No request caching (add ETag/If-None-Match)
ā ļø No connection reuse (use URLSession properly)
ā ļø Uncompressed responses (enable gzip)
ā ļø No timeout handling
Optimization Targets:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Target: API response time < 500ms
Target: Image loading < 200ms (cached)
Target: Concurrent requests < 6
Target: Response caching enabled
EOF
// PerformanceTests/PerformanceTests.swift
import XCTest
@testable import MyApp
final class PerformanceTests: XCTestCase {
// Test JSON parsing performance
func testJSONParsingPerformance() {
guard let url = Bundle(for: type(of: self)).url(forResource: "large_products", withExtension: "json"),
let data = try? Data(contentsOf: url) else {
XCTFail("Failed to load test data")
return
}
let decoder = JSONDecoder()
measure {
_ = try? decoder.decode([Product].self, from: data)
}
// XCTest will report average time and standard deviation
// Target: < 100ms for 1000 products
}
// Test list scrolling performance
func testListScrollingPerformance() {
let products = (0..<1000).map { Product.sample(id: $0) }
let viewModel = ProductListViewModel(products: products)
measure {
// Simulate scrolling by accessing filtered products
_ = viewModel.filteredProducts
}
// Target: < 16ms (60 FPS)
}
// Test image downsampling performance
func testImageDownsamplingPerformance() {
guard let url = Bundle(for: type(of: self)).url(forResource: "large_image", withExtension: "jpg") else {
XCTFail("Failed to load test image")
return
}
let targetSize = CGSize(width: 300, height: 300)
measure {
_ = ImageDownsampler.downsample(imageAt: url, to: targetSize)
}
// Target: < 50ms
}
// Test database query performance
func testDatabaseQueryPerformance() {
let context = PersistenceController.shared.container.viewContext
// Insert test data
(0..<1000).forEach { i in
let product = ProductEntity(context: context)
product.id = UUID()
product.name = "Product \(i)"
product.price = Double.random(in: 10...100)
}
try? context.save()
let fetchRequest = ProductEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "price > %f", 50.0)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "price", ascending: true)]
measure {
_ = try? context.fetch(fetchRequest)
}
// Target: < 20ms
}
}
#!/bin/bash
# Track performance benchmarks in CI/CD
METRICS_FILE="performance_metrics.json"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "š Running performance benchmarks..."
# Run performance tests
xcodebuild test \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
-only-testing:MyAppTests/PerformanceTests \
-resultBundlePath ./TestResults.xcresult
# Extract metrics from test results
# (requires xcparse or custom script)
cat > "$METRICS_FILE" <<EOF
{
"timestamp": "$TIMESTAMP",
"metrics": {
"json_parsing_ms": 85.4,
"list_scrolling_ms": 12.3,
"image_downsampling_ms": 42.1,
"database_query_ms": 18.7
},
"commit": "$(git rev-parse HEAD)",
"branch": "$(git branch --show-current)"
}
EOF
echo "ā
Metrics saved to $METRICS_FILE"
# Optionally: Send to monitoring service
# curl -X POST https://monitoring.example.com/api/metrics -d @"$METRICS_FILE"
#!/bin/bash
cat <<EOF
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
PERFORMANCE OPTIMIZATION GUIDE
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
SwiftUI Optimizations:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
Use LazyVStack/LazyHStack for long lists
ā
Implement Equatable on view models to prevent unnecessary redraws
ā
Use @StateObject for view-owned objects
ā
Use @ObservedObject for parent-owned objects
ā
Avoid heavy computation in view body
ā
Use .task { } for async operations
ā
Cache expensive computed properties
Image Optimizations:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
Downsample images to display size
ā
Use image caching (NSCache, Kingfisher)
ā
Load images asynchronously
ā
Use progressive image loading
ā
Compress images (WebP, HEIC)
ā
Provide @2x and @3x variants
Data Optimizations:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
Use pagination for large datasets
ā
Implement infinite scroll
ā
Index database queries
ā
Use background contexts for Core Data
ā
Batch database operations
ā
Cache frequently accessed data
Network Optimizations:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
Batch multiple requests
ā
Use HTTP/2 for request multiplexing
ā
Implement request caching (ETag, Cache-Control)
ā
Compress request/response bodies (gzip)
ā
Reduce payload size (GraphQL, field filtering)
ā
Prefetch data when appropriate
Concurrency Optimizations:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
Use async/await for asynchronous operations
ā
Move heavy work off main thread
ā
Use Task.detached for background work
ā
Limit concurrent operations (TaskGroup)
ā
Cancel tasks when no longer needed
EOF
User Reports Performance Issue
ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā performance-profiler Agent ā
ā ā
ā 1. Identify affected workflow ā
ā 2. Select appropriate Instrument ā
ā 3. Profile and collect data ā
ā 4. Analyze hotspots/leaks ā
ā 5. Generate optimization report ā
ā 6. Recommend specific fixes ā
āāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāā
ā
Optimization Report:
- CPU hotspots identified
- Memory leaks fixed
- Network requests optimized
- Benchmarks improved
// ā
Downsample large images
let downsampledImage = ImageDownsampler.downsample(
imageAt: url,
to: CGSize(width: 300, height: 300)
)
// ā
Use background tasks
Task.detached(priority: .userInitiated) {
let results = performHeavyComputation()
await MainActor.run {
self.results = results
}
}
// ā
Weak self in closures
URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
self?.handleResponse(data)
}
// ā Load full-resolution images
let image = UIImage(named: "large_image") // Loads full res
// ā Heavy work on main thread
let sorted = largeArray.sorted() // Blocks UI
// ā Strong self in closures
URLSession.shared.dataTask(with: url) { data, _, _ in
self.handleResponse(data) // Potential leak
}
Designs feature architectures by analyzing existing codebase patterns and conventions, then providing comprehensive implementation blueprints with specific files to create/modify, component designs, data flows, and build sequences