A lightweight, zero-dependency, type-safe dependency injection framework designed for modern Swift projects.
Inspired by Swinject and swift-dependencies, Service leverages modern Swift features for simple, robust dependency injection. Extremely easy to learn with familiar register/resolve patterns, elegant dependency injection through property wrappers.
- 🚀 Modern Swift: Uses property wrappers, TaskLocal, and concurrency primitives, fully leverages modern Swift features
- 🎯 Simple API, Ready to Use: Use
@Serviceproperty wrapper, no manual dependency passing needed, cleaner code - 📦 Zero Dependencies, Lightweight: No third-party dependencies, adds no burden to your project, perfect for any Swift project
- 🔒 Type-Safe, Compile-Time Checked: Leverages Swift's type system to catch errors at compile time
- ⚡ Thread-Safe, Concurrency-Friendly: Built-in thread safety guarantees, perfect support for Swift 6 concurrency model
- 🌍 Environment Isolation, Test-Friendly: Task-level environment switching based on TaskLocal, easily swap dependencies in tests
- 🎨 MainActor Support: Dedicated
@MainServiceAPI for SwiftUI view models and UI components - 🔍 Automatic Circular Dependency Detection: Runtime detection of circular dependencies with clear error messages
- 🧩 Modular Assembly: Organize service registrations through ServiceAssembly pattern for clearer code structure
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/nslogmeng/swift-service", .upToNextMajor(from: "1.0.0"))
],
targets: [
.target(
name: "MyProject",
dependencies: [
.product(name: "Service", package: "swift-service"),
]
)
]Get started with Service in just three steps:
import Service
// Register a service (supports both protocols and concrete types)
ServiceEnv.current.register(DatabaseProtocol.self) {
DatabaseService(connectionString: "sqlite://app.db")
}Use the @Service property wrapper to automatically resolve dependencies:
struct UserRepository {
@Service
var database: DatabaseProtocol
func fetchUser(id: String) -> User? {
return database.findUser(id: id)
}
}let repository = UserRepository()
let user = repository.fetchUser(id: "123")
// database is automatically injected, no manual passing needed!// Register MainActor service
ServiceEnv.current.registerMain(UserViewModel.self) {
UserViewModel()
}
// Use @MainService in your views
struct UserView: View {
@MainService
var viewModel: UserViewModel
var body: some View {
Text(viewModel.userName)
}
}// Switch to test environment in tests
await ServiceEnv.$current.withValue(.test) {
// Register mock services for testing
ServiceEnv.current.register(DatabaseProtocol.self) {
MockDatabase()
}
// All service resolutions use test environment
let repository = UserRepository()
// Test with mock database...
}For comprehensive documentation, tutorials, and examples, see the Service Documentation.
- Getting Started - Quick setup guide
- Basic Usage - Core patterns and examples
- Service Environments - Managing different service configurations
- MainActor Services - Working with UI components
- Service Assembly - Organizing service registrations
- Circular Dependencies - Understanding and avoiding circular dependencies
- Resetting Services - Clearing caches and resetting service environments
- Real-World Examples - Practical use cases
- Understanding Service - Deep dive into architecture
- Concurrency Model - Understanding Service's concurrency model
If you're familiar with traditional dependency injection patterns (like Swinject), Service will feel very familiar. With property wrappers, you don't even need to manually pass dependencies:
// Traditional way: need to manually pass dependencies
class UserService {
init(database: DatabaseProtocol, logger: LoggerProtocol) { ... }
}
let service = UserService(database: db, logger: logger)
// Service way: automatic injection, cleaner code
class UserService {
@Service var database: DatabaseProtocol
@Service var logger: LoggerProtocol
}
let service = UserService() // Dependencies automatically injected!- Swift 6 Concurrency Model: Perfect support for
Sendableand@MainActor, with dedicated APIs for UI services - TaskLocal Environment Isolation: Task-based environment switching, no need to modify global state in tests
- Property Wrappers: Leverages modern Swift features for elegant dependency injection
- Compile-Time Type Checking: Leverages Swift's type system to catch errors at compile time
- Thread Safety Guarantees: Built-in locking mechanism, supports concurrent access
- Circular Dependency Detection: Automatic runtime detection and reporting of circular dependencies
- Zero Dependencies: No third-party dependencies, won't add complexity to your project
- Minimal Runtime Cost: Efficient implementation with minimal impact on app performance
- Wide Applicability: Perfect for SwiftUI apps, server-side Swift, command-line tools, and any Swift project
- Multiple Registration Methods: Supports factory functions, direct instances, and ServiceKey protocol
- Modular Assembly: Organize service registrations through ServiceAssembly for clearer code structure
- Environment Isolation: Production, development, and test environments are completely isolated
This project is licensed under the MIT License. See the LICENSE file for details.
