Copilot, Claude, Cursor, and others all read from ~/.agents/. The npx skills CLI handles fan-out to tool-specific directories.
5.2 KiB
5.2 KiB
| name | description | globs | |
|---|---|---|---|
| Modern Swift | Modern Swift language patterns, concurrency, and API usage |
|
Modern Swift Patterns
Use modern Swift language features and avoid deprecated patterns.
Concurrency
Always Mark Observable Classes with @MainActor
@Observable
@MainActor
final class FeatureStore {
// All properties and methods run on main actor
}
Use Modern Concurrency (No GCD)
// BAD - Old GCD patterns
DispatchQueue.main.async {
self.updateUI()
}
DispatchQueue.global().async {
let result = self.heavyWork()
DispatchQueue.main.async {
self.handle(result)
}
}
// GOOD - Modern concurrency
await MainActor.run {
updateUI()
}
Task.detached {
let result = await heavyWork()
await MainActor.run {
handle(result)
}
}
Use Task.sleep(for:) Not nanoseconds
// BAD
try await Task.sleep(nanoseconds: 1_000_000_000)
// GOOD
try await Task.sleep(for: .seconds(1))
try await Task.sleep(for: .milliseconds(500))
Strict Concurrency Compliance
// Ensure data crossing actor boundaries is Sendable
struct Item: Sendable {
let id: UUID
let name: String
}
// Use @unchecked Sendable only when you've manually verified thread safety
final class Cache: @unchecked Sendable {
private let lock = NSLock()
private var storage: [String: Any] = [:]
}
Foundation APIs
Use Modern URL APIs
// BAD - Deprecated patterns
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let file = docs.appendingPathComponent("data.json")
// GOOD - Modern APIs
let docs = URL.documentsDirectory
let file = docs.appending(path: "data.json")
Use Modern Date Formatting
// BAD - DateFormatter
let formatter = DateFormatter()
formatter.dateStyle = .medium
let string = formatter.string(from: date)
// GOOD - format method
let string = date.formatted(.dateTime.month().day().year())
let relative = date.formatted(.relative(presentation: .named))
Use Modern Number Formatting
// BAD - C-style or NumberFormatter
let string = String(format: "%.2f", price)
// GOOD - format method
let string = price.formatted(.currency(code: "USD"))
let percent = ratio.formatted(.percent.precision(.fractionLength(1)))
String Handling
Use localizedStandardContains for User Search
// BAD - Case-sensitive or manual lowercasing
items.filter { $0.name.lowercased().contains(query.lowercased()) }
// GOOD - Locale-aware, case/diacritic insensitive
items.filter { $0.name.localizedStandardContains(query) }
String Interpolation Over Concatenation
// BAD
let message = "Hello, " + user.name + "!"
// GOOD
let message = "Hello, \(user.name)!"
Type Safety
Avoid Force Unwraps
// BAD
let value = dictionary["key"]!
let url = URL(string: urlString)!
// GOOD - Guard or optional binding
guard let value = dictionary["key"] else {
throw ValidationError.missingKey
}
guard let url = URL(string: urlString) else {
throw NetworkError.invalidURL
}
Avoid Force Casts
// BAD
let view = cell as! CustomCell
// GOOD
guard let view = cell as? CustomCell else {
assertionFailure("Expected CustomCell")
return
}
Avoid Force Try
// BAD
let data = try! encoder.encode(object)
// GOOD - Handle the error
do {
let data = try encoder.encode(object)
} catch {
logger.error("Encoding failed: \(error)")
}
Prefer Swift-Native Patterns
Static Member Lookup
// BAD - Struct instances
.clipShape(RoundedRectangle(cornerRadius: 8))
// GOOD - Static member lookup
.clipShape(.rect(cornerRadius: 8))
Result Builders Over Imperative Construction
// BAD - Imperative array building
var views: [AnyView] = []
if showHeader {
views.append(AnyView(HeaderView()))
}
views.append(AnyView(ContentView()))
// GOOD - ViewBuilder
@ViewBuilder
var content: some View {
if showHeader {
HeaderView()
}
ContentView()
}
KeyPath Expressions
// BAD
items.map { $0.name }
items.sorted { $0.date < $1.date }
// GOOD
items.map(\.name)
items.sorted(using: KeyPathComparator(\.date))
Collections
Use First(where:) Over Filter().first
// BAD - Creates intermediate array
let item = items.filter { $0.id == targetId }.first
// GOOD - Short-circuits
let item = items.first { $0.id == targetId }
Use Contains(where:) Over Filter().isEmpty
// BAD
let hasActive = !items.filter { $0.isActive }.isEmpty
// GOOD
let hasActive = items.contains { $0.isActive }
Use Lazy for Chained Operations
// Process large collections efficiently
let result = largeArray
.lazy
.filter { $0.isValid }
.map { $0.transformed }
.prefix(10)
.map(Array.init) // Materialize only when needed
Error Handling
Prefer Typed Throws (Swift 6)
// Swift 6 - Typed throws
enum NetworkError: Error {
case notFound
case unauthorized
case serverError(Int)
}
func fetch() throws(NetworkError) -> Data {
// ...
}
Use Result for Async Callbacks (When Not Using async/await)
func fetch(completion: @escaping (Result<Data, NetworkError>) -> Void) {
// ...
}