
# 2026.3.x から 2026.4.x への移行

2026.4.0 では Swift 6 言語モードに対応し、あわせて公開 API の一部を変更しました。
このドキュメントでは 2026.3.x から 2026.4.x へ移行する際に必要な対応をまとめています。

## Swift 6 言語モードへの対応

### 概要

2026.4.0 の `Package.swift` は `swift-tools-version` が 6.3 で、 `swiftLanguageModes: [.v6]` により Swift 6 言語モードを宣言しています。
そのため Swift Package Manager で取り込んだ場合も SDK は Swift 6 言語モードでコンパイルされます。利用側で言語モードを指定する必要はありません。

`swift-tools-version` 6.3 の manifest を読むために Swift Package Manager 6.3 以降が必要です。
Swift Package Manager 6.3 は Xcode 26.6 に同梱されているため、 2026.4.0 以降を利用するには Xcode 26.6 以降が必要です。

> **重要**
>
> 利用側のアプリは Swift 5 言語モードでも Swift 6 言語モードでも利用できます。
> アプリ側が Swift 5 言語モードを継続利用する場合は、対応は不要です。

> **注釈**
>
> Xcode 26.6 には Swift 6 のコンパイラが同梱されていますが、 Swift コンパイラのバージョンと Swift の言語モードは別の概念です。
> Swift 6 のコンパイラは Swift 5 言語モードでも Swift 6 言語モードでもコンパイルでき、言語モードによって actor 隔離や `Sendable` の検査の厳しさが変わります ( Swift 6 言語モードでは違反がエラーになります) 。
> アプリ側の言語モードはアプリ側のビルド設定 ( `SWIFT_VERSION` ) で選択し、 SDK の言語モード ( `Package.swift` の `swiftLanguageModes` ) とは独立しています。

### 利用側で必要な対応

SDK 側で公開型に `Sendable` 準拠を追加したため、利用側で独自に `Sendable` 準拠を追加していた場合は削除してください。
重複適合は警告になり、 warnings-as-errors を有効にした build ではエラーになります。

> **ヒント**
>
> アプリ側で既に Swift 6 言語モード対応済み、かつ SDK を `@preconcurrency import Sora` でインポートしている場合は、 2026.4.0 では公開型が `Sendable` に準拠したため `import Sora` に置き換えてください。

