swift-conventions/SKILL.md
Swift coding conventions and best practices for modern Swift development. Use when writing, reviewing, or refactoring Swift code to ensure consistency with naming conventions, access control, async/await patterns, and SwiftUI/framework best practices.
npx skillsauth add heikopanjas/agent-skills swift-conventionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use PascalCase for all type names:
public class ProcessManager { }
public struct Location { }
public enum ProcessQuality { }
public protocol ProcessController { }
Use camelCase for properties, variables, and instance names:
let locationManager = LocationManager()
var subscriptions: [ProcessSubscription] = []
private let updateInterval: TimeInterval = 60
Use camelCase with descriptive action verbs:
func refreshData(for location: Location) async throws -> ProcessSensor?
func updateLocation(location: Location)
private func significantLocationChange(previous: Location?, current: Location) -> Bool
Use camelCase for constant properties, UPPER_SNAKE_CASE for macro-like constants:
public static let houseOfWorldCultures = Location(latitude: 52.51889, longitude: 13.36528)
private let updateInterval: TimeInterval = 60
Use PascalCase for enum name, camelCase for cases:
public enum ProcessQuality {
case good
case uncertain
case bad
}
public enum ProcessSelector: Hashable {
case weather(Weather)
case forecast(Forecast)
}
Opening brace on same line, closing brace on new line:
public class ProcessManager {
func updateSubscriptions() {
for subscription in subscriptions {
subscription.update(timeout: updateInterval)
}
}
}
Target 120 characters maximum per line. Break at logical points:
public func dataWithRetry(
from url: URL, retryCount: Int = 3, retryInterval: TimeInterval = 1.0
) async throws -> (Data, URLResponse) {
// Implementation
}
Always explicit: Mark APIs as public intentionally; avoid relying on default internal.
public class ProcessManager: Identifiable {
public let id = UUID()
public static let shared = ProcessManager()
private let locationManager = LocationManager()
private var location: Location?
}
Order of access levels (most to least restrictive):
private - Only within current declarationfileprivate - Only within same source fileinternal - Within module (default)public - Visible to consumersopen - Visible and subclassable (use rarely)public class ProcessManager: Identifiable, LocationManagerDelegate {
public let id = UUID()
public static let shared = ProcessManager()
private init() { }
}
public struct Location: Equatable, Hashable {
public let latitude: Double
public let longitude: Double
public init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
public enum ProcessQuality {
case good
case uncertain
case bad
case unknown
}
public enum Weather: Int, CaseIterable {
case temperature = 0
case apparentTemperature = 1
}
public enum ProcessSelector: Hashable {
case weather(Weather)
case forecast(Forecast)
case covid(Covid)
}
public protocol ProcessController {
func refreshData(for location: Location) async throws -> ProcessSensor?
}
public protocol LocationManagerDelegate: Identifiable where ID == UUID {
func locationManager(didUpdateLocation location: Location)
}
public let id = UUID()
private let locationManager = LocationManager()
private var location: Location?
private let updateInterval: TimeInterval = 60
public var coordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2D(latitude: self.latitude, longitude: self.longitude)
}
var isReady: Bool {
return location != nil && subscriptions.isEmpty == false
}
var location: Location? {
willSet {
print("About to set location")
}
didSet {
if location != oldValue {
refreshSubscriptions()
}
}
}
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
return formatter
}()
public func refreshData(for location: Location) async throws -> ProcessSensor? {
let weather = try await WeatherService.shared.weather(for: clLocation)
return ProcessSensor(name: "", location: location, measurements: [:], timestamp: Date.now)
}
Descriptive external labels; omit with underscore when appropriate:
func updateLocation(location: Location)
func add(subscriber: any ProcessSubscriber, timeout: TimeInterval)
func process(_ data: Data) -> Result
Place at end of parameter list:
public func dataWithRetry(
from url: URL,
retryCount: Int = 3,
retryInterval: TimeInterval = 1.0
) async throws -> (Data, URLResponse) {
// Implementation
}
Designated initializer with convenience initializers:
public init(value: Measurement<T>, customData: [String: Any]?, quality: ProcessQuality, timestamp: Date) {
self.value = value
self.customData = customData
self.quality = quality
self.timestamp = timestamp
}
public convenience init(value: Measurement<T>, quality: ProcessQuality) {
self.init(value: value, customData: nil, quality: quality, timestamp: Date.now)
}
if location != nil {
refreshSubscriptions()
}
if let location = self.location {
delegate.locationManager(didUpdateLocation: location)
}
guard let location = self.location else {
return
}
guard let data = data,
let response = response as? HTTPURLResponse,
(200...299).contains(response.statusCode) else {
throw NetworkError.invalidResponse
}
for subscription in subscriptions {
subscription.update(timeout: updateInterval)
}
for (index, item) in items.enumerated() {
print("\(index): \(item)")
}
for subscription in subscriptions where subscription.isPending() {
subscription.reset()
}
switch quality {
case .good:
return "✓"
case .uncertain:
return "~"
case .bad:
return "✗"
case .unknown:
return "?"
}
switch connectionType {
case .wifi, .ethernet:
return true
case .cellular:
return false
}
enum NetworkError: Error {
case invalidResponse
case serverError(statusCode: Int)
case noData
}
public func refreshData(for location: Location) async throws -> ProcessSensor? {
let weather = try await WeatherService.shared.weather(for: clLocation)
return sensor
}
do {
let (data, response) = try await self.data(from: url)
return (data, response)
} catch NetworkError.invalidResponse {
return nil
} catch {
print("Error: \(error)")
return nil
}
if let placemark = try? await geocoder.reverseGeocodeLocation(location).first {
// Use placemark
}
let config = try! Configuration.load() // Only if guaranteed to succeed
public func refreshData(for location: Location) async throws -> ProcessSensor? {
let weather = try await WeatherService.shared.weather(for: clLocation)
let placemark = await LocationManager.reverseGeocodeLocation(location: location)
return ProcessSensor(/* ... */)
}
Task {
await delegate.refreshData(location: location)
}
Task {
do {
let result = try await fetchData()
process(result)
} catch {
print("Error: \(error)")
}
}
actor NetworkManager {
private var isConnected = true
func updateConnectionStatus(_ status: Bool) {
self.isConnected = status
}
}
public class UnitRadiation: Dimension, @unchecked Sendable {
public static let sieverts = UnitRadiation(
symbol: "Sv/h",
converter: UnitConverterLinear(coefficient: 1.0)
)
}
public class ProcessManager: Identifiable, LocationManagerDelegate {
// Implementation
}
extension ProcessManager: CustomStringConvertible {
public var description: String {
return "ProcessManager with \(subscriptions.count) subscriptions"
}
}
Use MARK comments to organize extensions by purpose:
// MARK: - LocationManagerDelegate
extension ProcessManager: LocationManagerDelegate {
public func locationManager(didUpdateLocation location: Location) {
// Implementation
}
}
// MARK: - Subscription Management
extension ProcessManager {
public func add(subscriber: any ProcessSubscriber, timeout: TimeInterval) {
// Implementation
}
}
public struct ProcessValue<T: Dimension>: Identifiable {
public let id = UUID()
public let value: Measurement<T>
public let quality: ProcessQuality
}
func measure<T: Dimension>(_ value: Double, unit: T) -> Measurement<T> {
return Measurement(value: value, unit: unit)
}
private var subscribers: [UUID: any ProcessSubscriber] = [:]
public func add(subscriber: any ProcessSubscriber, timeout: TimeInterval) {
subscribers[subscriber.id] = subscriber
}
Comment the "why", not the "what":
// Check if device is connected before attempting network request
guard ReachabilityManager.shared.isConnected else {
throw URLError(.notConnectedToInternet)
}
Use DocC-style documentation for public APIs:
/// A simple and fast logging facility with support for different log levels.
public class Trace {
/// Creates a new Logger instance
/// - Parameters:
/// - minimumLevel: Minimum level of logs to display
/// - showColors: Whether to use ANSI colors
/// - dateFormat: Format string for timestamps
public init(
minimumLevel: Level = .debug,
showColors: Bool = true,
dateFormat: String = "yyyy-MM-dd HH:mm:ss.SSS"
) {
// Implementation
}
}
public class WeatherController {
// MARK: - Properties
private let service = WeatherService.shared
// MARK: - Initialization
public init() { }
// MARK: - Public Methods
public func refreshData(for location: Location) async throws -> ProcessSensor? {
// Implementation
}
// MARK: - Private Helpers
private func processWeatherData(_ data: WeatherData) -> ProcessSensor {
// Implementation
}
}
Separate logical sections with blank lines:
public class ProcessManager {
public let id = UUID()
public static let shared = ProcessManager()
private let locationManager = LocationManager()
private var location: Location?
public func refreshSubscriptions() {
// Implementation
}
}
[1, 2, 3]a + bvar dict: [String: Int] = [:]for i in 0..<count, 0...10Remove all trailing whitespace at end of lines.
if let location = self.location {
process(location)
}
guard let location = self.location else {
return
}
let count = subscribers[id]?.subscriptions.count
let value = optionalValue ?? defaultValue
let manager = ProcessManager.shared
let id = UUID()
let values = [1, 2, 3]
let timeout: TimeInterval = 60 // Explicit when needed
Timer.scheduledTimer(withTimeInterval: updateInterval, repeats: true) { _ in
self.updateSubscriptions()
}
items.map { $0.value * 2 }
UIView.animate(withDuration: 0.3) {
view.alpha = 0
} completion: { _ in
view.removeFromSuperview()
}
var subscriptions: [ProcessSubscription] = []
var measurements: [ProcessSelector: [ProcessValue<Dimension>]] = [:]
let uniqueIds: Set<UUID> = []
@Published var measurements: [ProcessValue<Dimension>] = []
@AppStorage("refreshInterval") var refreshInterval: TimeInterval = 60
public access control@unchecked Sendablepublic class WeatherController: ProcessController {
public func refreshData(for location: Location) async throws -> ProcessSensor? {
// Fetch data from service
// Process into ProcessSensor
// Return structured data
}
}
public class CovidService {
static func fetchDistricts(for location: Location, radius: Double) async throws -> Data? {
// Perform HTTP request
// Return raw data
}
}
public class WeatherTransformer: ProcessTransformer {
override public func renderCurrent(measurements: [ProcessSelector: [ProcessValue<Dimension>]])
-> [ProcessSelector: ProcessValue<Dimension>] {
// Transform measurements into current values
}
}
Service (HTTP) → Controller (Parse) → Transformer (Process) → Consumer (Display)
testing
Record recent changes and decisions in the AGENTS.md file. Use when making project decisions, choosing technologies, establishing conventions, or completing significant changes that should be tracked in the project history.
development
Determine whether a version bump is required after code changes and apply semantic versioning (SemVer). Use when making code changes, fixing bugs, adding features, or introducing breaking changes to a project that uses SemVer.
development
Rust coding conventions and best practices for idiomatic Rust development. Use when writing, reviewing, or refactoring Rust code to ensure consistency with error handling, RAII principles, naming conventions, and Rust edition best practices.
development
Commit specific staged changes to a git repository using conventional commits format. Use when the user asks to commit, stage, or check in particular files or changes.