Skip to content

Commit 50c1938

Browse files
committed
Added bridge between CurrentValuePublisher and @Published
1 parent 4a9e618 commit 50c1938

3 files changed

Lines changed: 157 additions & 100 deletions

File tree

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import PackageDescription
55
let package = Package(
66
name: "CombineExtensions",
77
platforms: [
8-
.macOS(.v10_15),
9-
.iOS(.v13),
8+
.macOS(.v11),
9+
.iOS(.v14),
1010
],
1111
products: [
1212
.library(
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Combine
2+
3+
extension CurrentValuePublisher {
4+
5+
/// Creates a `CurrentValuePublisher` from a `@Published` property’s publisher.
6+
///
7+
/// The resulting publisher emits the initial value of the `@Published` property, followed
8+
/// by all subsequent values.
9+
///
10+
/// - Parameter publisher: A publisher associated with a `@Published` property.
11+
public convenience init(
12+
_ publisher: Published<Output>.Publisher
13+
) where Failure == Never {
14+
var initialValue: Output!
15+
16+
// `Published.Publisher`, similarly to `CurrentValueSubject` and ultimately also
17+
// `CurrentValuePublisher`, sends its current value to a subscriber upon subscription.
18+
// We leverage this behavior for obtaining the current value with a short-lived
19+
// subscription and skip it in the upstream publisher.
20+
_ = publisher
21+
.first()
22+
.sink { initialValue = $0 }
23+
24+
self.init(
25+
initial: initialValue,
26+
upstream: publisher.dropFirst()
27+
)
28+
}
29+
30+
}
31+
32+
extension Published {
33+
34+
/// Creates a `@Published` property wrapper that reflects the values of a `CurrentValuePublisher`.
35+
///
36+
/// This is typically used to bind values from a `CurrentValuePublisher` to a `@Published`
37+
/// property in the initializer of an observable object. The property wrapper has to be
38+
/// assigned via `self._property = Published(publisher)`.
39+
///
40+
/// - Parameter publisher: A `CurrentValuePublisher` whose values the property wrapper
41+
/// will reflect.
42+
public init(_ publisher: CurrentValuePublisher<Publisher.Output, Publisher.Failure>) {
43+
self.init(initialValue: publisher.value)
44+
publisher.assign(to: &projectedValue)
45+
}
46+
47+
/// Creates a `@Published` property wrapper that reflects the values of a `CurrentValueSubject`.
48+
///
49+
/// This is typically used to bind values from a `CurrentValueSubject` to a `@Published`
50+
/// property in the initializer of an observable object. The property wrapper has to be
51+
/// assigned via `self._property = Published(subject)`.
52+
///
53+
/// - Parameter publisher: A `CurrentValueSubject` whose values the property wrapper
54+
/// will reflect.
55+
public init(_ subject: CurrentValueSubject<Publisher.Output, Publisher.Failure>) {
56+
self.init(CurrentValuePublisher(subject))
57+
}
58+
59+
}

Tests/CombineExtensionsTests/CurrentValuePublisher/CurrentValuePublisherTests.swift

Lines changed: 96 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -269,93 +269,91 @@ class CurrentValuePublisherTests: XCTestCase {
269269
XCTAssertEqual(subject.value, "third")
270270
}
271271

272-
// // ============================================================================ //
273-
// // MARK: - Published
274-
// // ============================================================================ //
275-
//
276-
// func testPublishedToCurrentValuePublisher_accessingValue() {
277-
// let object = MutableTestObject(initialValue: "initial")
278-
// let publisher = object.$value.toCurrentValuePublisher()
279-
//
280-
// XCTAssertEqual(publisher.value, "initial")
281-
//
282-
// object.value = "second"
283-
// XCTAssertEqual(publisher.value, "second")
284-
//
285-
// object.value = "third"
286-
// XCTAssertEqual(publisher.value, "third")
287-
// }
288-
//
289-
// func testPublishedToCurrentValuePublisher_receivingValues() {
290-
// var cancellables = Set<AnyCancellable>()
291-
// var values = [String]()
292-
//
293-
// let object = MutableTestObject(initialValue: "initial")
294-
//
295-
// object.$value
296-
// .toCurrentValuePublisher()
297-
// .sink { values.append($0) }
298-
// .store(in: &cancellables)
299-
//
300-
// XCTAssertEqual(values, ["initial"])
301-
//
302-
// object.value = "second"
303-
// XCTAssertEqual(values, ["initial", "second"])
304-
//
305-
// object.value = "third"
306-
// XCTAssertEqual(values, ["initial", "second", "third"])
307-
// }
308-
//
309-
// func testPublishedToCurrentValuePublisher_cancellation() {
310-
// var cancellables = Set<AnyCancellable>()
311-
// var values = [String]()
312-
//
313-
// let object = MutableTestObject(initialValue: "initial")
314-
//
315-
// object.$value
316-
// .toCurrentValuePublisher()
317-
// .sink { values.append($0) }
318-
// .store(in: &cancellables)
319-
//
320-
// XCTAssertEqual(values, ["initial"])
321-
//
322-
// object.value = "second"
323-
// XCTAssertEqual(values, ["initial", "second"])
324-
//
325-
// cancellables.forEach { $0.cancel() }
326-
// XCTAssertEqual(values, ["initial", "second"])
327-
//
328-
// object.value = "third"
329-
// XCTAssertEqual(values, ["initial", "second"])
330-
// }
331-
//
332-
// func testCurrentValuePublisherToPublished_accessingValue() {
333-
// let subject = CurrentValueSubject<String, Never>("initial")
334-
// let publisher = CurrentValuePublisher<String, Never>(subject)
335-
// let object = ImmutableTestObject(publisher: publisher)
336-
//
337-
// XCTAssertEqual(object.value, "initial")
338-
//
339-
// subject.value = "second"
340-
// XCTAssertEqual(object.value, "second")
341-
//
342-
// subject.value = "third"
343-
// XCTAssertEqual(object.value, "third")
344-
// }
345-
//
346-
// func testCurrentValuePublisherToPublished_completionFinished() {
347-
// let subject = CurrentValueSubject<String, Never>("initial")
348-
// let publisher = CurrentValuePublisher<String, Never>(subject)
349-
// let object = ImmutableTestObject(publisher: publisher)
350-
//
351-
// XCTAssertEqual(object.value, "initial")
352-
//
353-
// subject.send("second")
354-
// XCTAssertEqual(object.value, "second")
355-
//
356-
// subject.send(completion: .finished)
357-
// XCTAssertEqual(object.value, "second")
358-
// }
272+
// ============================================================================ //
273+
// MARK: - Published
274+
// ============================================================================ //
275+
276+
func testPublishedToCurrentValuePublisher_accessingValue() {
277+
let object = MutableObservableObject(initialValue: "initial")
278+
let publisher = CurrentValuePublisher(object.$value)
279+
280+
XCTAssertEqual(publisher.value, "initial")
281+
282+
object.value = "second"
283+
XCTAssertEqual(publisher.value, "second")
284+
285+
object.value = "third"
286+
XCTAssertEqual(publisher.value, "third")
287+
}
288+
289+
func testPublishedToCurrentValuePublisher_receivingValues() {
290+
var cancellables = Set<AnyCancellable>()
291+
var values = [String]()
292+
293+
let object = MutableObservableObject(initialValue: "initial")
294+
295+
CurrentValuePublisher(object.$value)
296+
.sink { values.append($0) }
297+
.store(in: &cancellables)
298+
299+
XCTAssertEqual(values, ["initial"])
300+
301+
object.value = "second"
302+
XCTAssertEqual(values, ["initial", "second"])
303+
304+
object.value = "third"
305+
XCTAssertEqual(values, ["initial", "second", "third"])
306+
}
307+
308+
func testPublishedToCurrentValuePublisher_cancellation() {
309+
var cancellables = Set<AnyCancellable>()
310+
var values = [String]()
311+
312+
let object = MutableObservableObject(initialValue: "initial")
313+
314+
CurrentValuePublisher(object.$value)
315+
.sink { values.append($0) }
316+
.store(in: &cancellables)
317+
318+
XCTAssertEqual(values, ["initial"])
319+
320+
object.value = "second"
321+
XCTAssertEqual(values, ["initial", "second"])
322+
323+
cancellables.forEach { $0.cancel() }
324+
XCTAssertEqual(values, ["initial", "second"])
325+
326+
object.value = "third"
327+
XCTAssertEqual(values, ["initial", "second"])
328+
}
329+
330+
func testCurrentValuePublisherToPublished_accessingValue() {
331+
let subject = CurrentValueSubject<String, Never>("initial")
332+
let publisher = CurrentValuePublisher<String, Never>(subject)
333+
let object = ImmutableObservableObject(publisher: publisher)
334+
335+
XCTAssertEqual(object.value, "initial")
336+
337+
subject.value = "second"
338+
XCTAssertEqual(object.value, "second")
339+
340+
subject.value = "third"
341+
XCTAssertEqual(object.value, "third")
342+
}
343+
344+
func testCurrentValuePublisherToPublished_completionFinished() {
345+
let subject = CurrentValueSubject<String, Never>("initial")
346+
let publisher = CurrentValuePublisher<String, Never>(subject)
347+
let object = ImmutableObservableObject(publisher: publisher)
348+
349+
XCTAssertEqual(object.value, "initial")
350+
351+
subject.send("second")
352+
XCTAssertEqual(object.value, "second")
353+
354+
subject.send(completion: .finished)
355+
XCTAssertEqual(object.value, "second")
356+
}
359357

360358
// ============================================================================ //
361359
// MARK: - Map Operator
@@ -946,7 +944,7 @@ class CurrentValuePublisherTests: XCTestCase {
946944

947945
fileprivate struct TestError: Error, Equatable {}
948946

949-
fileprivate class MutableTestObject {
947+
fileprivate class MutableObservableObject: ObservableObject {
950948

951949
fileprivate init(initialValue: String) {
952950
self.value = initialValue
@@ -957,13 +955,13 @@ fileprivate class MutableTestObject {
957955

958956
}
959957

960-
//fileprivate class ImmutableTestObject {
961-
//
962-
// fileprivate init(publisher: CurrentValuePublisher<String, Never>) {
963-
// self._value = Published(publisher)
964-
// }
965-
//
966-
// @Published
967-
// fileprivate private(set) var value: String
968-
//
969-
//}
958+
fileprivate class ImmutableObservableObject {
959+
960+
fileprivate init(publisher: CurrentValuePublisher<String, Never>) {
961+
self._value = Published(publisher)
962+
}
963+
964+
@Published
965+
fileprivate private(set) var value: String
966+
967+
}

0 commit comments

Comments
 (0)