# SKPublished Property Wrapper: Reactive State Management in SectionKit

> Discover SKPublished, a Swift property wrapper for SectionKit. Effortlessly manage reactive state and data binding with Combine publishers, eliminating boilerplate for smoother development.

- Repository: [神奇/sectionkit](https://github.com/linhay/sectionkit)
- Tags: deep-dive
- Published: 2026-03-06

---

**SKPublished is a Swift property wrapper that transforms stored properties into Combine-compatible observable publishers, enabling reactive data binding without the boilerplate of manual subject management.**

The `SKPublished` property wrapper is a core reactive primitive in the [linhay/sectionkit](https://github.com/linhay/sectionkit) open-source library. Unlike SwiftUI's built-in `@Published`, this implementation provides granular control over publishing semantics and includes a composable transform system for customizing data flow. As implemented in [`Sources/SectionKit/Common/SKPublished.swift`](https://github.com/linhay/sectionkit/blob/main/Sources/SectionKit/Common/SKPublished.swift), it bridges imperative property assignment with declarative Combine pipelines, making it ideal for UIKit-based sectioned interfaces that require precise state synchronization.

## Core Architecture and Components

Under the hood, `SKPublished` composes three distinct types defined in [`Sources/SectionKit/Common/SKPublished.swift`](https://github.com/linhay/sectionkit/blob/main/Sources/SectionKit/Common/SKPublished.swift):

**SKPublishedValue** acts as a custom `Publisher` that maintains the current state and forwards changes through Combine pipelines. It wraps either a `PassthroughSubject` or `CurrentValueSubject` depending on the configuration.

**SKPublishedTransform** provides a type-safe, composable description of publisher transformations. These transforms inject behaviors—such as deduplication or thread hopping—without requiring manual Combine chain construction at every call site.

**SKPublished** (the wrapper itself) exposes the stored value through `wrappedValue` and the observable interface through `projectedValue` (accessed via `$property`). This dual interface allows both imperative assignment and reactive subscription.

## Publishing Behaviors with SKPublishedKind

The `SKPublishedKind` enum defines two distinct broadcasting strategies that determine which Combine subject powers the observable:

```swift
public enum SKPublishedKind {
    case passThrough      // Emits every assignment regardless of previous value
    case currentValue     // Starts with the initial value and emits subsequent changes
}

```

**Pass-through behavior** uses `PassthroughSubject`, emitting events only when the value changes after initialization. **Current-value behavior** uses `CurrentValueSubject`, immediately emitting the initial value to new subscribers and caching the latest value for future subscribers.

## Composable Transform Pipelines

Transforms are stored in `SKPublishedValue.transforms` and applied sequentially when the `publisher` property is accessed. The `SKPublishedTransform` type provides several built-in helpers:

- **`.print(prefix:)`** – Logs value changes to the console with a custom prefix for debugging.
- **`.receiveOnMainQueue()`** – Ensures delivery on the main dispatch queue for UI updates.
- **`.removeDuplicates()` / `.removeDuplicates(by:)`** – Filters out consecutive equal values to prevent redundant processing.
- **`.dropFirst(count:)`** – Skips the specified number of initial emissions.
- **`.filter(_:)`** – Applies a predicate to conditionally emit values.

These transforms are declared at the property definition site and execute automatically upon subscription.

## Practical Implementation Examples

### Observing a Basic Property

Any stored property can become observable by applying the wrapper. Accessing `$count` returns the `SKPublishedValue` publisher:

```swift
class Counter {
    @SKPublished var count: Int = 0
}

let counter = Counter()

// Subscribe to changes
let cancellable = counter.$count.sink { newValue in
    print("Count changed to:", newValue)
}

// Update the value – subscriber prints the change
counter.count = 1   // → "Count changed to: 1"
counter.count = 2   // → "Count changed to: 2"

```

### Chaining Transforms for Logging and Deduplication

Transforms are passed as an array to the wrapper initializer. The following configuration logs all changes while suppressing duplicate consecutive values:

```swift
class Settings {
    @SKPublished(
        transform: [
            .print(prefix: "🔧"),
            .removeDuplicates()
        ]
    )
    var mode: String = "light"
}

let settings = Settings()
let sub = settings.$mode.sink { _ in }   // subscription needed to activate pipeline

settings.mode = "dark"   // prints: [SKPublished] 🔧 light => dark
settings.mode = "dark"   // no output – duplicate filtered

```

### Fire-on-Every-Assignment with passThrough

When you need to emit events even if the value remains identical, specify `kind: .passThrough`:

```swift
class Logger {
    @SKPublished(kind: .passThrough) var message: String = ""
}

let logger = Logger()
let cancellable = logger.$message.sink { print("Log:", $0) }

logger.message = "Start"   // prints "Log: Start"
logger.message = "Start"   // prints again because `.passThrough` ignores previous value

```

### Binding to UI Controls

The wrapper integrates with UIKit through the `bind(_:)` method, which handles thread safety and memory management:

```swift
class ViewModel {
    @SKPublished var title: String = "Hello"
}

let vm = ViewModel()
let label = UILabel()

// Bind label text to the published value
let bindCancellable = vm.$title.bind { newTitle in
    label.text = newTitle
}

// Updating the model updates the label automatically
vm.title = "World"   // label.text becomes "World"

```

### Broadcasting Void Events

For stateless triggers like refresh controls, `SKPublished` supports `Void` types with a convenience `send()` method:

```swift
class RefreshTrigger {
    @SKPublished var refresh = ()
}

let trigger = RefreshTrigger()
let cancellable = trigger.$refresh.sink { _ in
    print("Refresh requested")
}

trigger.refresh = ()   // prints "Refresh requested"
trigger.refresh()     // convenience `send()` for Void publishes again

```

## Thread Safety and Main Queue Delivery

As implemented in [`Sources/SectionKit/Common/SKPublished.swift`](https://github.com/linhay/sectionkit/blob/main/Sources/SectionKit/Common/SKPublished.swift), the `bind(_:)` helper ensures thread safety by optionally delivering the initial value on the main queue. This prevents UI updates from occurring on background threads when binding model properties to view layers. The `receiveOnMainQueue()` transform provides similar guarantees for custom subscriber implementations.

## Summary

- **SKPublished** is a Combine-compatible property wrapper defined in [`Sources/SectionKit/Common/SKPublished.swift`](https://github.com/linhay/sectionkit/blob/main/Sources/SectionKit/Common/SKPublished.swift) that converts stored properties into reactive publishers.
- It supports two publishing strategies via **SKPublishedKind**: `currentValue` (caching) and `passThrough` (event-only).
- **SKPublishedTransform** enables declarative composition of behaviors like deduplication, logging, and thread dispatch without manual Combine chaining.
- The `projectedValue` (accessed with `$`) exposes `SKPublishedValue`, which conforms to `Publisher` and supports binding helpers for UIKit integration.
- Thread safety is handled through built-in main-queue delivery options, making it suitable for UI-driven architectures.

## Frequently Asked Questions

### How does SKPublished differ from SwiftUI's @Published?

While both wrappers enable Combine observation, **SKPublished** provides explicit control over publishing semantics through `SKPublishedKind` and supports composable transforms. SwiftUI's `@Published` always behaves like `currentValue` semantics and lacks the built-in transform pipeline system found in SectionKit's implementation.

### Can SKPublished be used with optional values?

Yes. The wrapper includes dedicated initializers for optional types, simplifying usage with nullable model fields. You can declare `@SKPublished var name: String? = nil` and subscribe to the optional stream directly through the projected value.

### What Combine subject does SKPublished use internally?

The implementation selects between `CurrentValueSubject` for `.currentValue` kind and `PassthroughSubject` for `.passThrough` kind. This choice is made at initialization and determines whether new subscribers receive the current cached value immediately or only receive subsequent updates.

### Is SKPublished thread-safe for UI updates?

Yes. When using the `bind(_:)` method or the `.receiveOnMainQueue()` transform, values are delivered on the main thread. The wrapper is designed to prevent common threading issues in UIKit applications by ensuring that mutable state observations occur on the appropriate dispatch queue.