
Coding Best Practices
- 3 installs
- 591 repo stars
- Updated July 24, 2026
- rshankras/claude-code-apple-skills
Reviews Swift/iOS code against modern Swift idioms, Apple platform best practices, architecture patterns, and code-quality standards.
About
Reviews Swift and iOS code for idiomatic patterns, architecture, and quality, offering refactoring and performance suggestions. A developer uses it for code review or to improve an existing Swift codebase.
- Checks modern Swift idioms and Apple platform best practices
- Covers architecture patterns and performance optimization
Coding Best Practices by the numbers
- 3 all-time installs (skills.sh)
- +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #923 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill coding-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 591 |
| Last updated | July 24, 2026 |
| Repository | rshankras/claude-code-apple-skills ↗ |
What it does
Reviews Swift/iOS code against modern Swift idioms, Apple platform best practices, architecture patterns, and code-quality standards.
Files
Coding Best Practices Skill
Reviews Swift/iOS code for adherence to modern Swift idioms, Apple platform best practices, architecture patterns, and code quality standards.
When This Skill Activates
Use this skill when the user:
- Asks for code review or code quality check
- Mentions "best practices", "clean code", or "refactoring"
- Wants to improve existing code
- Requests architecture or design pattern review
- Asks about Swift idioms or modern patterns
- Wants performance optimization suggestions
Review Process
1. Identify Scope
- If user specifies files/classes, review those
- Otherwise, ask which areas to focus on or review recent changes
- Prioritize ViewModels, business logic, and data layer over simple views
2. Load Reference Patterns
Before starting the review, familiarize yourself with the reference patterns by reading the following files in .claude/skills/coding-best-practices/:
- swift-patterns.md - Optionals, type safety, collections, error handling, naming
- swiftui-patterns.md - State management, view composition, performance
- architecture-patterns.md - MVVM, code organization, memory management, security
- coredata-patterns.md - Core Data best practices, fetching, saving, relationships
3. Review Categories
Apply these review categories based on the code type:
For All Code:
- Swift language idioms (optionals, type safety, collections)
- Naming conventions
- Error handling
- Memory management
For SwiftUI Code:
- State management (
@State,@Observable+@Bindable; legacy@StateObject/@ObservedObjectpre-iOS 17) - View composition and performance
- MVVM separation
For ViewModels:
- Business logic placement
- MVVM architecture adherence
- Testability (dependency injection)
For Core Data Code:
- Context management
- Save/fetch patterns
- Relationship handling
- CloudKit integration
4. Review Output Format
Provide review in this structure:
✅ Strengths Found
- List well-implemented patterns
- Highlight good practices
- Acknowledge clean code sections
⚠️ Issues Found
For each issue, use this format:
Category: [Category Name]
[Priority]: [File.swift:line] - [Issue description]
// Current:
[problematic code]
// Suggested:
[improved code]
// Reason: [explanation]Priority Levels:
- High: Will cause bugs, crashes, or serious issues
- Medium: Inefficient, hard to maintain, or non-idiomatic
- Low: Minor improvements, nice-to-haves
📊 Code Quality Score
Overall: X/10
- Swift Idioms: X/10
- Architecture: X/10
- Error Handling: X/10
- Naming: X/10
- Organization: X/10
- Performance: X/10
📋 Recommendations
1. High Priority: [Critical issues] 2. Medium Priority: [Improvements] 3. Low Priority: [Nice-to-haves]
🔧 Quick Wins
List 3-5 easy fixes that provide immediate value
Review Checklist
Use this comprehensive checklist during review:
Swift Language
- [ ] No force unwrapping unless intentional
- [ ] Proper optional handling (guard, if let, ??)
- [ ] Enums instead of string/int constants
- [ ] Functional collection operations (map, filter, etc.)
- [ ] Proper error handling (not silent try?)
- [ ] Clear, descriptive naming
SwiftUI
- [ ] Correct property wrapper usage
- [ ] No ViewModels created in body
- [ ] Views broken into components
- [ ] No heavy computation in body
- [ ] Single source of truth
Architecture
- [ ] MVVM separation maintained
- [ ] Business logic in ViewModels
- [ ] UI logic in Views only
- [ ] Proper code organization with MARK
- [ ] Private by default
Core Data
- [ ] Using shared context
- [ ] hasChanges check before save
- [ ] Typed fetch requests
- [ ] Safe property access
- [ ] Proper error handling
Memory Management
- [ ] [weak self] in escaping closures
- [ ] Weak delegates
- [ ] No retain cycles
Testing & Security
- [ ] Testable code structure
- [ ] Dependency injection
- [ ] No hardcoded secrets
- [ ] Input validation
- [ ] Safe logging
Example Review Output
Reviewing: ExpenseViewModel.swift
✅ Strengths Found
- Excellent use of @Published properties
- Clean separation between public and private methods
- Good error handling with custom error types
- Proper use of guard statements for early returns
⚠️ Issues Found
**Category: Optionals Handling**
**High Priority: ExpenseViewModel.swift:45** - Force unwrapping
// Current:
let payer = expense.payer!
// Suggested:
guard let payer = expense.payer else {
print("Expense has no payer")
return
}
// Reason: Force unwrapping will crash if payer is nil. Use guard for safe unwrapping.
**Category: Core Data**
**Medium Priority: ExpenseViewModel.swift:89** - Saving without checking hasChanges
// Current:
try? context.save()
// Suggested:
if context.hasChanges {
do {
try context.save()
} catch {
print("Failed to save: \(error.localizedDescription)")
}
}
// Reason: Check hasChanges to avoid unnecessary saves. Handle errors properly.
**Category: Collections**
**Low Priority: ExpenseViewModel.swift:123** - Inefficient filtering
// Current:
let found = expenses.filter { $0.id == targetId }.first
// Suggested:
let found = expenses.first { $0.id == targetId }
// Reason: first(where:) stops at first match, filter processes entire array.
📊 Code Quality Score
**Overall: 7/10**
- Swift Idioms: 6/10 (force unwrapping, inefficient collection usage)
- Architecture: 9/10 (excellent MVVM separation)
- Error Handling: 7/10 (using try? too often)
- Naming: 9/10 (clear, descriptive names)
- Organization: 8/10 (good marks, could improve grouping)
- Performance: 7/10 (some inefficient patterns)
📋 Recommendations
1. **High Priority**: Remove all force unwrapping (5 instances found)
2. **Medium Priority**: Improve error handling (don't swallow errors with try?)
3. **Low Priority**: Use first(where:) instead of filter().first
🔧 Quick Wins
1. Replace `expense.payer!` with safe unwrapping (ExpenseViewModel.swift:45)
2. Add hasChanges check before context.save() (ExpenseViewModel.swift:89)
3. Use first(where:) for finding items (ExpenseViewModel.swift:123)Tips for Effective Reviews
Be Constructive
- Provide clear code examples for every issue
- Explain WHY, not just WHAT
- Be educational, not judgmental
Consider Context
- Some patterns are valid in certain scenarios
- Balance idealism with pragmatism
- Consider project constraints
Prioritize Impact
- Focus on issues that affect correctness first
- Then performance and maintainability
- Style issues last
Actionable Feedback
- Provide specific line numbers
- Show exact code to change
- Explain expected behavior
References
Notes
- Read the reference pattern files for detailed examples
- Focus on the most impactful improvements first
- Provide code examples for all suggested changes
- Reference exact file locations (filename.swift:lineNumber)
- Be thorough but constructive
Architecture Patterns
Best practices for MVVM architecture, code organization, and memory management.
MVVM Architecture
Core Principles
Model: Pure data structures (Core Data entities, structs) View: SwiftUI views - presentation only ViewModel: Business logic, data operations, state management
Anti-patterns
// ❌ Business logic in View
struct ExpenseView: View {
@State var expenses: [Expense] = []
var body: some View {
VStack {
Button("Calculate") {
// Complex calculation logic here
let total = expenses.reduce(0) { $0 + $1.amount }
// Save to Core Data here
}
}
}
}
// ❌ UI logic in ViewModel
class ExpenseViewModel: ObservableObject {
func getBackgroundColor() -> Color {
return isValid ? .green : .red
}
}Good patterns
// ✅ Clean View
struct ExpenseView: View {
@StateObject private var viewModel: ExpenseViewModel
var body: some View {
VStack {
Button("Calculate") {
viewModel.calculateTotal()
}
}
.background(isValidColor)
}
private var isValidColor: Color {
viewModel.isValid ? .green : .red
}
}
// ✅ ViewModel with business logic
class ExpenseViewModel: ObservableObject {
@Published var totalAmount: Double = 0
@Published var isValid: Bool = false
func calculateTotal() {
totalAmount = expenses.reduce(0) { $0 + $1.amount }
isValid = totalAmount > 0
}
}Responsibilities
Views Should:
- Render UI
- Handle user input events
- Bind to ViewModel state
- Apply UI styling and layout
Views Should NOT:
- Perform business logic
- Access data layer directly
- Calculate derived values
- Make network/database calls
ViewModels Should:
- Contain business logic
- Manage state
- Validate data
- Coordinate data operations
- Transform data for presentation
ViewModels Should NOT:
- Reference UIKit/SwiftUI types (Color, Font, etc.)
- Perform UI layout
- Handle navigation directly
Checklist
- [ ] Views only handle presentation
- [ ] ViewModels contain business logic
- [ ] Models are pure data structures
- [ ] No business logic in Views
- [ ] No UI code in ViewModels
- [ ] Clear separation of concerns
Code Organization
File Structure
// ✅ Organized with marks
class ExpenseViewModel: ObservableObject {
// MARK: - Properties
@Published private(set) var expenses: [Expense] = []
@Published private(set) var totalAmount: Double = 0
private var isLoading: Bool = false
// MARK: - Initialization
init() {
loadExpenses()
}
// MARK: - Public Methods
func addExpense(_ expense: Expense) { }
func deleteExpense(_ expense: Expense) { }
// MARK: - Private Methods
private func loadExpenses() { }
private func calculateTotal() { }
}
// ✅ Extensions for protocols
extension ExpenseViewModel: UITableViewDelegate {
// Delegate methods
}Access Control
// ❌ Public everything
class ExpenseViewModel {
var expenses: [Expense] = []
var totalAmount: Double = 0
var isLoading: Bool = false
}
// ✅ Private by default
class ExpenseViewModel {
@Published private(set) var expenses: [Expense] = []
@Published private(set) var totalAmount: Double = 0
private var isLoading: Bool = false
}Common MARK Sections
// MARK: - Properties
// MARK: - Initialization
// MARK: - Lifecycle
// MARK: - Public Methods
// MARK: - Private Methods
// MARK: - Actions
// MARK: - Helpers
// MARK: - ConstantsChecklist
- [ ] Logical grouping with
// MARK: - - [ ] Private by default
- [ ] Consistent ordering (properties → lifecycle → methods)
- [ ] Extensions for protocol conformance
- [ ] Clear file naming
Memory Management
Retain Cycles
Anti-patterns
// ❌ Retain cycle
class ExpenseViewModel {
var onComplete: (() -> Void)?
func loadData() {
dataService.fetch { data in
self.onComplete?() // Potential retain cycle
}
}
}
// ❌ Delegate retain cycle
class MyView {
var delegate: MyDelegate // Should be weak
}Good patterns
// ✅ Weak self
func loadData() {
dataService.fetch { [weak self] data in
guard let self = self else { return }
self.process(data)
}
}
// ✅ Weak delegate
protocol ExpenseViewModelDelegate: AnyObject { }
class ExpenseViewModel {
weak var delegate: ExpenseViewModelDelegate?
}
// ✅ Unowned for guaranteed non-nil
class ExpenseView {
unowned let parentController: UIViewController
}Closure Capture Rules
// ✅ Use [weak self] when:
// - Self might be deallocated before closure completes
// - Async operations (network, timers)
// - Escaping closures
// ✅ Use [unowned self] when:
// - Self is guaranteed to exist
// - Closure lifecycle tied to self
// ✅ No capture needed when:
// - Non-escaping closures
// - Static context
// - No reference to selfChecklist
- [ ] Use
[weak self]in closures when needed - [ ] Avoid retain cycles with delegates (use
weak) - [ ] Be careful with
@escapingclosures - [ ] Use value types (structs) where appropriate
- [ ] Cancel tasks/subscriptions in deinit
Dependency Injection
Anti-patterns
// ❌ Hard to test
class ExpenseViewModel {
func save() {
CoreDataStack.sharedInstance.save()
}
}Good patterns
// ✅ Testable with dependency injection
protocol DataStore {
func save() throws
}
class ExpenseViewModel {
private let dataStore: DataStore
init(dataStore: DataStore = CoreDataStack.sharedInstance) {
self.dataStore = dataStore
}
func save() {
try? dataStore.save()
}
}
// ✅ For testing
class MockDataStore: DataStore {
var saveCallCount = 0
func save() throws {
saveCallCount += 1
}
}Benefits
- Testability: Easy to mock dependencies
- Flexibility: Swap implementations
- Decoupling: Reduce direct dependencies
- Clarity: Explicit dependencies
Checklist
- [ ] Protocol-based dependencies
- [ ] Inject dependencies via initializer
- [ ] Provide default implementations
- [ ] Avoid singletons (or make them injectable)
Security & Privacy
Sensitive Data
// ❌ Bad practices
let apiKey = "sk_live_abc123..." // Hardcoded
UserDefaults.standard.set(password, forKey: "password") // Insecure storage
// ✅ Good practices
// Use environment variables or configuration files
let apiKey = ProcessInfo.processInfo.environment["API_KEY"]
// Use Keychain for sensitive data
KeychainManager.save(password, for: "userPassword")Input Validation
// ✅ Always validate user input
func saveExpense(amount: String) throws {
guard let amountValue = Double(amount), amountValue > 0 else {
throw ValidationError.invalidAmount
}
// ...
}Logging
// ❌ Don't log sensitive data
print("User password: \(password)")
print("Credit card: \(creditCard)")
// ✅ Log safely
print("User authenticated: \(user.id)")
print("Payment processed: ***")Checklist
- [ ] No hardcoded credentials or API keys
- [ ] Sensitive data in Keychain, not UserDefaults
- [ ] Use HTTPS for network requests
- [ ] Validate user input
- [ ] Avoid logging sensitive data
File Organization Best Practices
Project Structure
EasySplit/
├── Models/ # Core Data entities, data models
├── ViewModels/ # ViewModels for each feature
├── Views/ # SwiftUI views
│ ├── Main/
│ ├── Expense/
│ ├── Group/
│ └── Settings/
├── Services/ # Core Data, networking, etc.
├── Utilities/ # Helpers, extensions
└── Resources/ # Assets, localizationNaming Conventions
- Views:
ExpenseListView.swift,AddExpenseView.swift - ViewModels:
ExpenseViewModel.swift,GroupViewModel.swift - Models:
Expense.swift,Group.swift - Protocols:
DataStore.swift,Loadable.swift - Extensions:
String+Extensions.swift,Date+Formatting.swift
References
Core Data Patterns
Best practices for working with Core Data in iOS applications.
Context Management
Anti-patterns
// ❌ Creating new contexts
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
// ❌ Multiple contexts without coordination
let context1 = persistentContainer.viewContext
let context2 = persistentContainer.newBackgroundContext()
// Using both without proper synchronizationGood patterns
// ✅ Use shared context
let context = CoreDataStack.sharedInstance.managedObjectContext
// ✅ Background context for heavy operations
let backgroundContext = CoreDataStack.sharedInstance.newBackgroundContext()
backgroundContext.perform {
// Heavy work here
try? backgroundContext.save()
}Checklist
- [ ] Always use single shared context for UI operations
- [ ] Use background contexts for batch operations
- [ ] Coordinate context changes with notifications
- [ ] Not creating new contexts unnecessarily
Saving Data
Anti-patterns
// ❌ Saving without checking
try? context.save()
// ❌ Saving in a loop
for user in users {
let expense = Expense(context: context)
expense.name = user.name
try? context.save() // Inefficient!
}
// ❌ Silent failures
do {
try context.save()
} catch {
// Ignoring error
}Good patterns
// ✅ Check before saving
if context.hasChanges {
do {
try context.save()
} catch {
print("Save failed: \(error.localizedDescription)")
// Handle error appropriately
}
}
// ✅ Batch operations
for user in users {
let expense = Expense(context: context)
expense.name = user.name
}
// Save once after all changes
if context.hasChanges {
try? context.save()
}
// ✅ Proper error handling
func saveContext() throws {
guard context.hasChanges else { return }
try context.save()
}Save Checklist
- [ ] Check
hasChangesbefore saving - [ ] Handle save errors properly
- [ ] Batch multiple changes before saving
- [ ] Use proper error handling, not
try?
Fetching Data
Anti-patterns
// ❌ Untyped fetch
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Expense")
// ❌ Fetching all data
let expenses = try? context.fetch(request)
// ❌ String-based sorting
let sort = NSSortDescriptor(key: "date", ascending: true)Good patterns
// ✅ Typed fetch
let request: NSFetchRequest<Expense> = Expense.fetchRequest()
// ✅ Use predicates to filter
request.predicate = NSPredicate(format: "amount > %f", 100.0)
// ✅ Use sort descriptors
request.sortDescriptors = [
NSSortDescriptor(keyPath: \Expense.date, ascending: false)
]
// ✅ Limit results if needed
request.fetchLimit = 50
// ✅ Fetch with error handling
do {
let expenses = try context.fetch(request)
return expenses
} catch {
print("Fetch failed: \(error.localizedDescription)")
return []
}Predicate Examples
// Simple comparison
NSPredicate(format: "amount > %f", 100.0)
// String matching
NSPredicate(format: "name CONTAINS[cd] %@", searchText)
// Date filtering
NSPredicate(format: "date >= %@ AND date <= %@", startDate as NSDate, endDate as NSDate)
// Relationship filtering
NSPredicate(format: "group.name == %@", groupName)
// Multiple conditions
NSPredicate(format: "amount > %f AND category == %@", 50.0, category)
// IN operator
NSPredicate(format: "category IN %@", categories)
// Compound predicates
let predicate1 = NSPredicate(format: "amount > %f", 100.0)
let predicate2 = NSPredicate(format: "isPaid == YES")
let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate1, predicate2])Fetch Checklist
- [ ] Use typed fetch requests
- [ ] Apply predicates to filter data
- [ ] Use sort descriptors for ordering
- [ ] Set fetch limits for large datasets
- [ ] Handle fetch errors properly
Property Access
Anti-patterns
// ❌ Force unwrapping managed objects
let name = expense.name!
let amount = expense.amount!
// ❌ Not checking for nil
func displayExpense(_ expense: Expense) {
label.text = expense.name // Might be nil
}Good patterns
// ✅ Safe property access
let name = expense.name ?? "Untitled"
let amount = expense.amount ?? 0.0
// ✅ Guard for required properties
guard let name = expense.name, !name.isEmpty else {
print("Expense has no name")
return
}
// ✅ Provide defaults in Core Data model
// Set default values in .xcdatamodeld editorChecklist
- [ ] Avoid force unwrapping Core Data properties
- [ ] Provide default values in model
- [ ] Use nil coalescing for optional properties
- [ ] Guard for required properties
Relationships & Cascade Rules
Delete Rules
// In .xcdatamodeld editor:
// Cascade: Delete related objects
Group -> Expense (Cascade)
// When group deleted, all expenses deleted
// Nullify: Set relationship to nil
Expense -> Payer (Nullify)
// When user deleted, expense.payer = nil
// Deny: Prevent deletion if relationship exists
Group -> User (Deny)
// Can't delete group if it has users
// No Action: Do nothing (use carefully!)Using Relationships
// ✅ Access relationships safely
if let expenses = group.expenses as? Set<Expense> {
let sortedExpenses = expenses.sorted { $0.date ?? Date() > $1.date ?? Date() }
}
// ✅ Add to relationships
group.addToExpenses(expense)
// ✅ Remove from relationships
group.removeFromExpenses(expense)
// ✅ Fetching with relationship predicates
let request: NSFetchRequest<Expense> = Expense.fetchRequest()
request.predicate = NSPredicate(format: "group.name == %@", "Trip to Paris")Checklist
- [ ] Use appropriate cascade delete rules
- [ ] Access relationships safely
- [ ] Use Core Data's relationship methods
- [ ] Consider relationship cardinality
Performance Optimization
Batch Operations
// ✅ Batch delete
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Expense.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "date < %@", oldDate as NSDate)
let batchDelete = NSBatchDeleteRequest(fetchRequest: fetchRequest)
try? context.execute(batchDelete)
// ✅ Batch update
let batchUpdate = NSBatchUpdateRequest(entityName: "Expense")
batchUpdate.predicate = NSPredicate(format: "isPaid == NO")
batchUpdate.propertiesToUpdate = ["isPaid": true]
try? context.execute(batchUpdate)Faulting
// ✅ Prefetch relationships
let request: NSFetchRequest<Group> = Group.fetchRequest()
request.relationshipKeyPathsForPrefetching = ["expenses", "users"]
let groups = try? context.fetch(request)
// ✅ Return faults for IDs only
request.returnsObjectsAsFaults = trueFetch Performance
// ✅ Fetch only what you need
request.propertiesToFetch = ["name", "amount"]
request.resultType = .dictionaryResultType
// ✅ Use fetch batching
request.fetchBatchSize = 20Checklist
- [ ] Use batch operations for mass updates/deletes
- [ ] Prefetch relationships when needed
- [ ] Limit fetch results
- [ ] Use fetch batching for large datasets
- [ ] Perform heavy operations on background context
CloudKit Integration
Checking Share Status
// ✅ Check if shared
let isShared = CoreDataStack.sharedInstance.isShared(object: group)
// ✅ Check edit permissions
let canEdit = CoreDataStack.sharedInstance.canEdit(object: group)
// ✅ Check ownership
let isOwner = CoreDataStack.sharedInstance.isOwner(object: group)
// ✅ Get share
if let share = CoreDataStack.sharedInstance.getShare(group) {
// Work with CKShare
}Sharing Objects
// ✅ Share a group
if let share = try? CoreDataStack.sharedInstance.shareObject(
group,
to: share
) {
// Present share controller
}
// ✅ Stop sharing
try? CoreDataStack.sharedInstance.stopSharing(group)Checklist
- [ ] Always check
isShared()before operations - [ ] Verify
canEdit()for shared objects - [ ] Handle share conflicts gracefully
- [ ] Test sync scenarios thoroughly
Debugging
Enable Logging
// ✅ SQL debug logging
// In scheme: -com.apple.CoreData.SQLDebug 1
// ✅ CloudKit logging
UserDefaults.standard.set(true, forKey: "EnableCloudKitLogs")
// ✅ Migration logging
// In scheme: -com.apple.CoreData.MigrationDebug 1Common Issues
// Issue: Objects not updating in UI
// Solution: Ensure @FetchRequest or manual refresh
// Issue: Crash on save
// Solution: Check constraints and required attributes
// Issue: Memory issues
// Solution: Use batch fetching and refresh objects
// Issue: CloudKit sync not working
// Solution: Check container configuration and logsTesting
In-Memory Store
// ✅ For unit tests
class CoreDataStack {
static func inMemory() -> CoreDataStack {
let stack = CoreDataStack()
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
stack.persistentContainer.persistentStoreDescriptions = [description]
return stack
}
}Test Data
// ✅ Create test data
func createTestExpense(context: NSManagedObjectContext) -> Expense {
let expense = Expense(context: context)
expense.name = "Test Expense"
expense.amount = 100.0
expense.date = Date()
return expense
}Common Patterns
Singleton Core Data Stack
class CoreDataStack {
static let sharedInstance = CoreDataStack()
private init() { }
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Model")
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Failed to load Core Data: \(error)")
}
}
return container
}()
var managedObjectContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
func save() throws {
let context = managedObjectContext
if context.hasChanges {
try context.save()
}
}
}ViewModel Integration
class ExpenseViewModel: ObservableObject {
@Published var expenses: [Expense] = []
private let context = CoreDataStack.sharedInstance.managedObjectContext
func loadExpenses() {
let request: NSFetchRequest<Expense> = Expense.fetchRequest()
request.sortDescriptors = [
NSSortDescriptor(keyPath: \Expense.date, ascending: false)
]
do {
expenses = try context.fetch(request)
} catch {
print("Failed to fetch: \(error)")
}
}
func addExpense(name: String, amount: Double) {
let expense = Expense(context: context)
expense.name = name
expense.amount = amount
expense.date = Date()
if context.hasChanges {
try? context.save()
}
loadExpenses()
}
}References
Swift Language Patterns
Quick reference for Swift language best practices and idioms.
Optionals Handling
Anti-patterns
// ❌ Force unwrapping
let name = user.userName!
// ❌ Nested if lets (pyramid of doom)
if let user = user {
if let name = user.userName {
if let email = user.email {
// ...
}
}
}
// ❌ Unnecessary optional chaining
if expense?.amount != nil {
let amount = expense!.amount
}Good patterns
// ✅ Nil coalescing
let name = user.userName ?? "Unknown"
// ✅ Guard for early return
guard let user = expense.payer else { return }
// ✅ Combined guard
guard let user = expense.payer,
let name = user.userName,
!name.isEmpty else { return }
// ✅ Optional chaining
let upperName = user?.userName?.uppercased()Checklist
- [ ] Avoid force unwrapping (
!) unless crash is intentional - [ ] Use optional chaining (
?.) and nil coalescing (??) - [ ] Prefer
guard letfor early returns - [ ] Use
if letfor conditional unwrapping - [ ] Avoid pyramid of doom with multiple
if let
Type Safety & Enums
Anti-patterns
// ❌ String constants
if category == "Food" { }
// ❌ Magic numbers
if status == 0 { }
// ❌ If-else chains for enums
if category == .food {
} else if category == .transport {
} else if category == .entertainment {
}Good patterns
// ✅ Enum with raw values
enum ExpenseCategory: String, CaseIterable {
case food = "Food"
case transport = "Transport"
case entertainment = "Entertainment"
}
// ✅ Switch with exhaustive checking
switch category {
case .food:
// ...
case .transport:
// ...
case .entertainment:
// ...
}
// ✅ Associated values
enum NetworkResult {
case success(data: Data)
case failure(error: Error)
}Checklist
- [ ] Use enums instead of string/int constants
- [ ] Avoid stringly-typed code
- [ ] Use associated values for related data
- [ ] Prefer
switchover multipleiffor enums - [ ] Use
CaseIterablewhen appropriate
Collections & Sequences
Anti-patterns
// ❌ Manual loop for transformation
var names: [String] = []
for user in users {
names.append(user.name)
}
// ❌ Inefficient filtering
let found = users.filter { $0.id == userId }.first
// ❌ Force array access
let first = array[0]
// ❌ Count comparison
if array.count == 0 { }Good patterns
// ✅ Map for transformation
let names = users.map { $0.name }
// ✅ CompactMap for optional unwrapping
let names = users.compactMap { $0.userName }
// ✅ First(where:) for finding
let found = users.first { $0.id == userId }
// ✅ Safe array access
guard let first = array.first else { return }
// ✅ isEmpty
if array.isEmpty { }Checklist
- [ ] Use
map,filter,reduceinstead of loops where appropriate - [ ] Prefer
compactMapovermap+ filter nil - [ ] Use
first(where:)instead offilter().first - [ ] Avoid force-accessing arrays with
[0] - [ ] Use
isEmptyinstead ofcount == 0
Error Handling
Anti-patterns
// ❌ Silent failure
try? context.save()
// ❌ Generic error handling
do {
try something()
} catch {
print("Error")
}
// ❌ Boolean success flags
func saveData() -> Bool {
// ...
}Good patterns
// ✅ Custom errors
enum DataError: LocalizedError {
case saveFailed
case invalidData(reason: String)
var errorDescription: String? {
switch self {
case .saveFailed:
return "Failed to save data"
case .invalidData(let reason):
return "Invalid data: \(reason)"
}
}
}
// ✅ Proper error propagation
func saveExpense() throws {
guard !name.isEmpty else {
throw DataError.invalidData(reason: "Name is empty")
}
try context.save()
}
// ✅ Result type
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
// ...
}Checklist
- [ ] Use Swift error handling (
throws,try,catch) - [ ] Define custom error types
- [ ] Avoid swallowing errors silently
- [ ] Use
Resulttype for async operations - [ ] Provide meaningful error messages
Naming Conventions
Anti-patterns
// ❌ Unclear names
var e: Expense
func calc() { }
var flg: Bool
// ❌ Redundant names
func calculateCalculation() { }
var nameString: String
// ❌ Poor boolean names
var active: Bool
var valid: BoolGood patterns
// ✅ Clear names
var currentExpense: Expense
func calculateTotal() { }
var isActive: Bool
// ✅ Descriptive methods
func saveExpense(named: String, amount: Double)
func deleteExpense(_ expense: Expense)
// ✅ Boolean clarity
var isActive: Bool
var hasValidData: Bool
var shouldShowAlert: BoolChecklist
- [ ] Clear, descriptive names
- [ ] Boolean names start with
is,has,should - [ ] Method names describe action
- [ ] Avoid abbreviations
- [ ] Follow Swift API Design Guidelines
References
SwiftUI Patterns
Quick reference for SwiftUI best practices and common patterns.
State Management
Anti-patterns
// ❌ ObservedObject for view-owned
@ObservedObject var viewModel = MyViewModel()
// ❌ State for complex objects
@State var viewModel = MyViewModel()
// ❌ Multiple sources of truth
@State var name = ""
var expense: ExpenseGood patterns
// ✅ StateObject for view-owned
@StateObject private var viewModel = MyViewModel()
// ✅ ObservedObject for passed
@ObservedObject var viewModel: MyViewModel
// ✅ Binding for child-parent connection
@Binding var isPresented: Bool
// ✅ Single source of truth
@State private var name = ""
// OR
@ObservedObject var expense: ExpenseWhen to Use Each Property Wrapper
| Wrapper | Use Case | Ownership |
|---|---|---|
@State | Simple value types (String, Int, Bool) | View owns |
@StateObject | ObservableObject (ViewModels) | View owns |
@ObservedObject | ObservableObject passed from parent | Parent owns |
@EnvironmentObject | Shared data across view hierarchy | App/Scene owns |
@Binding | Two-way connection to parent state | Parent owns |
Checklist
- [ ] Use
@Statefor view-local state - [ ] Use
@StateObjectfor view-owned objects - [ ] Use
@ObservedObjectfor passed objects - [ ] Use
@EnvironmentObjectfor shared data - [ ] Use
@Bindingfor two-way connections - [ ] Avoid
@ObservedObjectwhen should be@StateObject
View Composition
Anti-patterns
// ❌ Massive view body
var body: some View {
VStack {
// 200 lines of code
}
}
// ❌ Complex logic in view
var body: some View {
let filtered = items.filter { $0.isActive }
let sorted = filtered.sorted { $0.date > $1.date }
let grouped = Dictionary(grouping: sorted) { $0.category }
// ...
}Good patterns
// ✅ Extracted subviews
var body: some View {
VStack {
headerView
contentView
footerView
}
}
private var headerView: some View { /* ... */ }
// ✅ Separate view components
struct ExpenseRow: View {
let expense: Expense
var body: some View { /* ... */ }
}
// ✅ Logic in ViewModel or computed property
var sortedExpenses: [Expense] {
viewModel.getSortedExpenses()
}Checklist
- [ ] Break large views into smaller components
- [ ] Use
@ViewBuilderfor conditional views - [ ] Avoid heavy computation in
body - [ ] Extract subviews for reusability
- [ ] Keep view structs focused (Single Responsibility)
Performance
Anti-patterns
// ❌ Creating objects in body
var body: some View {
let formatter = DateFormatter() // Created every render!
Text(formatter.string(from: date))
}
// ❌ Heavy computation without caching
var body: some View {
let total = expenses.reduce(0) { $0 + $1.amount }
Text("Total: \(total)")
}Good patterns
// ✅ Static/cached formatters
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
// ✅ Computed in ViewModel
var totalAmount: Double {
viewModel.totalAmount
}
// ✅ Use @State for computed values
@State private var totalAmount: Double = 0
var body: some View {
Text("Total: \(totalAmount)")
.onAppear {
totalAmount = viewModel.calculateTotal()
}
}Checklist
- [ ] Use
Equatablefor complex view data - [ ] Avoid creating ViewModels in
body - [ ] Use
id()modifier carefully - [ ] Minimize
@Statechanges - [ ] Avoid unnecessary view updates
- [ ] Cache expensive formatters and computations
Common SwiftUI Patterns
Conditional Views
// ✅ Using @ViewBuilder
@ViewBuilder
var content: some View {
if isLoading {
ProgressView()
} else if hasError {
ErrorView()
} else {
ContentView()
}
}List Performance
// ✅ Provide stable IDs
List(expenses, id: \.id) { expense in
ExpenseRow(expense: expense)
}
// ✅ For dynamic lists
List {
ForEach(expenses) { expense in
ExpenseRow(expense: expense)
}
.onDelete(perform: deleteExpenses)
}Form Handling
// ✅ Clean form structure
Form {
Section("Details") {
TextField("Name", text: $name)
TextField("Amount", value: $amount, format: .currency(code: "USD"))
}
Section("Options") {
Toggle("Is Paid", isOn: $isPaid)
Picker("Category", selection: $category) {
ForEach(ExpenseCategory.allCases) { category in
Text(category.rawValue).tag(category)
}
}
}
}Navigation
// ✅ NavigationStack (iOS 16+)
NavigationStack {
List(items) { item in
NavigationLink(value: item) {
ItemRow(item: item)
}
}
.navigationDestination(for: Item.self) { item in
ItemDetailView(item: item)
}
}
// ✅ Sheet presentation
.sheet(isPresented: $showingSheet) {
AddItemView()
}
// ✅ Alert presentation
.alert("Delete Item?", isPresented: $showingAlert) {
Button("Delete", role: .destructive) {
deleteItem()
}
Button("Cancel", role: .cancel) { }
}Memory Management in SwiftUI
Closures in ViewModels
// ✅ Weak self in escaping closures
func loadData() {
dataService.fetch { [weak self] data in
guard let self = self else { return }
self.process(data)
}
}Task Cancellation
// ✅ Proper task lifecycle
.task {
await viewModel.loadData()
}
// Automatically cancelled when view disappears
// ✅ Manual task management
@State private var dataTask: Task<Void, Never>?
var body: some View {
ContentView()
.onAppear {
dataTask = Task {
await loadData()
}
}
.onDisappear {
dataTask?.cancel()
}
}