Feature/xcode 26#233
Open
YutoMizutani wants to merge 2 commits into
Open
Conversation
added 2 commits
March 18, 2026 06:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OperantKit Xcode 26 / RxSwift 6 マイグレーションノート
概要
feature/xcode-26ブランチでは、OperantKit を RxSwift 4.x → 6.x へ移行し、Swift Package Manager の定義を swift-tools-version 4.2 → 5.9 に更新する作業を行った。変更一覧
1. Package.swift の更新
4.25.9"4.0.0" ..< "5.0.0"exact: "6.10.2""RxSwift"文字列指定.product(name:package:)形式"Sources"/"Tests""OperantKit"/"OperantKitTests"[.v5]2. RxSwift API の変更 (
.share(replay:)→.share(replay:scope:))RxSwift 6 では
share(replay:)のデフォルト scope が変わったため、明示的に.whileConnectedを指定する必要がある。対象ファイル:
Sources/Application/Extensions/ObservableType+.swiftSources/Application/Extensions/UIApplication+Rx.swiftSources/Application/Extensions/UIViewController+Rx.swiftExamples/iOS/RatChamber/.../SessionPresenter.swift3.
ObservableType+.swiftのジェネリクス型変更RxSwift 6 では
ObservableTypeの associated type エイリアスEが廃止され、Elementに統一された。storeメソッド等の型パラメータ表記をE→Elementに変更。対象ファイル:
Sources/Application/Extensions/ObservableType+.swift4.
Single.createクロージャの.error()→.failure()変更RxSwift 6 では
SingleEventが Swift 標準のResult<T, Error>に変更された。これによりsingle(.error(...))はsingle(.failure(...))に変更が必要。対象ファイル(計 6 ファイル、41 箇所):
Sources/Application/Protocols/Repositories/ScheduleRepository.swift(14 箇所)Sources/Domain/UseCases/Timer/CADisplayLinkTimerUseCase.swift(6 箇所) ← 修正漏れにより後から追加Sources/Domain/UseCases/Timer/CVDisplayLinkTimerUseCase.swift(6 箇所)Sources/Domain/UseCases/Timer/WhileLoopTimerUseCase.swift(6 箇所)Sources/Domain/UseCases/Timer/StepTimerUseCaseTimerUseCase.swift(6 箇所)Sources/Domain/UseCases/DiscreteTrialUseCase.swift(3 箇所)5.
ResponseEntityをclassのまま維持当初
class→structへの変更を試みたが、コードベース全体(54 ファイル)がdo(onNext:)クロージャ内での共有オブジェクト変更など参照セマンティクスに依存しているため、classに戻した。structに変更する場合は、Rx パイプライン全体のアーキテクチャ変更が必要。6.
.gitignoreの更新.swiftpm/ディレクトリを追加(Xcode の SPM メタデータを除外)vendorディレクトリを追加(Ruby bundler 用)7. Cartfile.resolved の更新
5.0.1→6.10.28. Gemfile.lock の更新
2.123.0→2.231.11.9.0→1.27.02.0.1→2.3.79. RatChamber (Example App) の Carthage → SPM 移行
RatChamber は Carthage で
../../../Carthage/Build/iOS/からフレームワークをリンクしていたが、Carthage/Build/iOSディレクトリが存在しないためリンクエラーが発生。変更内容:
Presenter.swift:protocol Presenter: class→protocol Presenter: AnyObjectRatChamber.xcodeproj/project.pbxproj:FRAMEWORK_SEARCH_PATHSから$(PROJECT_DIR)/../../../Carthage/Build/iOSを削除XCSwiftPackageProductDependencyをリンクOTHER_LDFLAGSに-framework OperantKitを追加(ワークスペース内のフレームワークターゲット)10. Makefile の Carthage → SPM 移行
CARTHAGE_COMMANDcarthageinstall-frameworkscarthage bootstrap --no-use-binariesxcodebuild -resolvePackageDependenciesupdate-frameworkscarthage update --no-use-binariesxcodebuild -resolvePackageDependenciesbuild-allcarthage build+ framework ビルドつまづきポイント・人間が気づきにくい箇所
.share(replay:)の scope 問題RxSwift 6 で
share(replay:)を呼ぶと、コンパイルは通る場合があるが、デフォルト scope が.foreverに変更されている。これにより、購読者がいなくなっても内部バッファがリセットされず、メモリリークや古い値の再送が発生する。明示的にscope: .whileConnectedを付けないと、RxSwift 4 時代と異なる挙動になる。E→Elementの型パラメータ変更RxSwift 6 で
ObservableTypeの associated type エイリアスEが廃止された。Eのままだとコンパイルエラーになる。エラーメッセージは「Cannot find type 'E' in scope」で出るため、RxSwift のバージョン差異が原因だと気づくのに時間がかかる。プロジェクト全体をEで検索しても一般的な識別子であるため、grep でのフィルタリングが困難。SingleEventのResult型への変更RxSwift 6 では
SingleEventがResult<T, Error>に変更された。.error()→.failure()、.success()はそのまま。エラーメッセージはType 'Result<..., any Error>' has no member 'error'と出るが、「Result 型」と「RxSwift の Single API 変更」が結びつかず、原因特定に時間がかかる。特にSingle.createのクロージャが多数あるプロジェクトでは修正漏れが起きやすい。#if os(iOS)によるプラットフォーム条件付きコンパイルの修正漏れCADisplayLinkTimerUseCase.swiftは#if os(iOS) || os(tvOS)で囲まれており、macOS ターゲットでビルドするとコンパイル対象外になるため、エラーが検出されない。実際に、他の Timer ファイル(WhileLoopTimerUseCase、CVDisplayLinkTimerUseCase、StepTimerUseCase)はすべて.failure()に修正済みだったにもかかわらず、CADisplayLinkTimerUseCaseだけが.error()のまま残存していた。iOS ターゲットでビルドして初めてエラーが顕在化する。プラットフォーム条件付きファイルは grep ベースの一括置換で対応すべき。ResponseEntityの class → struct 変更の危険性値型に変更すると、既存コードで 変数に代入して共有していた箇所が、コピーセマンティクスに変わる。Rx の
do(onNext:)クロージャ内でResponseEntityのプロパティを変更する副作用パターンが多数あり、structにすると'entity' is a 'let' constantエラーが発生する。仮にvarにしても、クロージャ内でのコピーへの変更は元のオブジェクトに反映されないため、無音でバグになる。この型はclassのまま維持する必要がある。Package.swiftのターゲットパス変更パスを
"Sources"→"OperantKit"に変更しているが、実際のファイルシステム上のディレクトリ構造が対応しているかの確認が必要。SPM はターゲットパスと実ディレクトリが一致しないとビルドに失敗するが、エラーメッセージが分かりにくい("no sources found" 等)。setup-structure.shはこの問題に対応するために用意されたと思われる。RatChamber の Carthage → SPM 移行時のリンクエラー
RatChamber は
../../../Carthage/Build/iOS/をFRAMEWORK_SEARCH_PATHSに指定して Carthage ビルド済みフレームワークを参照していた。Carthage を削除した後、単純にフレームワーク参照を削除するだけでは コンパイルは通るがリンクで失敗する。SPM パッケージは iOS ターゲットではフレームワークバンドル(.framework)ではなくオブジェクトファイル(.o)としてBUILT_PRODUCTS_DIRに配置されるため、-framework RxSwiftでリンクしても見つからない。RatChamber 自身のプロジェクトにもXCRemoteSwiftPackageReferenceとXCSwiftPackageProductDependencyを追加し、SPM が正しくリンクを構成するようにする必要がある。RxSwift の exact バージョン指定
exact指定は将来のパッチアップデートも受け取れなくなる。意図的なピン留めであれば問題ないが、通常は.upToNextMinor(from:)や.upToNextMajor(from:)の方が保守しやすい。エージェントの洞察
型推論の不一致:
Result型と RxSwift のSingleEventRxSwift 6 で
SingleEventがResult<Element, Swift.Error>の typealias に変更されたことにより、コンパイラのエラーメッセージがType 'Result<Void, any Error>' has no member 'error'と表示される。これは Swift 標準ライブラリのResult型のエラーであり、RxSwift の API 変更だと認識しづらい。特に.success()はそのまま動くため、.error()だけが壊れる非対称性があり、部分的にコンパイルが通る状態になりやすい。暗黙的なメモリリスク:
ResponseEntityの参照セマンティクス依存ResponseEntityをclassとして維持する必要があるが、これは Rx パイプライン内でdo(onNext:)を使った副作用パターン(外部オブジェクトのプロパティ変更)に全面的に依存しているためである。このパターンには以下のリスクがある:循環参照:
do(onNext:)クロージャがResponseEntityインスタンスを強参照で保持し、そのインスタンスが Observable チェーンを保持する場合、循環参照が発生する。現状のコードではResponseEntityは Observable を保持していないため直接的な問題はないが、将来の拡張時に注意が必要。スレッドセーフティ:
ResponseEntityのプロパティ(numOfResponses,milliseconds)が複数の Rx ストリームから同時に変更される可能性がある。classのプロパティへの同時書き込みはデータレースになる。WhileLoopTimerUseCaseがDispatchQueueを使用している箇所があり、マルチスレッド環境での使用が想定されている。Observable チェーン内での副作用の順序保証:
do(onNext:)内での変更は、後続のmap/flatMapに即座に反映されるが、複数のdo(onNext:)が異なるストリームで同じResponseEntityを変更する場合、変更順序は購読タイミングに依存する。ObservableType.Eの廃止パターンEは RxSwift 4/5 時代にObservableType.Elementの短縮エイリアスとして存在したが、Swift のジェネリクスシステムの改善に伴い RxSwift 6 で廃止された。エラーメッセージCannot find type 'E' in scopeは一見するとローカルなジェネリクスパラメータの問題に見え、RxSwift のマイグレーションガイドを参照しないと原因特定が困難。同様のエイリアス廃止が他の RxSwift プロトコル(ObserverType等)でも発生する可能性がある。プラットフォーム条件付きコンパイル (
#if os(...)) による修正の「死角」本マイグレーションで最も見落としやすかったのが、
#if os(iOS) || os(tvOS)で囲まれたCADisplayLinkTimerUseCase.swiftの修正漏れである。macOS ターゲットでのビルドでは当該ファイルがコンパイル対象外となり、エラーが一切検出されない。同じディレクトリ内の他3ファイル(CVDisplayLinkTimerUseCase、WhileLoopTimerUseCase、StepTimerUseCase)はすべて修正済みであったため、ディレクトリ単位での確認では「完了済み」と誤認しやすい。マイグレーション作業では 全プラットフォームでのビルド、または grep による機械的な一括置換 が不可欠である。IDEのビルドエラーだけに頼ると、条件付きコンパイルブロック内の未修正コードが残存するリスクがある。Carthage → SPM 移行時の「コンパイル成功・リンク失敗」の罠
Carthage から SPM への移行時、ワークスペース内で SPM パッケージが解決されていれば コンパイルは成功する(モジュールマップが
BUILT_PRODUCTS_DIRから解決される)。しかし、リンクフェーズで Carthage パスの.frameworkを探しに行きld: framework 'RxCocoa' not foundで失敗する。コンパイルが通るため「コード修正は完了」と誤認しやすいが、実際にはプロジェクトの依存管理の書き換えが未完了。SPM では各ターゲットにXCSwiftPackageProductDependencyを明示的に追加する必要があり、ワークスペース内で共有されている SPM パッケージは自動的にはサブプロジェクトにリンクされない。