Copilot, Claude, Cursor, and others all read from ~/.agents/. The npx skills CLI handles fan-out to tool-specific directories.
4.8 KiB
| name | description | globs | |
|---|---|---|---|
| Swift Protocol-Oriented Programming | Protocol-first architecture patterns for reusability and testability |
|
Protocol-Oriented Programming (POP)
Protocol-first architecture is a priority. When designing new features, always think about protocols and composition before concrete implementations.
When Architecting New Code
-
Start with the protocol - Before writing a concrete type, ask "What capability am I defining?" and express it as a protocol.
-
Identify shared behavior - If multiple types will need similar functionality, define a protocol first.
-
Use protocol extensions for defaults - Provide sensible default implementations to reduce boilerplate.
-
Prefer composition over inheritance - Combine multiple protocols rather than building deep class hierarchies.
When Reviewing Existing Code
-
Look for duplicated patterns - Similar logic across files is a candidate for protocol extraction.
-
Identify common interfaces - Types that expose similar properties/methods should conform to a shared protocol.
-
Check before implementing - Search for existing protocols that could be adopted or extended.
-
Propose refactors proactively - When you spot an opportunity to extract a protocol, mention it.
Protocol Design Guidelines
Naming Conventions
Use capability-based suffixes:
-able:Persistable,Shareable,Validatable-ing:DataProviding,ErrorHandling,Loading-Provider:ContentProvider,DataProvider-Delegate:NavigationDelegate,FormDelegate
Keep Protocols Focused
Each protocol should represent one capability (Interface Segregation Principle):
// GOOD - Focused protocols
protocol Identifiable {
var id: UUID { get }
}
protocol Nameable {
var displayName: String { get }
}
protocol Timestamped {
var createdAt: Date { get }
var updatedAt: Date { get }
}
// Compose as needed
struct User: Identifiable, Nameable, Timestamped { ... }
// BAD - Kitchen sink protocol
protocol Entity {
var id: UUID { get }
var displayName: String { get }
var createdAt: Date { get }
var updatedAt: Date { get }
func save() async throws
func delete() async throws
func validate() -> Bool
}
Associated Types
Use sparingly. Prefer concrete types or generics at the call site when possible:
// Prefer this for simple cases
protocol DataFetching {
func fetch<T: Decodable>(from url: URL) async throws -> T
}
// Use associated types when the type is fundamental to the protocol
protocol Repository {
associatedtype Entity
func fetch(id: UUID) async throws -> Entity?
func save(_ entity: Entity) async throws
}
Value vs Reference Semantics
Constrain to AnyObject only when reference semantics are required:
// Default - allows structs and classes
protocol Configurable {
mutating func configure(with options: Options)
}
// When you need reference semantics (delegates, observers)
protocol NavigationDelegate: AnyObject {
func didNavigate(to destination: Destination)
}
Protocol Extensions
Provide default implementations for common behavior:
protocol Validatable {
var validationErrors: [String] { get }
var isValid: Bool { get }
}
extension Validatable {
var isValid: Bool {
validationErrors.isEmpty
}
}
Dependency Injection with Protocols
Define protocols for services to enable testing:
protocol NetworkServiceProtocol {
func fetch<T: Decodable>(from url: URL) async throws -> T
}
// Production implementation
final class NetworkService: NetworkServiceProtocol { ... }
// Test mock
final class MockNetworkService: NetworkServiceProtocol { ... }
Benefits
- Reusability - Shared protocols work across features
- Testability - Mock types can conform to protocols for unit testing
- Flexibility - New features can adopt existing protocols immediately
- Maintainability - Fix a bug in a protocol extension, fix it everywhere
- Discoverability - Protocols document the expected interface clearly
Common Patterns
Repository Pattern
protocol Repository {
associatedtype Entity: Identifiable
func fetch(id: Entity.ID) async throws -> Entity?
func fetchAll() async throws -> [Entity]
func save(_ entity: Entity) async throws
func delete(_ entity: Entity) async throws
}
Service Pattern
protocol AuthServiceProtocol {
var isAuthenticated: Bool { get }
func signIn(email: String, password: String) async throws
func signOut() async throws
}
Coordinator/Navigation Pattern
protocol NavigationCoordinating: AnyObject {
func navigate(to destination: Destination)
func dismiss()
func presentSheet(_ sheet: SheetType)
}