Skip to content

Null Object ​

Null Object supplies a valid object that intentionally does nothing. It can remove repeated optional checks where “no behavior” is a legitimate configuration.

When it helps in iOS ​

A feature records analytics in production, but previews and unit tests should not send events. A no-op analytics client lets the feature call the same interface in every environment.

Original example: a no-op analytics client ​

swift
import Foundation

protocol AnalyticsTracking {
    func track(_ event: String)
}

struct NoOpAnalytics: AnalyticsTracking {
    func track(_ event: String) {}
}

struct ConsoleAnalytics: AnalyticsTracking {
    func track(_ event: String) { print("Event: \(event)") }
}

final class SavedItemsFeature {
    private let analytics: any AnalyticsTracking

    init(analytics: any AnalyticsTracking) {
        self.analytics = analytics
    }

    func itemSaved() {
        analytics.track("item_saved")
    }
}

let preview = SavedItemsFeature(analytics: NoOpAnalytics())
preview.itemSaved()

The feature has no if analytics != nil branch. The composition root decides whether behavior is real or intentionally absent.

Production decisions ​

Use a null object only when silence is correct. A payment service that silently does nothing would hide a serious error. For tests that must verify events, inject a recording spy instead of NoOpAnalytics. Keep the no-op implementation visibly named so production wiring mistakes are easy to spot.

Why a real interface matters ​

SavedItemsFeature requires AnalyticsTracking, so it can always call track(_:) without an optional check. NoOpAnalytics fulfills the protocol while intentionally doing nothing. The feature does not know whether it is in a preview, test, or production build; the composition root chooses the implementation. This keeps environment policy outside the feature and makes absence of analytics explicit.

From example to an iOS app ​

Use a production analytics adapter at app startup, respecting consent and privacy settings. Inject NoOpAnalytics into previews, offline demos, or a user session that has not consented. If consent changes while the app runs, a higher-level analytics router can switch implementations or check policy before forwarding. The feature should log semantic events such as item_saved, not screen-specific implementation details.

Failure modes and verification ​

A no-op should never hide a required operation such as charging a card or saving a document. Test that preview mode sends nothing and use a recording spy to assert production feature events. Make the no-op type name obvious and audit composition-root wiring so it is not accidentally shipped where real tracking is required. An optional dependency may be clearer if absence itself is important to the feature's behavior.

All patterns

Released under the MIT License.