Skip to content

Commit b2e6d88

Browse files
committed
Added Array support
1 parent 0592e03 commit b2e6d88

File tree

2 files changed

+143
-8
lines changed

2 files changed

+143
-8
lines changed

Sources/PublishedObject/PublishedObject.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ public struct PublishedObject<Value> {
4343
startListening(to: wrappedValue)
4444
}
4545

46+
public init<V>(wrappedValue: Value) where Value == [V], V: ObservableObject, V.ObjectWillChangePublisher == ObservableObjectPublisher {
47+
self.wrappedValue = wrappedValue
48+
self.cancellable = nil
49+
_startListening = { futureSelf, wrappedValue in
50+
let publisher = futureSelf._projectedValue
51+
let parent = futureSelf.parent
52+
for element in wrappedValue {
53+
futureSelf.cancellable = element.objectWillChange.sink { [parent] in
54+
parent.objectWillChange?()
55+
DispatchQueue.main.async {
56+
publisher.send(wrappedValue)
57+
}
58+
}
59+
}
60+
publisher.send(wrappedValue)
61+
}
62+
startListening(to: wrappedValue)
63+
}
64+
4665
public var wrappedValue: Value {
4766
willSet { parent.objectWillChange?() }
4867
didSet { startListening(to: wrappedValue) }

Tests/PublishedObjectTests/PublishedObjectTests.swift

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,24 @@ class OuterOptional: ObservableObject {
3333
}
3434
}
3535

