Rork Max gets you off the starting line fast, but shipping to the App Store at real quality still means opening Xcode and doing hands-on work. This article walks through the workflow I've converged on after six months of using Rork Max on real solo-dev projects — including the traps that cost me time.
Reading a Rork Max Project Structure
Before touching anything, understand what Rork generated.
MyApp/
├── MyApp.xcodeproj/
├── Sources/
│ ├── App/ ← application entry point
│ ├── Views/ ← SwiftUI views
│ ├── Models/ ← data models, Codable types
│ ├── Services/ ← API clients, persistence
│ └── Utils/ ← extensions, helpers
├── Resources/
│ ├── Assets.xcassets/
│ ├── Localizations/
│ └── Fonts/
└── .rork/ ← Rork's internal metadata (don't touch)
The cardinal rule is: never edit .rork/. It's how Rork Max tracks what it generated so it can regenerate or update the project cleanly later. Hand-editing it breaks regeneration.
Sources are fair game, but changes to files Rork considers "generated" count as conflicts the next time you regenerate. The merge strategy below is how to stay ahead of that.
Three Instruments Profiles to Run
Rork-generated code works. At the starting line, though, it tends to be at "functional" rather than "polished." Instruments is how you close that gap.
1. Time Profiler — CPU Hotspots
First thing I check when something feels slow. The classic Rork-generated pattern that shows up as a hotspot:
// Slow: recomputes every frame during scrolling
var body: some View {
List(items) { item in
Row(item: item, score: computeScore(for: item))
}
}If computeScore is non-trivial, it recalculates on every frame while scrolling. The fix is a cached version:
@State private var cachedScores: [UUID: Double] = [:]
var body: some View {
List(items) { item in
Row(item: item, score: cachedScores[item.id] ?? 0)
}
.task {
await computeAllScores()
}
}2. Allocations — Memory Usage
Image handling in generated code is often the biggest memory waste. Look at Persistent allocations (not Transient). Lists that use AsyncImage or UIImage(data:) without downsampling are a usual suspect.
A helper worth pasting in:
func downsample(image data: Data, to pointSize: CGSize, scale: CGFloat) -> UIImage? {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
return nil
}
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
return nil
}
return UIImage(cgImage: downsampledImage)
}Dropping memory usage by 5x on an image-heavy list is not unusual.
3. SwiftUI Instrument — Redraw Visibility
SwiftUI's declarative model makes redraws non-obvious. The SwiftUI Instrument reveals which views redraw when — and where Rork-generated code triggers unnecessary ones.
The typical culprit: a view observing an ObservableObject redraws for any property change, even unrelated ones. Break the object apart with @Published fields consumed individually, or wrap with EquatableView.