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.