Version 5.0.0 is a complete rewrite of WatchConnectivitySwift with a new API designed for Swift 6 and modern concurrency patterns. This guide helps you migrate from v1.x.
| Aspect | v1.x | v5.0.0 |
|---|---|---|
| Minimum iOS | 13.0 | 17.0 |
| Minimum watchOS | 8.0 | 10.0 |
| Swift Version | 5.5+ | 6.0+ |
| Main Class | WatchConnectivityRPCRepository |
WatchConnection |
| Request Type | Protocol + enum | Protocol with associated type |
| Response Type | Protocol + enum | Associated type on request |
| Concurrency | Completion handlers | async/await |
| State Sync | Manual | SharedState<T> with automatic sync |
| Retry Logic | Custom implementation | Built-in RetryPolicy |
| Health Monitoring | None | Built-in SessionHealth |
In your Package.swift or Xcode project settings:
// Package.swift
platforms: [
.iOS(.v17),
.watchOS(.v10)
]enum RecipeRequest: WatchConnectivityRPCRequest, Codable {
case fetchRecipe(id: String)
case searchRecipes(query: String)
}
enum RecipeResponse: WatchConnectivityRPCResponse, Codable {
case recipe(Recipe)
case recipes([Recipe])
case error(String)
}// Each request is its own type with a specific response
struct FetchRecipeRequest: WatchRequest {
typealias Response = Recipe
let id: String
}
struct SearchRecipesRequest: WatchRequest {
typealias Response = [Recipe]
let query: String
}Benefits of v5.0.0 approach:
- Compile-time type safety (can't return wrong response type)
- Clearer API (each request clearly states what it returns)
- No need for switch statements on response
final class RecipeRepository: WatchConnectivityRPCRepository<
RecipeRequest,
RecipeResponse,
RecipeSharedContext
> {
override func onRequest(_ request: RecipeRequest) async throws -> RecipeResponse {
switch request {
case .fetchRecipe(let id):
let recipe = try await fetchRecipe(id: id)
return .recipe(recipe)
case .searchRecipes(let query):
let recipes = try await search(query: query)
return .recipes(recipes)
}
}
}@MainActor
class AppCoordinator {
let connection = WatchConnection()
func setup() {
// Register handlers
connection.register(FetchRecipeRequest.self) { request in
try await self.fetchRecipe(id: request.id)
}
connection.register(SearchRecipesRequest.self) { request in
try await self.search(query: request.query)
}
}
private func fetchRecipe(id: String) async throws -> Recipe { ... }
private func search(query: String) async throws -> [Recipe] { ... }
}let repository = RecipeRepository()
func loadRecipe(id: String) {
Task {
do {
let response: RecipeResponse = try await repository.perform(
request: .fetchRecipe(id: id)
)
switch response {
case .recipe(let recipe):
self.recipe = recipe
case .error(let message):
self.error = message
default:
break
}
} catch {
self.error = error.localizedDescription
}
}
}let connection = WatchConnection()
func loadRecipe(id: String) async {
do {
// Response type is automatically inferred
recipe = try await connection.send(FetchRecipeRequest(id: id))
} catch {
self.error = error
}
}struct RecipeSharedContext: WatchConnectivityRPCApplicationContext, Codable {
var lastViewedRecipeID: String?
var favoriteRecipeIDs: [String]
}
// In repository
func updateSharedContext(_ context: RecipeSharedContext) {
// Manual sync via applicationContext
}struct RecipeContext: Codable, Sendable, Equatable {
var lastViewedRecipeID: String?
var favoriteRecipeIDs: [String]
}
// Automatic sync (requires WatchConnection instance)
let connection = WatchConnection()
let sharedContext = SharedState(
initialValue: RecipeContext(),
connection: connection
)
// Update (automatically syncs)
try sharedContext.update(RecipeContext(
lastViewedRecipeID: "123",
favoriteRecipeIDs: ["456", "789"]
))
// Read current value (observable via @Observable)
let current = sharedContext.valuev5.0.0 has built-in retry logic. Configure as needed:
// Use built-in policies
let recipe = try await connection.send(
FetchRecipeRequest(id: id),
retryPolicy: .patient // 5 attempts, 30s timeout
)
// Or customize
let recipe = try await connection.send(
FetchRecipeRequest(id: id),
retryPolicy: RetryPolicy(maxAttempts: 4, timeout: .seconds(15))
)v5.0.0 includes session health monitoring:
// Observe health state
Task {
for await event in connection.diagnosticEvents {
if case .healthChanged(_, let to) = event {
if !to.isHealthy {
showRecoveryPrompt()
}
}
}
}
// Or check directly
if !connection.sessionHealth.isHealthy {
await connection.attemptRecovery()
}| v1.x | v5.0.0 |
|---|---|
WatchConnectivityService |
WatchConnection |
WatchConnectivityRPCRepository<R,S,C> |
WatchConnection + handler registration |
| v1.x | v5.0.0 |
|---|---|
WatchConnectivityRPCRequest |
WatchRequest |
WatchConnectivityRPCResponse |
Associated type Response on WatchRequest |
WatchConnectivityRPCApplicationContext |
State type for SharedState<T> |
| v1.x | v5.0.0 |
|---|---|
repository.perform(request:) |
connection.send(_:) |
repository.onRequest(_:) |
connection.register(_:handler:) |
| Manual context sync | sharedState.update(_:) |
| v1.x | v5.0.0 |
|---|---|
service.activationState |
connection.activationState |
service.isReachable |
connection.isReachable |
service.isPaired |
connection.isPaired (iOS only) |
| N/A | connection.sessionHealth |
| N/A | connection.diagnosticEvents |
| N/A | connection.pendingRequestCount |
The following v1.x features are replaced with better alternatives:
| v1.x Feature | v5.0.0 Replacement |
|---|---|
AsyncStream for raw messages |
Type-safe handlers |
| Manual retry loop | Built-in RetryPolicy |
| Dictionary-based messages | Codable structs |
Take advantage of these new capabilities:
- Fire-and-forget requests: Use
FireAndForgetRequestfor analytics/logging - Delivery strategies: Choose how messages are delivered with fallbacks
- Shared state: Automatic synchronization via applicationContext
- Session health: Monitor and recover from connectivity issues
- Diagnostic events: Debug connectivity behavior
If you encounter issues during migration:
- Check the API Reference for detailed documentation
- Review the Architecture Overview to understand the design
- Open an issue on GitHub