Engineering Process
How We Build
Not a methodology. A set of principles that shape every architecture decision — from data model to AI integration to App Store submission.
1. Offline-first is the default
Every system we build assumes the network is unreliable. Data lives on-device first — in Core Data or SwiftData — and sync is a separate concern layered on top.
This isn't a preference — it's the architecture choice that most directly determines whether an app is usable in practice. A field worker in poor connectivity, a user on a plane, a team operating across time zones: all of them need the app to work regardless of network state.
We use CloudKit for sync when the product calls for it — not as a primary data layer, but as a propagation mechanism. Records merge on device. Conflicts are handled explicitly.
See also: Offline-first iOS architecture guide · NSPersistentCloudKitContainer production guide
Stack
- — Core Data with NSPersistentCloudKitContainer
- — SwiftData for greenfield iOS 17+ apps
- — CloudKit for cross-device and share-based sync
- — WatchConnectivity for Apple Watch data bridging
2. AI runs on the device, not in the cloud
When intelligence belongs in the product, it belongs on the device. We integrate Core ML and Apple Foundation Models to run inference locally — no user data leaves the device, no API keys to manage, no latency from a round-trip to a cloud endpoint.
This architecture is not just a privacy choice — it's a product reliability choice. AI features work offline, respond instantly, and don't depend on infrastructure you don't control.
Where cloud AI is genuinely appropriate (large-context summarization, multi-modal tasks), we scope it as an optional extension layer, not the foundation. The core product always functions without it.
Stack
- — Core ML for classification, prediction, and NLP
- — Apple Foundation Models (on-device LLM, iOS 26+)
- — Create ML for custom model training
- — Vision framework for image and document analysis
3. SwiftUI architecture that extends without rewrites
We build SwiftUI apps in layers: a view layer with zero business logic, a domain layer that knows nothing about the UI, and a data layer that handles persistence and sync. Each layer can be tested independently. Each can be replaced without touching the others.
This isn't abstract architectural preference — it's the difference between a codebase a client can hand to a second engineer in month three, versus one that only the original author can safely modify.
We use MVVM or a thin TCA-inspired approach depending on project complexity, always with explicit state ownership and predictable data flow.
4. Every constraint gets named explicitly
Scope, timeline, and platform targets determine what architecture is appropriate. A 6-week MVP sprint has different constraints than a 12-month enterprise platform. We don't apply the same architecture to both.
We size solutions to the problem. That means sometimes recommending SwiftData over Core Data because the team will ship faster. Sometimes recommending against CloudKit sync because the sync conflict model doesn't match the product's data shape. Sometimes recommending an architecture that's slower to build but faster to maintain.
These trade-offs are documented as part of the engagement — not as footnotes, but as first-class deliverables. A client handed a system should be able to understand why every major decision was made.
5. Shipping is part of the architecture
App Store submission, TestFlight distribution, CI/CD configuration, entitlements, provisioning — these are not afterthoughts. We design for deployability from the start, because an app that can't be shipped reliably isn't production-grade regardless of code quality.
Every engagement ends with a working build in the client's account, documented processes, and a codebase that a new engineer can orient in within a day.
Technology decisions: direct recommendations
These come up in every architecture conversation. The right answer depends on constraints — here are the criteria we apply.
On-Device AI vs Cloud AI for iOS
Use on-device AI when:
- ✓ The feature must work offline
- ✓ User data is sensitive (health, finances, private content)
- ✓ The inference task fits Core ML or Foundation Models (classification, short-form text, image analysis)
- ✓ You want no per-request API cost and no rate limits
- ✓ Response latency matters (on-device returns in milliseconds)
Use cloud AI when:
- — The task requires large context (full document summarization, multi-turn reasoning)
- — You need multi-modal capabilities not available on-device
- — The target devices are older than A12 (no Neural Engine efficiency)
- — The feature is non-core and an internet requirement is acceptable
Recommendation: Start on-device. Add cloud AI only as an optional extension if on-device capabilities are genuinely insufficient for the specific task. Apps that run AI purely in the cloud have a hard dependency on internet availability that users will notice.
SwiftUI vs React Native for Production iOS Apps
Use SwiftUI when:
- ✓ The app uses Apple-specific frameworks: Core Data, CloudKit, HealthKit, ARKit, Core ML, WatchKit
- ✓ You are building iOS/iPadOS/macOS only — no Android requirement
- ✓ The interaction model relies on platform system UI (widgets, Live Activities, Siri integration)
- ✓ Long-term maintainability and alignment with Apple platform evolution matters
Use React Native when:
- — You need iOS and Android from a single codebase with a small team
- — The app is primarily content display and navigation (no hardware-intensive features)
- — Existing web/JavaScript team is doing the build
Recommendation: For any app that uses more than one Apple framework (Core Data, HealthKit, Core ML, CloudKit, etc.), SwiftUI is the right choice. React Native's bridging overhead compounds with each native framework added. Build in Swift when you are building for Apple platforms.
CloudKit vs Custom Backend for iOS Sync
Use CloudKit when:
- ✓ Sync is primarily single-user across personal devices (iPhone → iPad → Mac)
- ✓ Collaborative writes can be partitioned (each user owns specific records)
- ✓ Zero infrastructure maintenance is a priority (no server, no DevOps)
- ✓ Apple infrastructure availability and compliance is sufficient for your users
Use a custom backend when:
- — Multiple users concurrently modify shared numeric state (inventory counts, balances)
- — You need real-time collaboration (shared document editing, live presence)
- — Web or Android clients need the same data layer
- — You need server-side business logic, webhooks, or third-party integrations
Recommendation: For iOS-only apps with a single user or simple team collaboration, default to CloudKit. It eliminates infrastructure cost and maintenance burden. Move to a custom backend when concurrent multi-user writes on shared state are a requirement — CloudKit's last-writer-wins merge will produce incorrect results for that pattern. See the CloudKit CRDT limits research for the detailed failure scenarios.
See it in practice
These principles aren't abstracted from real work. Each case study documents a real system built with these constraints.