36+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
37+
class OuterArray: ObservableObject {
38+
@PublishedObject var innerPublishedObject: [Inner]
39+
@Published var innerPublished: [Inner]
40+
41+
init(_ value: Int) {
42+
self.innerPublishedObject = [Inner(value)]
43+
self.innerPublished = [Inner(value)]
44+
}
45+
}
46+
3647
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
3748
final class PublishedObjectTests: XCTestCase {
3849
var cancellables: Set<AnyCancellable> = []
39-
40-
func testObjectWillChange() throws {
41-
let outer = Outer(1)
42-
50+
51+
var outer: Outer! = Outer(1)
52+
53+
func testObjectWillChange() {
4354
// Setting property on Outer (This will send an update with either @Published or @PublishedObject)
4455

4556
let exp1 = XCTestExpectation(description: "outer.objectWillChange will be called")
@@ -66,11 +77,10 @@ final class PublishedObjectTests: XCTestCase {
6677
wait(for: [exp4], timeout: 0.1)
6778
}
6879

69-
func testProjectedValue() throws {
70-
let outer = Outer(1)
71-
80+
func testProjectedValue() {
81+
7282
// Initial value
73-
83+
7484
let exp0 = XCTestExpectation(description: "projectedValue will be called")
7585
outer.$innerPublishedObject.first().sink { inner in
7686
if inner.value == 1 {
@@ -216,10 +226,116 @@ final class PublishedObjectTests: XCTestCase {
216226
wait(for: [exp4], timeout: 0.1)
217227
}
218228

229+
func testArrayObjectWillChange() {
230+
let outer = OuterArray(1)
231+
232+
// Setting property on Outer (This will send an update with either @Published or @PublishedObject)
233+
234+
let exp1 = XCTestExpectation(description: "outer.objectWillChange will be called")
235+
outer.objectWillChange.first().sink { exp1.fulfill() } .store(in: &cancellables)
236+
outer.innerPublishedObject = [Inner(2)]
237+
wait(for: [exp1], timeout: 0.1)
238+
239+
let exp2 = XCTestExpectation(description: "outer.objectWillChange will be called")
240+
outer.objectWillChange.first().sink { exp2.fulfill() } .store(in: &cancellables)
241+
outer.innerPublished = [Inner(2)]
242+
wait(for: [exp2], timeout: 0.1)
243+
244+
// Setting property on Inner (This will only send an update when using @PublishedObject)
245+
246+
let exp3 = XCTestExpectation(description: "outer.objectWillChange will be called")
247+
outer.objectWillChange.first().sink { exp3.fulfill() } .store(in: &cancellables)
248+
outer.innerPublishedObject[0].value = 3
249+
wait(for: [exp3], timeout: 0.1)
250+
251+
let exp4 = XCTestExpectation(description: "outer.objectWillChange will NOT be called")
252+
exp4.isInverted = true
253+
outer.objectWillChange.first().sink { exp4.fulfill() } .store(in: &cancellables)
254+
outer.innerPublished[0].value = 3
255+
wait(for: [exp4], timeout: 0.1)
256+
}
257+
258+
func testArrayProjectedValue() {
259+
let outer = OuterArray(1)
260+
261+
// Initial value
262+
263+
let exp0 = XCTestExpectation(description: "projectedValue will be called")
264+
outer.$innerPublishedObject.compactMap(\.first).first().sink { inner in
265+
if inner.value == 1 {
266+
exp0.fulfill()
267+
}
268+
}.store(in: &cancellables)
269+
wait(for: [exp0], timeout: 0.1)
270+
271+
let exp1 = XCTestExpectation(description: "projectedValue will be called")
272+
outer.$innerPublished.compactMap(\.first).first().sink { inner in
273+
if inner.value == 1 {
274+
exp1.fulfill()
275+
}
276+
}.store(in: &cancellables)
277+
wait(for: [exp1], timeout: 0.1)
278+
279+
280+
// Setting property on Outer (This will send an update with either @Published or @PublishedObject)
281+
282+
let exp2 = XCTestExpectation(description: "projectedValue will be called")
283+
outer.$innerPublishedObject.compactMap(\.first).dropFirst().first().sink { inner in
284+
if inner.value == 2 {
285+
exp2.fulfill()
286+
}
287+
}.store(in: &cancellables)
288+
outer.innerPublishedObject[0] = Inner(2)
289+
wait(for: [exp2], timeout: 0.1)
290+
291+
let exp3 = XCTestExpectation(description: "projectedValue will be called")
292+
outer.$innerPublished.compactMap(\.first).dropFirst().first().sink { inner in
293+
if inner.value == 2 {
294+
exp3.fulfill()
295+
}
296+
}.store(in: &cancellables)
297+
outer.innerPublished[0] = Inner(2)
298+
wait(for: [exp3], timeout: 0.1)
299+
300+
// Setting property on Inner (This will only send an update when using @PublishedObject)
301+
302+
let exp4 = XCTestExpectation(description: "projectedValue will be called")
303+
outer.$innerPublishedObject.compactMap(\.first).dropFirst().first().sink { inner in
304+
if inner.value == 3 {
305+
exp4.fulfill()
306+
}
307+
}.store(in: &cancellables)
308+
outer.innerPublishedObject[0].value = 3
309+
wait(for: [exp4], timeout: 0.1)
310+
311+
let exp5 = XCTestExpectation(description: "projectedValue will NOT be called")
312+
exp5.isInverted = true
313+
outer.$innerPublished.compactMap(\.first).dropFirst().first().sink { inner in
314+
if inner.value == 3 {
315+
exp5.fulfill()
316+
}
317+
}.store(in: &cancellables)
318+
outer.innerPublished[0].value = 3
319+
wait(for: [exp5], timeout: 0.1)
320+
}
321+
322+
func testMemoryLeak() {
323+
weak var weakOuter = outer
324+
testObjectWillChange()
325+
outer = nil
326+
XCTAssertNil(weakOuter)
327+
outer = Outer(1)
328+
weakOuter = outer
329+
testProjectedValue()
330+
outer = nil
331+
XCTAssertNil(weakOuter)
332+
}
333+
219334
static var allTests = [
220335
("testObjectWillChange", testObjectWillChange),
221336
("testProjectedValue", testProjectedValue),
222337
("testOptionalWithValue", testOptionalWithValue),
223338
("testOptionalWithoutValue", testOptionalWithoutValue),
339+
("testMemoryLeak", testMemoryLeak),
224340
]
225341
}

0 commit comments

Comments
 (0)