Lightweight iOS <-> watchOS synchronization built on WatchConnectivity.
Connectivity provides two ways to exchange data:
- automatic state sync for
ObservableObjectmodels viaConnectivityCommunicator– useful for SwiftUI, and shared settings. - manual message transport via
ConnectivitySender+ConnectivityReceiver– fast and robust multipath message delivery.
It is designed for settings/state replication where the newest value matters more than delivery of every intermediate event.
- Bidirectional iPhone/Watch communication (
ConnectivitySender,ConnectivityReceiver) Portableprotocol for automatic model synchronization (ConnectivityCommunicator)- Manual messaging API with typed payloads (
Codable) - Message routing by:
ConnectivityChannel(settings,watchLog)ConnectivityCounterpart(iOS,watchOS)
- Built-in latest-value reduction per key
- Optional persisted message log/records (via
FileStorage)
- Swift 6.2+ with concurrency
- iOS 16+
- watchOS 9+
- Xcode with iOS/watchOS SDKs and
WatchConnectivity
dependencies: [
.package(url: "https://github.com/kvaDrug/Connectivity.git", branch: "main")
],
targets: [
.target(
name: "YourTarget",
dependencies: [
"Connectivity"
]
)
]import Connectivity
struct SharedSettingsPortable: Codable, Equatable {
var premiumEnabled: Bool
var refreshInterval: Int
}
@MainActor
final class SharedSettingsStore: ObservableObject, Portable {
typealias PortableRepresentation = SharedSettingsPortable
@Published var premiumEnabled = false
@Published var refreshInterval = 15
var portable: PortableRepresentation {
.init(
premiumEnabled: premiumEnabled,
refreshInterval: refreshInterval
)
}
func updateValues(with portable: PortableRepresentation) {
premiumEnabled = portable.premiumEnabled
refreshInterval = portable.refreshInterval
}
}import Connectivity
@MainActor
final class ConnectivityBootstrap {
private let settings = SharedSettingsStore()
private var communicator: ConnectivityCommunicator?
func start() {
ConnectivitySessionManager.shared.activate()
communicator = ConnectivityCommunicator(
observable: settings,
key: .sharedSettings
)
}
}ConnectivityCommunicator will:
- send updates when
objectWillChangeemits - debounce outgoing updates (
900msby default) - receive newest remote value and apply it with
updateValues(with:)
Use this when you want explicit messages instead of full model sync.
import Connectivity
let sender = ConnectivitySender(sendingTo: .watchOS, via: .watchLog)
let key: ConnectivityMessageKey = "watch-log-line"
do {
let message = try ConnectivityMessage(
key: key,
value: "App launched"
)
sender.send(message)
} catch {
print("Failed to build ConnectivityMessage:", error)
}import Connectivity
import Foundation
@MainActor
final class WatchLogReceiver {
let receiver = ConnectivityReceiver(
receiveIn: .iOS,
via: .watchLog,
uniqueKeys: false
)
private var token: NSObjectProtocol?
func start() {
token = NotificationCenter.default.addObserver(
forName: ConnectivityReceiver.didReceiveDataNotification,
object: receiver,
queue: .main
) { [weak self] _ in
guard let self else { return }
print("Received messages:", self.receiver.log.count)
}
}
}If uniqueKeys is true, use receiver.records to get newest value per key.
ConnectivityMessage stores:
key: ConnectivityMessageKeyjsonValue: String(payload encoded fromCodable)timestamp: TimeIntervaldestination: ConnectivityCounterpart?
You can define your own keys:
import Connectivity
extension ConnectivityMessageKey {
static let accountState: Self = "account-state"
}Portable: protocol for automatic sync ofObservableObjectConnectivityCommunicator: wiresPortableto sender/receiver flowConnectivitySender: sendsConnectivityMessageto counterpartConnectivityReceiver: receives, merges, stores messages and posts updatesConnectivitySessionManager: singleton managingWCSessionactivation/delegateSessionDelegateMulticast: fan-out ofWCSessionDelegateevents as publishersConnectivityMessage,ConnectivityMessageKey,ConnectivityRecordWCSession.canSend: helper for delivery eligibility
Combine is exported by this package, so AnyPublisher, @Published, etc. are available after importing Connectivity.
- Core types are
@MainActorwhere mutable shared state is managed. It's required byWatchConnectivity. - Keep
ConnectivitySender,ConnectivityReceiver, andConnectivityCommunicatorusage on the main actor.
- Designed for "latest state wins" scenarios.
- Messages may be delayed; do not rely on strict real-time delivery.
- For long logs, duplicate keys are reduced to most recent message.
destinationfiltering prevents cross-target processing.