Frequently Asked Questions (FAQ)
Common questions and answers about WeaveDI.
General Questions
What is WeaveDI?
WeaveDI is a modern, Swift-native dependency injection framework that provides:
- Type-safe dependency injection with compile-time guarantees
- Multiple injection patterns:
@Injected
,@Factory
, and legacy@Inject
- TCA-compatible API with KeyPath-based access
- Swift Concurrency support with Actor isolation
- Performance optimization with built-in caching and lazy loading
When to use WeaveDI:
// ✅ Perfect for:
// - iOS/macOS apps with complex dependency graphs
// - Apps using The Composable Architecture (TCA)
// - Projects requiring strict type safety
// - Apps needing testable architecture
// Example: Clean, testable view model
class UserViewModel {
@Injected(\.userService) var userService
@Injected(\.analytics) var analytics
func loadUser() async {
let user = await userService.fetchCurrentUser()
analytics.track("user_loaded")
}
}
How does WeaveDI compare to other DI frameworks?
Feature | WeaveDI | Swinject | Needle | Factory |
---|---|---|---|---|
Type Safety | ✅ Compile-time | ⚠️ Runtime | ✅ Compile-time | ✅ Compile-time |
TCA Compatible | ✅ Yes | ❌ No | ❌ No | ⚠️ Limited |
Property Wrappers | ✅ @Injected, @Factory | ✅ @Inject | ❌ No | ✅ @Injected |
Swift Concurrency | ✅ Full support | ⚠️ Partial | ⚠️ Limited | ✅ Full support |
Performance | ✅ Optimized | ⚠️ Moderate | ✅ Fast | ✅ Fast |
Learning Curve | ⚠️ Moderate | ⚠️ Moderate | ❌ Steep | ✅ Easy |
Migration-friendly:
// Easy to migrate from other frameworks
// Old (Swinject-style)
@Inject var service: UserService?
// New (WeaveDI v3.2.0+)
@Injected(\.userService) var service // Type-safe, non-optional
See Framework Comparison for detailed analysis.
Installation & Setup
Which Swift version do I need?
Version Requirements:
- Swift 6.0+: Full concurrency, strict Sendable (recommended)
- Swift 5.9+: Complete async/await support
- Swift 5.8+: Core DI features
- Swift 5.7+: Limited support (fallback implementations)
Example SPM setup:
// Package.swift
dependencies: [
.package(url: "https://github.com/Roy-wonji/WeaveDI.git", from: "3.2.0")
],
targets: [
.target(
name: "YourApp",
dependencies: ["WeaveDI"],
swiftSettings: [
// Swift 6 strict concurrency (optional)
.enableExperimentalFeature("StrictConcurrency")
]
)
]
How do I set up WeaveDI in my project?
Quick Setup (3 steps):
// 1. Define InjectedKey
struct UserServiceKey: InjectedKey {
static var liveValue: UserService = UserServiceImpl()
static var testValue: UserService = MockUserService()
}
// 2. Extend InjectedValues
extension InjectedValues {
var userService: UserService {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
// 3. Use @Injected
class ViewModel {
@Injected(\.userService) var userService
func loadData() async {
await userService.fetchUser()
}
}
App-wide setup:
@main
struct MyApp: App {
init() {
// Optional: Bootstrap for complex dependencies
Task {
await WeaveDI.Container.bootstrap { container in
container.register(DatabaseService.self) {
CoreDataService()
}
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
See Quick Start Guide for complete setup.
Property Wrappers
When should I use @Injected vs @Factory?
Use @Injected (default):
// ✅ Shared services (singleton-like)
@Injected(\.userService) var userService
@Injected(\.apiClient) var apiClient
@Injected(\.database) var database
// Why: Reuses same instance, better performance
Use @Factory:
// ✅ Fresh instances needed
@Factory var pdfGenerator: PDFGenerator
@Factory var dateFormatter: DateFormatter
@Factory var reportBuilder: ReportBuilder
// Why: Each access creates new instance
// Perfect for: Stateless operations, concurrent processing
// Example: Parallel PDF generation
class DocumentService {
@Factory var pdfGenerator: PDFGenerator
func generateReports(_ data: [Report]) async -> [PDF] {
await withTaskGroup(of: PDF.self) { group in
for report in data {
group.addTask {
let gen = self.pdfGenerator // Fresh instance per task
return gen.generate(report)
}
}
var results: [PDF] = []
for await pdf in group {
results.append(pdf)
}
return results
}
}
}
Decision Tree:
Need new instance each time?
├─ Yes → Use @Factory
│ └─ Examples: Formatters, Builders, Parsers
└─ No → Use @Injected
└─ Examples: Services, Repositories, Managers
What happened to @Inject and @SafeInject?
Deprecated in v3.2.0:
// ❌ Deprecated (still works but not recommended)
@Inject var service: UserService?
@SafeInject var api: APIClient?
// ✅ Migrate to @Injected
@Injected(\.userService) var service
@Injected(\.apiClient) var api
Why deprecated:
- Optional-based (requires nil checking)
- No compile-time KeyPath safety
- Not TCA-compatible
- Limited testing support
Migration Guide: See Migration: @Inject → @Injected
Can I use constructor injection instead?
Yes! Both patterns are supported:
// Pattern 1: Property injection (recommended)
class UserViewModel {
@Injected(\.userService) var userService
@Injected(\.analytics) var analytics
}
// Pattern 2: Constructor injection
class UserViewModel {
private let userService: UserService
private let analytics: Analytics
init(
userService: UserService,
analytics: Analytics
) {
self.userService = userService
self.analytics = analytics
}
}
// Register with constructor injection
container.register(UserViewModel.self) {
UserViewModel(
userService: container.resolve(UserService.self),
analytics: container.resolve(Analytics.self)
)
}
When to use constructor injection:
- Dependencies are required (not optional)
- Immutable dependencies preferred
- Testing with different implementations
- Explicit dependency declaration
When to use property wrappers:
- Simpler, less boilerplate
- TCA integration
- Dynamic dependency swapping
- Most typical use cases
Testing
How do I mock dependencies in tests?
Recommended: Use withInjectedValues
:
func testUserLogin() async {
// Create mocks
let mockAuth = MockAuthService()
let mockUser = MockUserService()
// Scope dependency overrides to test
await withInjectedValues { values in
values.authService = mockAuth
values.userService = mockUser
} operation: {
// Test with mocked dependencies
let viewModel = LoginViewModel()
await viewModel.login(username: "test", password: "pass")
// Verify mock interactions
XCTAssertTrue(mockAuth.loginCalled)
XCTAssertEqual(mockAuth.lastUsername, "test")
}
// Dependencies automatically revert after test
}
Why this approach:
- ✅ Automatic cleanup (no global state pollution)
- ✅ Type-safe
- ✅ Works with async/await
- ✅ Scoped to test execution
Alternative: Define testValue in InjectedKey:
struct UserServiceKey: InjectedKey {
static var liveValue: UserService = UserServiceImpl()
static var testValue: UserService = MockUserService() // Auto-used in tests
}
// Tests automatically use testValue
func testWithDefaults() async {
let viewModel = UserViewModel()
await viewModel.loadUser()
// Uses MockUserService automatically
}
See Testing Guide for advanced patterns.
How do I test code that uses @Injected?
Pattern 1: Override dependencies:
class UserViewModel {
@Injected(\.userService) var userService
func loadUser() async -> User? {
await userService.fetchUser(id: "123")
}
}
// Test
func testLoadUser() async {
let mockService = MockUserService()
mockService.stubbedUser = User(id: "123", name: "Test")
await withInjectedValues { values in
values.userService = mockService
} operation: {
let viewModel = UserViewModel()
let user = await viewModel.loadUser()
XCTAssertEqual(user?.name, "Test")
XCTAssertTrue(mockService.fetchUserCalled)
}
}
Pattern 2: Constructor injection for testing:
class UserViewModel {
@Injected(\.userService) var userService
// Test-friendly initializer
init(userService: UserService? = nil) {
if let service = userService {
// Override for testing
self._userService = Injected(wrappedValue: service)
}
}
}
// Test
func testLoadUser() async {
let mockService = MockUserService()
let viewModel = UserViewModel(userService: mockService)
await viewModel.loadUser()
XCTAssertTrue(mockService.fetchUserCalled)
}
Do I need to clean up dependencies between tests?
With withInjectedValues
: No cleanup needed
func testA() async {
await withInjectedValues { values in
values.service = MockA()
} operation: {
// Test A
}
// Auto-cleanup
}
func testB() async {
await withInjectedValues { values in
values.service = MockB()
} operation: {
// Test B - clean state
}
}
Without withInjectedValues
: Manual cleanup
class MyTests: XCTestCase {
override func tearDown() async throws {
await WeaveDI.Container.releaseAll()
}
}
Performance
Does WeaveDI impact app performance?
Performance characteristics:
Operation | Time | Impact |
---|---|---|
@Injected resolution | ~0.0001ms | ✅ Negligible |
@Factory creation | ~0.001ms | ✅ Minimal |
Container bootstrap | ~1-5ms | ✅ One-time cost |
Benchmark example:
// Resolution performance (1000 iterations)
let start = CFAbsoluteTimeGetCurrent()
for _ in 0..<1000 {
let service = InjectedValues.current.userService
_ = service.fetchUser()
}
let duration = CFAbsoluteTimeGetCurrent() - start
print("Total: \(duration * 1000)ms, Avg: \(duration)ms per resolution")
// Typical: ~0.1ms total = 0.0001ms per resolution
Optimization tips:
// 1. Enable runtime optimization
UnifiedRegistry.shared.enableOptimization()
// 2. Use @Injected for frequently accessed dependencies
@Injected(\.logger) var logger // Cached
// 3. Use @Factory only when fresh instances needed
@Factory var tempService: TempService // Creates new instance
// 4. Minimize dependency count per class (< 5)
class ViewModel {
@Injected(\.facade) var facade // Facade combines multiple services
}
See Performance Guide for optimization techniques.
Should I use @Factory or @Injected for better performance?
@Injected is faster (shared instance):
@Injected(\.service) var service
// First access: Resolves and caches
// Subsequent: Returns cached instance (very fast)
@Factory creates new instances (slower but necessary):
@Factory var generator: PDFGenerator
// Every access: Creates new instance
// Use when: State isolation needed
Performance comparison:
// Benchmark
class PerformanceTest {
@Injected(\.sharedService) var shared
@Factory var factory: Service
func benchmark() {
// @Injected: ~0.0001ms per access
measure {
for _ in 0..<1000 {
_ = shared.doWork()
}
}
// @Factory: ~0.001ms per access (10x slower but still fast)
measure {
for _ in 0..<1000 {
let service = factory
_ = service.doWork()
}
}
}
}
Recommendation:
- Default to
@Injected
(faster, shared state) - Use
@Factory
only when fresh instances required
Architecture
Can I use WeaveDI with SwiftUI?
Yes! Perfect integration:
// 1. Define dependencies
struct UserServiceKey: InjectedKey {
static var liveValue: UserService = UserServiceImpl()
}
extension InjectedValues {
var userService: UserService {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
// 2. Use in ViewModel
@MainActor
class UserViewModel: ObservableObject {
@Injected(\.userService) var userService
@Published var user: User?
func loadUser() async {
user = await userService.fetchCurrentUser()
}
}
// 3. Use in SwiftUI View
struct UserView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
VStack {
if let user = viewModel.user {
Text("Hello, \(user.name)")
}
}
.task {
await viewModel.loadUser()
}
}
}
SwiftUI Previews:
struct UserView_Previews: PreviewProvider {
static var previews: some View {
UserView()
.withInjectedValues { values in
values.userService = MockUserService()
}
}
}
Can I use WeaveDI with The Composable Architecture (TCA)?
Yes! WeaveDI is TCA-inspired:
// Dependencies (TCA-style)
extension InjectedValues {
var apiClient: APIClient {
get { self[APIClientKey.self] }
set { self[APIClientKey.self] = newValue }
}
}
// Reducer
struct UserFeature: Reducer {
@Injected(\.apiClient) var apiClient
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .loadUser:
return .run { send in
let user = await apiClient.fetchUser()
await send(.userLoaded(user))
}
}
}
}
// Tests
func testLoadUser() async {
await withInjectedValues { values in
values.apiClient = MockAPIClient()
} operation: {
let store = TestStore(initialState: UserFeature.State()) {
UserFeature()
}
await store.send(.loadUser)
await store.receive(.userLoaded)
}
}
See TCA Integration Guide for complete patterns.
How do I structure dependencies in a large app?
Recommended: Feature-based organization:
// File: DI/CoreDependencies.swift
extension InjectedValues {
// Infrastructure
var logger: Logger {
get { self[LoggerKey.self] }
set { self[LoggerKey.self] = newValue }
}
var database: Database {
get { self[DatabaseKey.self] }
set { self[DatabaseKey.self] = newValue }
}
}
// File: DI/UserFeatureDependencies.swift
extension InjectedValues {
// User feature
var userService: UserService {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
var userRepository: UserRepository {
get { self[UserRepositoryKey.self] }
set { self[UserRepositoryKey.self] = newValue }
}
}
// File: DI/PaymentFeatureDependencies.swift
extension InjectedValues {
// Payment feature
var paymentService: PaymentService {
get { self[PaymentServiceKey.self] }
set { self[PaymentServiceKey.self] = newValue }
}
}
Layer structure:
// Layer 1: Infrastructure (bottom)
extension InjectedValues {
var networkClient: NetworkClient { ... }
var database: Database { ... }
var logger: Logger { ... }
}
// Layer 2: Data/Repository
extension InjectedValues {
var userRepository: UserRepository { ... }
var productRepository: ProductRepository { ... }
}
// Layer 3: Domain/Business Logic
extension InjectedValues {
var userService: UserService { ... }
var orderService: OrderService { ... }
}
// Layer 4: Presentation
// ViewModels inject from Layer 3
See Best Practices for architecture patterns.
Common Issues
Why is my dependency nil?
Common causes:
- Dependency not registered:
// ❌ Problem: Never registered
@Injected(\.userService) var service // Might use default/nil
// ✅ Solution: Define InjectedKey
struct UserServiceKey: InjectedKey {
static var liveValue: UserService = UserServiceImpl()
}
- Wrong type:
// ❌ Problem: Type mismatch
container.register(Animal.self) { Dog() }
@Inject var cat: Cat? // Wrong type!
// ✅ Solution: Use correct type
container.register(Dog.self) { Dog() }
@Inject var dog: Dog?
- Accessing too early:
// ❌ Problem: Access in init
class ViewModel {
@Injected(\.service) var service
init() {
service.doWork() // Might not be ready
}
}
// ✅ Solution: Access after init
class ViewModel {
@Injected(\.service) var service
func start() {
service.doWork() // ✅ Works
}
}
See Troubleshooting Guide for solutions.
How do I avoid circular dependencies?
Problem:
class ServiceA {
@Injected(\.serviceB) var serviceB // ServiceA → ServiceB
}
class ServiceB {
@Injected(\.serviceA) var serviceA // ServiceB → ServiceA (circular!)
}
Solution 1: Introduce abstraction
protocol EventBus {
func publish(_ event: Event)
}
class ServiceA {
@Injected(\.eventBus) var eventBus // Both depend on abstraction
func doWork() {
eventBus.publish(WorkEvent())
}
}
class ServiceB {
@Injected(\.eventBus) var eventBus
init() {
eventBus.subscribe(WorkEvent.self) { event in
// Handle event
}
}
}
Solution 2: Weak reference
class ServiceA {
weak var serviceB: ServiceB? // Weak breaks cycle
}
class ServiceB {
@Injected(\.serviceA) var serviceA
}
Solution 3: Refactor shared logic
class SharedDependency {
func performSharedWork() {
// Logic both services need
}
}
class ServiceA {
@Injected(\.shared) var shared
}
class ServiceB {
@Injected(\.shared) var shared
}
Migration
How do I migrate from Swinject/Resolver?
Swinject → WeaveDI:
// Old (Swinject)
let container = Container()
container.register(UserService.self) { _ in
UserServiceImpl()
}
class ViewModel {
let service = container.resolve(UserService.self)!
}
// New (WeaveDI)
struct UserServiceKey: InjectedKey {
static var liveValue: UserService = UserServiceImpl()
}
extension InjectedValues {
var userService: UserService {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
class ViewModel {
@Injected(\.userService) var service
}
How do I migrate from @Inject to @Injected?
See complete Migration Guide.
Quick migration:
// Step 1: Old code
@Inject var service: UserService?
// Step 2: Define InjectedKey
struct UserServiceKey: InjectedKey {
static var liveValue: UserService = UserServiceImpl()
}
extension InjectedValues {
var userService: UserService {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
// Step 3: New code
@Injected(\.userService) var service // Non-optional!
Advanced Topics
Can I use WeaveDI with UIKit?
Yes:
class UserViewController: UIViewController {
@Injected(\.userService) var userService
@Injected(\.analytics) var analytics
override func viewDidLoad() {
super.viewDidLoad()
Task {
await loadUser()
}
}
func loadUser() async {
let user = await userService.fetchCurrentUser()
updateUI(with: user)
analytics.track("user_view_loaded")
}
}
How do I handle environment-specific dependencies?
struct APIClientKey: InjectedKey {
static var liveValue: APIClient {
#if DEBUG
return MockAPIClient() // Development
#else
return URLSessionAPIClient() // Production
#endif
}
}
// Or with build configuration
struct ConfigurableAPIKey: InjectedKey {
static var liveValue: APIClient {
if ProcessInfo.processInfo.environment["USE_MOCK"] == "1" {
return MockAPIClient()
}
return URLSessionAPIClient()
}
}
Can I use WeaveDI in a Swift Package?
Yes:
// Package.swift
let package = Package(
name: "MyFeature",
dependencies: [
.package(url: "https://github.com/Roy-wonji/WeaveDI.git", from: "3.2.0")
],
targets: [
.target(
name: "MyFeature",
dependencies: ["WeaveDI"]
)
]
)
// MyFeature/Sources/DI.swift
import WeaveDI
public extension InjectedValues {
var myFeatureService: MyFeatureService {
get { self[MyFeatureServiceKey.self] }
set { self[MyFeatureServiceKey.self] = newValue }
}
}
Getting Help
Where can I find more examples?
How do I report a bug or request a feature?
Is there a community?
- GitHub Discussions
- Stack Overflow (tag:
weave-di
)
Next Steps
- Quick Start Guide - Get started in 5 minutes
- Best Practices - Recommended patterns
- Troubleshooting - Common issues
- API Reference - Complete API documentation