Skip to content

RegistrationMetricsDispatcher API ​

High-performance batching system that collects multiple monitoring tasks generated during registration into a single pipeline.

Overview ​

RegistrationMetricsDispatcher is WeaveDI's efficient batching layer that consolidates monitoring operations to minimize performance overhead. Instead of executing monitoring tasks immediately upon each registration, it intelligently batches them into pipeline operations for optimal throughput.

Core Features ​

⚡ Batch Processing ​

  • Task Queuing: Collects multiple monitoring jobs before execution
  • Automatic Scheduling: Smart scheduling prevents unnecessary Task spawning
  • Utility Priority: Background processing doesn't interfere with main operations
  • Memory Efficient: Minimal overhead with job accumulation strategy

🔒 Thread Safety ​

  • NSLock Protection: Thread-safe concurrent job enqueuing
  • Atomic Scheduling: Prevents race conditions in batch scheduling
  • Safe Flushing: Coordinated batch processing without data races

Performance Characteristics ​

Efficiency Comparison ​

ApproachTask CreationContext SwitchesMemory Pressure
Immediate execution1 per registrationHighMedium
RegistrationMetricsDispatcher1 per batchLowMinimal

Batch Benefits ​

swift
// Without batching (inefficient)
// Each registration creates separate Task
UnifiedDI.register(ServiceA.self) { ServiceA() }  // → Task 1
UnifiedDI.register(ServiceB.self) { ServiceB() }  // → Task 2
UnifiedDI.register(ServiceC.self) { ServiceC() }  // → Task 3

// With RegistrationMetricsDispatcher (efficient)
// Multiple registrations share single Task
UnifiedDI.register(ServiceA.self) { ServiceA() }  // → Queued
UnifiedDI.register(ServiceB.self) { ServiceB() }  // → Queued
UnifiedDI.register(ServiceC.self) { ServiceC() }  // → Queued + Batch execution

Internal Architecture ​

Core Structure ​

swift
final class RegistrationMetricsDispatcher: @unchecked Sendable {
    typealias Job = @Sendable () async -> Void

    static let shared = RegistrationMetricsDispatcher()

    private let lock = NSLock()
    private var pending: [Job] = []
    private var isScheduled = false
}

Key Operations ​

Job Enqueuing ​

swift
func enqueueRegistration<T>(_ type: T.Type) where T: Sendable {
    enqueue { await AutoDIOptimizer.shared.trackRegistration(type) }
}

private func enqueue(_ job: @escaping Job) {
    var shouldSchedule = false
    lock.lock()
    pending.append(job)
    if !isScheduled {
        isScheduled = true
        shouldSchedule = true
    }
    lock.unlock()

    if shouldSchedule {
        Task(priority: .utility) {
            await self.flush()
        }
    }
}

Batch Processing ​

swift
private func flush() async {
    while true {
        let jobs = nextBatch()
        if jobs.isEmpty { break }
        for job in jobs {
            await job()
        }
    }
}

private func nextBatch() -> [Job] {
    lock.lock()
    let jobs = pending
    pending.removeAll()
    if pending.isEmpty {
        isScheduled = false
    }
    lock.unlock()
    return jobs
}

Integration with WeaveDI ​

Automatic Usage ​

swift
// RegistrationMetricsDispatcher is used automatically
let service = UnifiedDI.register(UserService.self) { UserService() }

// Internal flow:
// 1. Registration completes
// 2. RegistrationMetricsDispatcher.shared.enqueueRegistration(UserService.self)
// 3. Job is queued for batch processing
// 4. Background Task processes all queued monitoring jobs

Performance Flow ​

swift
// Multiple rapid registrations
for i in 1...100 {
    UnifiedDI.register(Service\(i).self) { Service\(i)() }
}

// Efficient batching:
// - 100 monitoring jobs queued
// - Single Task spawned for batch processing
// - All jobs processed sequentially in background
// - Minimal impact on registration performance

Optimization Techniques ​

1. Smart Scheduling ​

swift
// Only schedule new Task if none is running
if !isScheduled {
    isScheduled = true
    shouldSchedule = true
}
// → Prevents unnecessary Task creation

2. Utility Priority ​

swift
Task(priority: .utility) {
    await self.flush()
}
// → Background processing doesn't block main operations

3. Batch Accumulation ​

swift
// Collect all pending jobs in single operation
let jobs = pending
pending.removeAll()
// → Efficient memory management and processing

4. Lock Minimization ​

swift
lock.lock()
// Minimal work under lock
pending.append(job)
lock.unlock()
// → Reduced lock contention

