-
Notifications
You must be signed in to change notification settings - Fork 43
Combine Migration Guide
One of our goals is to let you use CombineX just like Apple Combine. However, due to the language limitation, replaceing every import Combine
with import CombineX
is not sufficient. for example, URLSession.dataTaskPublisher(for:)
can't be overloaded and always use Apple Combine. Here is the complete guide for migrating from Combine to CombineX.
It's recommanded to use CXShim
instead of hard coded CombineX
so your library can support both CombineX and Apple Combine.
Copy and paste the following code at the end of your Package.swift. It will be used in the later steps
enum CombineImplementation {
case combine
case combineX
case openCombine
static var `default`: CombineImplementation {
#if canImport(Combine)
return .combine
#else
return .combineX
#endif
}
init?(_ description: String) {
let desc = description.lowercased().filter { $0.isLetter }
switch desc {
case "combine": self = .combine
case "combinex": self = .combineX
case "opencombine": self = .openCombine
default: return nil
}
}
var swiftSettings: [SwiftSetting] {
switch self {
case .combine: return [.define("USE_COMBINE")]
case .combineX: return [.define("USE_COMBINEX")]
case .openCombine: return [.define("USE_OPEN_COMBINE")]
}
}
}
extension Optional where Wrapped: RangeReplaceableCollection {
mutating func append(contentsOf newElements: [Wrapped.Element]) {
if newElements.isEmpty { return }
if let wrapped = self {
self = wrapped + newElements
} else {
self = .init(newElements)
}
}
}
import Foundation
extension ProcessInfo {
var combineImplementation: CombineImplementation {
return environment["CX_COMBINE_IMPLEMENTATION"].flatMap(CombineImplementation.init) ?? .default
}
}
You package is likely requires macOS 10.15 / iOS 13. With CXShim
, it automatically support older version and linux:
let package = Package(
name: "MyAwesomeLibrary",
// no requirements needed when using CombineX
- platforms: [
- .macOS(.v10_15),
- .iOS(.v13),
- .tvOS(.v13),
- .watchOS(.v6),
- ],
products: [
...
// at the end of Package.swift
// you probably still need system requirements when using system Combine
+ if ProcessInfo.processInfo.combineImplementation == .combine {
+ package.platforms = [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)]
+ }
Foundation
somehow export Published and Observableobject from Combine. It introduces ambiguity:
import Foundation
import CombineX
class A: ObservableObject {}
// ❗️ 'ObservableObject' is ambiguous for type lookup in this context
// Foundation.ObservableObject or CombineX.ObservableObject?
CXShim
export desired type. You just need to export them in your module:
// create Typealias.swift
+ import CXShim
+
+ public typealias Published = CXShim.Published
+ public typealias ObservableObject = CXShim.ObservableObject
// Now you can use it normally
class A: ObservableObject {}
As described above, some Combine extensions on non-Combine type are not overloadable. They need to be accessed through cx
namespace:
let pub1 = [1, 2, 3].cx.publisher
// ^ ^
// Swift's world | | Combine's world
let pub2 = URLSession.shared.cx.dataTaskPublisher(for: request)
// ^ ^
// Foundation's world | | Combine's world
You also need .cx
to pass non-Combine type as Combine protocol:
pub.receive(on: DispatchQueue.main.cx)
// ^ pass as Scheduler
pub.decode(type: Model.self, decoder: JSONDecoder().cx)
// ^ pass as TopLevelDecoder
// practically `JSONDecoder` can conform to `CombineX.TopLevelDecoder`, but we don't do this for consistency.
Theres's also CX
namespace for types:
let pub: Optional.CX.Publisher = .init(42)
// ^ ^
// Swift Type | | Combine Type
However it becomes a little bit tricky on NSObject
Subclasses. The compiler can't distinguish between NSObject.CX
and, say, Timer.CX
. In this case, we have CXWrapper
:
CXWrappers.Timer.publish(...)
// CXWrappers.Timer is equivalent to Timer.CX, but without ambiguity issue
let dt: CXWrappers.DispatchQueue.SchedulerTimeType.Stride = .seconds(1)
// CXWrappers.DispatchQueue conforms to Schedular and have its SchedulerTimeType etc.
For consistency reason, you can still use CXWrappers
on non-NSObject types. It's recommanded to use CXWrappers
instead of CX
.
- let pub: Optional.CX.Publisher = .init(42)
+ let pub: CXWrappers.Optional.Publisher = .init(42)
Although CXShim
have dealt with most of the differences between underlying Combine, sometime you still want to handle the differences yourself. You can do it with predefined compilation conditions:
// at the end of Package.swift
let combineImp = ProcessInfo.processInfo.combineImplementation
for target in package.targets {
target.swiftSettings.append(contentsOf: combineImp.swiftSettings)
}
#if USE_COMBINE
// do something when use Apple Combine
#elseif USE_COMBINEX
// do something else when use CombineX
#endif