Skip to content

Fix Patterns

Concrete before/after examples for each fix category. Reference this when applying approved changes to ensure consistency.

Before (project.pbxproj):

DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";

After:

DEBUG_INFORMATION_FORMAT = dwarf;

Before:

SWIFT_COMPILATION_MODE = wholemodule;

After:

SWIFT_COMPILATION_MODE = singlefile;

Before (setting absent or):

COMPILATION_CACHE_ENABLE_CACHING = NO;

After:

COMPILATION_CACHE_ENABLE_CACHING = YES;

Before (setting absent):

After:

EAGER_LINKING = YES;

Before:

Terminal window
# Upload dSYMs to crash reporter
./scripts/upload-dsyms.sh

After:

Terminal window
# Upload dSYMs to crash reporter
[[ "$CONFIGURATION" != "Release" ]] && exit 0
./scripts/upload-dsyms.sh

When a script has no declared inputs or outputs, Xcode runs it on every build. Declare them in the build phase or use .xcfilelist files for long lists.

Before (in Xcode build phase):

Input Files: (none)
Output Files: (none)

After:

Input Files:
$(SRCROOT)/scripts/generate-constants.sh
$(SRCROOT)/Config/constants.json
Output Files:
$(DERIVED_FILE_DIR)/GeneratedConstants.swift

Before:

let result = items.map { $0.value }.filter { $0 > threshold }.reduce(0, +)

After:

let mapped: [Double] = items.map { $0.value }
let filtered: [Double] = mapped.filter { $0 > threshold }
let result: Double = filtered.reduce(0, +)

Before:

let config = try JSONDecoder().decode(
AppConfig.self,
from: Data(contentsOf: Bundle.main.url(forResource: "config", withExtension: "json")!)
)

After:

let configURL: URL = Bundle.main.url(forResource: "config", withExtension: "json")!
let configData: Data = try Data(contentsOf: configURL)
let config: AppConfig = try JSONDecoder().decode(AppConfig.self, from: configData)

Before:

class NetworkService {
func fetchData() async throws -> Data { ... }
}

After:

final class NetworkService {
func fetchData() async throws -> Data { ... }
}

Only apply when the class is not subclassed anywhere in the project. Search for : NetworkService and class ... : NetworkService before marking final.

Before:

class ViewModel {
var internalState: State = .idle
func processQueue() { ... }
}

After:

class ViewModel {
private var internalState: State = .idle
private func processQueue() { ... }
}

Apply private when the symbol is only used within the same declaration. Apply fileprivate when used within the same file but outside the declaration.

Before:

struct ContentView: View {
var body: some View {
VStack {
HStack {
Image(systemName: "person")
Text(user.name)
Spacer()
Button("Edit") { showEdit = true }
}
List(items) { item in
HStack {
Text(item.title)
Spacer()
Text(item.subtitle)
.foregroundStyle(.secondary)
}
}
}
}
}

After:

struct ContentView: View {
var body: some View {
VStack {
UserHeaderView(user: user, showEdit: $showEdit)
ItemListView(items: items)
}
}
}
struct UserHeaderView: View {
let user: User
@Binding var showEdit: Bool
var body: some View {
HStack {
Image(systemName: "person")
Text(user.name)
Spacer()
Button("Edit") { showEdit = true }
}
}
}
struct ItemListView: View {
let items: [Item]
var body: some View {
List(items) { item in
ItemRowView(item: item)
}
}
}
struct ItemRowView: View {
let item: Item
var body: some View {
HStack {
Text(item.title)
Spacer()
Text(item.subtitle)
.foregroundStyle(.secondary)
}
}
}

Before:

let handler = { value in
guard let result = try? process(value) else { return nil }
return result.transformed()
}

After:

let handler: (InputType) -> OutputType? = { (value: InputType) -> OutputType? in
guard let result = try? process(value) else { return nil }
return result.transformed()
}

Extract Shared Types to Lower-Layer Module

Section titled “Extract Shared Types to Lower-Layer Module”

Before (Package.swift):

.target(name: "FeatureA", dependencies: ["FeatureB"]),
.target(name: "FeatureB", dependencies: ["FeatureA"]),

After:

.target(name: "SharedContracts", dependencies: []),
.target(name: "FeatureA", dependencies: ["SharedContracts"]),
.target(name: "FeatureB", dependencies: ["SharedContracts"]),

Move the shared protocols and types into SharedContracts so both features depend downward instead of on each other.

Before:

.target(name: "Networking", dependencies: ["Models"]),
.target(name: "FeatureA", dependencies: ["Networking"]),
.target(name: "FeatureB", dependencies: ["Networking"]),

After:

.target(name: "NetworkingInterface", dependencies: []),
.target(name: "Networking", dependencies: ["NetworkingInterface", "Models"]),
.target(name: "FeatureA", dependencies: ["NetworkingInterface"]),
.target(name: "FeatureB", dependencies: ["NetworkingInterface"]),

Feature modules compile against the lightweight interface without waiting for the full implementation to build.