Best Practices ​

1. Automatic Operation ​

swift
// ✅ Good: Dispatcher works automatically
let service = UnifiedDI.register(UserService.self) { UserService() }

// ❌ Avoid: Manual dispatcher usage is unnecessary
// RegistrationMetricsDispatcher.shared.enqueueRegistration(UserService.self)

2. High-Volume Registration Patterns ​

swift
// ✅ Efficient: Batch processing handles high volume
func registerAllServices() {
    // All these registrations will be efficiently batched
    for serviceType in allServiceTypes {
        UnifiedDI.register(serviceType) { createService(serviceType) }
    }
}

3. Performance Monitoring ​

swift
// Monitor batch efficiency in development
#if DEBUG
func logBatchMetrics() {
    // Batch size and frequency analysis
    print("Average batch size: \(averageBatchSize)")
    print("Batch frequency: \(batchesPerSecond)")
}
#endif

Memory Management ​

Efficient Memory Usage ​

  • Job Storage: Minimal closure overhead per queued job
  • Batch Processing: Immediate job execution and deallocation
  • No Accumulation: Jobs don't persist after processing
  • Automatic Cleanup: Memory pressure doesn't build up

Memory Footprint ​

swift
// Actual memory usage per queued job
let jobSize = MemoryLayout<Job>.size  // 16 bytes (closure)
// Jobs are processed and deallocated quickly

Technical Implementation Details ​

Thread Safety Model ​

  1. Enqueue Path: Thread-safe job addition to queue
  2. Schedule Path: Atomic Task scheduling decision
  3. Process Path: Sequential job execution in Task
  4. Cleanup Path: Safe queue reset after processing

Concurrency Patterns ​

swift
// Thread-safe enqueuing from multiple sources
DispatchQueue.global().async {
    UnifiedDI.register(ServiceA.self) { ServiceA() }  // Thread 1
}

DispatchQueue.global().async {
    UnifiedDI.register(ServiceB.self) { ServiceB() }  // Thread 2
}

// Both registrations safely queued and batched

Performance Optimizations ​

Batch Size Optimization ​

  • Dynamic Batching: Processes all currently queued jobs
  • No Artificial Limits: Natural batching based on registration patterns
  • Immediate Processing: Jobs execute as soon as batch is ready

Context Switch Reduction ​

swift
// Traditional approach: N registrations = N Tasks
// RegistrationMetricsDispatcher: N registrations = 1 Task (batched)

// 50-80% reduction in context switches for high-volume scenarios

Error Handling ​

Graceful Degradation ​

swift
// Individual job failures don't affect batch processing
for job in jobs {
    await job()  // Each job isolated
}
// → Failed jobs don't break subsequent processing

Monitoring Integration ​

swift
// Integration with AutoDIOptimizer for dependency tracking
enqueue { await AutoDIOptimizer.shared.trackRegistration(type) }
// → Monitoring errors are contained within batch system

Real-World Usage Scenarios ​

Application Startup ​

swift
// Efficient handling of bulk dependency registration
class AppDependencySetup {
    func registerAllDependencies() {
        // 50+ service registrations
        registerCoreServices()      // → Batched
        registerNetworkServices()   // → Batched
        registerDataServices()      // → Batched
        registerUIServices()        // → Batched

        // All monitoring tasks processed in single background Task
    }
}

Module Loading ​

swift
// Dynamic module loading with efficient monitoring
func loadModule(_ module: DependencyModule) {
    module.dependencies.forEach { dependency in
        UnifiedDI.register(dependency.type, factory: dependency.factory)
        // → All registrations batched for optimal performance
    }
}

Comparison with Alternatives ​

Immediate Execution ​

swift
// Without RegistrationMetricsDispatcher
UnifiedDI.register(ServiceA.self) {
    let service = ServiceA()
    Task { await AutoDIOptimizer.shared.trackRegistration(ServiceA.self) }  // Task 1
    return service
}

UnifiedDI.register(ServiceB.self) {
    let service = ServiceB()
    Task { await AutoDIOptimizer.shared.trackRegistration(ServiceB.self) }  // Task 2
    return service
}
// → Multiple Tasks, high overhead

With RegistrationMetricsDispatcher ​

swift
// With RegistrationMetricsDispatcher
UnifiedDI.register(ServiceA.self) { ServiceA() }  // → Queued
UnifiedDI.register(ServiceB.self) { ServiceB() }  // → Queued + Batch execute
// → Single Task, minimal overhead

See Also ​

Released under the MIT License.