`Configuration` / `MediaChannel` / `MediaStream` / `MediaChannelHandlers` / `SoraHandlers` / `Statistics` / `VideoView` などは `Sendable` ではありません。
これらの値を actor 境界や `Task` の `@Sendable` クロージャへそのまま渡すことはできません。詳細は [nonisolated(unsafe) が必要なケース](2026_3_to_2026_4.html#c04bfa) をご確認ください。

`@MainActor` のクラス ( `UIViewController` など) の中でコールバッククロージャを書くと、クロージャが MainActor 隔離を継承します。
SDK はシグナリングスレッドなどからコールバックを呼ぶため、そのままでは Swift 6 言語モードの実行時隔離チェックでクラッシュします。
クロージャに `@Sendable` を付けるか、 `nonisolated` な関数へ処理を分離して隔離を外してください。

```swift
config.mediaChannelHandlers.onAddStream = { @Sendable [weak self] stream in
  // MediaStream は非 Sendable のため nonisolated(unsafe) で Task へ渡す
  nonisolated(unsafe) let stream = stream
  Task { @MainActor in
    guard let self else { return }
    self.attach(stream: stream)
  }
}
```

コールバックの呼び出しスレッドは保証されないため、 UI の更新は main actor / main queue へ束ねてください。

`MediaChannel.setVideoHardMute` など一部の API は `async` / `await` に対応しています。
これらは `MediaChannel` を保持している側のコンテキストから `Task` で局所的に呼び出す想定です。


### nonisolated(unsafe) が必要なケース

2026.4.0 の時点では、次の操作を行う場合にアプリ側で `nonisolated(unsafe)` が必要です。

- SDK のコールバックで受け取った `MediaChannel` / `MediaStream` を `Task` や `@Sendable` クロージャへ渡す- 例: `Sora.connect` の handler で受け取った `MediaChannel` を `Task { @MainActor in ... }` へ渡す
- `@MainActor` の文脈から `MediaChannel` の `async` メソッドを呼び出す- `rpc`
  - `sendableRPC`
  - `getStatsSnapshot()`
  - `setVideoHardMute(_:)`
  - `startScreenCapture(settings:)`
  - `stopScreenCapture()`
- `@MainActor` の文脈から `ScreenCaptureSettings` を `startScreenCapture(settings:)` へ渡す

`MediaChannel` / `MediaStream` / `ScreenCaptureSettings` は `Sendable` ではないためです。
また `MediaChannel` の `async` メソッドは `nonisolated` な非同期メソッドで、呼び出し元の executor では実行されません (コンパイラの診断では `@concurrent` と表示されます) 。
そのため `@MainActor` の文脈から呼び出すと、 `MediaChannel` の参照と引数が actor 境界を越えます。

`nonisolated(unsafe)` を付けた `let` で受けると、この検査を外せます。

```swift
@MainActor
final class RoomViewController: UIViewController {
  private var mediaChannel: MediaChannel?

  func setHardMute(_ mute: Bool) {
    guard let mediaChannel else { return }
    // MediaChannel は非 Sendable で、 setVideoHardMute は呼び出し元の executor では実行されないため
    nonisolated(unsafe) let channel = mediaChannel
    Task {
      do {
        try await channel.setVideoHardMute(mute)
      } catch {
        print(error)
      }
    }
  }
}
```

> **注釈**
>
> `@MainActor` の文脈から呼び出す同期 API ( `setVideoSoftMute(_:)`、 `disconnect(error:)`、 `getStats(handler:)` など) は actor 境界を越えないため、 `nonisolated(unsafe)` は不要です。

> **重要**
>
> `nonisolated(unsafe)` はコンパイラの検査を外すだけで、安全性を保証しません。
> 同じ `MediaChannel` / `MediaStream` / `ScreenCaptureSettings` を複数のスレッドから同時に操作していないことを確認した上で利用してください。

`MediaChannel` / `MediaStream` の参照を actor 境界へ渡す必要がある箇所は、 2026.4.0 で追加した `Sendable` な API へ置き換えることで減らせます。 [Sendable な API への移行](2026_3_to_2026_4.html#3c0265) をご確認ください。

> **ヒント**
>
> アプリの実装例については、 [sora-ios-sdk-samples](https://github.com/shiguredo/sora-ios-sdk-samples) をご確認ください。


### Sendable な API への移行

actor 境界や `@Sendable` クロージャへ値を渡す場合は、 2026.4.0 で追加した次の API を利用してください。

- RPC: `MediaChannel.sendableRPC`- 詳細は [Swift 6 言語モードに対応する Sendable な RPC を利用する](rpc.html#5baf81) をご確認ください
- 統計情報: `MediaChannel.getStatsSnapshot(handler:)` / `MediaChannel.getStatsSnapshot()`- 詳細は [Swift 6 言語モードに対応する Sendable な統計情報を取得する](get_stats.html#637049) をご確認ください
- イベント購読: `Sora.subscribeEvents(bufferingPolicy:)` / `MediaChannel.subscribeEvents(bufferingPolicy:)`- 詳細は [Swift 6 言語モードに対応する Sendable なイベントを購読する](callback.html#e0ef2b) をご確認ください
- 任意の JSON: `JSONValue`- 詳細は [任意の JSON を送受信する](rpc.html#0e8056) をご確認ください

`MediaChannel.rpc` / `MediaChannel.getStats(handler:)` とコールバック API は引き続き利用できます。
コールバックの型は変更していませんが、一部のコールバックは配送 executor が変わっています。詳細は [リリースノート](release.html) をご確認ください。

`MediaChannel.rpc` は互換 API として残していますが、将来のバージョンで削除される予定です。 RPC を actor 境界や `@Sendable` クロージャへ渡す場合は `MediaChannel.sendableRPC` へ移行してください。

### RPCErrorDetail.data の型変更

#### 概要

`RPCErrorDetail.data` の型を `Any?` から `JSONValue?` に変更しました。 `RPCErrorDetail` を `Sendable` な型にするためです。

`data` を `as? [String: Any]` のようにキャストしていたコードでは `always fails` の警告が出て常に `nil` になるため、 `JSONValue` の case 分岐へ書き換える必要があります。

#### 移行方法

```swift
// 移行前
if let errorData = detail.data as? [String: Any] {
  _ = errorData
}

// 移行後
if case .some(.object(let errorData)) = detail.data {
  _ = errorData
}
```

JSON-RPC 2.0 の `error.data` が省略された場合は `nil` 、 `null` の場合は `.null` になります。
数値は `JSONSerialization` を経由して `decimal` / `double` の case で復元されるため、小数の表記が変わることがあります (例: `0.1` は `.decimal(0.10000000000000001)` になります) 。

詳細は [エラーを扱う](rpc.html#e3b51e) をご確認ください。

### WebRTC の enum 型への CustomStringConvertible 準拠の削除

#### 概要

`RTCSignalingState` / `RTCIceConnectionState` / `RTCIceGatheringState` / `RTCDataChannelState` / `RTCDegradationPreference` / `RTCPriority` への `CustomStringConvertible` 準拠を削除しました。
別モジュールの型へプロトコル準拠を追加していた retroactive conformance の警告 (SE-0364) を解消するためです。

#### 影響を受けるケース

`description` を呼び出していたコードはコンパイルエラーになります。
文字列補間と `String(describing:)` は `RTCSignalingState(rawValue: 0)` のような raw 表現に変わります。

`RTCRtpParameters.description` からは `Optional(...)` が消え、未知の値は `"unknown(<rawValue>)"` になります ( `messagingError` の reason と SDK のログにも現れます) 。

#### 移行方法

enum の値を文字列化していた場合は `rawValue` を利用するか、利用側で文字列化する処理を用意してください。

```swift
// 移行前
print(state.description)

// 移行後 (raw value を利用する)
print(state.rawValue)
```

## Utilities.Stopwatch の非推奨

### 概要

`Utilities.Stopwatch` を非推奨にしました。
1 秒ごとに経過時間を `"時:分:秒"` の文字列で handler へ通知するカウンタで、 Sora iOS SDK 固有の機能ではなく SDK 内でも利用していないため、 2027 年中に廃止予定です。

### 移行方法

1 秒ごとの定期実行や表示の更新には、 `Timer` を MainActor 上のタイマーとして管理する方法か、 `ContinuousClock` と `Task.sleep(for:)` を組み合わせる方法へ移行してください。

経過時間の計測だけが目的の場合は、 `ContinuousClock` / `SuspendingClock` の `now` の差分 ( `Duration` ) へ移行してください。

用途によって移行先が異なるため、 SDK 固有の代替タイマーは提供しません。

## Utilities.randomString の非推奨

### 概要

`Utilities.randomString` を非推奨にしました。
紛らわしい文字 ( `l` / `I` / `O`) を除く 59 文字の英数字から `length` の長さの文字列を返す汎用ユーティリティで、 Sora iOS SDK 固有の機能ではなく SDK 内でも利用していないため、 2027 年中に廃止予定です。
シグネチャと挙動は変わりません。

### 移行方法

ランダムな文字列が必要な場合は、 `UUID` や標準の乱数 API ( `randomElement()` / `UInt32.random(in:)` など) へ移行してください。
`Utilities.randomString` とは出力の長さ指定と文字集合が異なるため、同じ出力が必要な場合は文字集合を自前で定義してください。

用途によって移行先が異なるため、 SDK 固有の代替ユーティリティは提供しません。

## その他の変更

移行時に確認が必要な 2026.4.0 の変更です。

- `CameraVideoCapturer.stream` は capturer が `MediaStream` を強参照しなくなり、利用者が capturer 以外に `MediaStream` の強参照を保たない場合は `nil` になります
- `MediaStream.send(videoFrame:)` は、映像フレームの加工と配送を非同期で行うようになりました (以前は呼び出し元の executor で同期的に実行していました) 。 `send` を呼び出した後にフレームとそれが保持する画素データを参照・変更していないか確認してください
- `VideoFilter` の `filter(videoFrame:)` はストリームごとの直列 executor で実行されるようになりました (以前は呼び出し元の executor で実行されていました) 。呼び出し元のスレッドで実行されることを前提にしていないか確認してください
- `MediaStream.terminate()` は冪等になり、以降に到着したフレームと renderer のコールバックを配送しなくなりました (以前は終了後も配送される場合がありました)
- `VideoRenderer` のコールバックは main queue で配送されるようになりました (以前はコールバックによって呼ばれるスレッドが異なる場合がありました) 。ストリームをまたぐコールバックの順序は保証しません
- `MediaChannel.setVideoHardMute(true)` の経路では `onSwitchVideo` が `VideoHardMuteActor` の executor で発火するようになりました (以前と異なるスレッドから呼ばれる場合があります)
- Sora から `"type": "redirect"` を受信すると、 SDK は接続を一度切断して指定された location に再接続します。このとき旧接続の DataChannel が無効化され、無効化が完了した後に開始した `MediaChannel.sendMessage` は `SoraError.messagingError` を返すようになりました (以前は旧 DataChannel へ送信されることがありました)
- `Configuration` の metadata と `dataChannels` の encode に失敗した場合は、接続開始前に `SoraError.configurationError` を返すようになりました (以前は接続がタイムアウトするか、接続時にプロセスが異常終了していました)
- connect message に載る数値の表記が変わることがあります ( `Float` / `Double` の指数表記の展開、 `-0.0` の符号など) 。 `dataChannels` を設定している場合は 17 桁表記が解消され `JSONEncoder` の表記に揃います
- JSON オブジェクトのキー順は不定になります ( JSON として等価です)

変更の全体は [リリースノート](release.html) をご確認ください。
