Skip to content

Commit 8f44ddc

Browse files
committed
Add async composition to load more feed pages
1 parent 1a029ce commit 8f44ddc

File tree

2 files changed

+56
-13
lines changed

2 files changed

+56
-13
lines changed

EssentialApp/EssentialApp/SceneDelegate.swift

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,35 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
143143
}
144144
.eraseToAnyPublisher()
145145
}
146+
147+
private func loadMoreRemoteFeed(last: FeedImage?) async throws -> Paginated<FeedImage> {
148+
async let cachedItems = try await loadLocalFeed()
149+
async let newItems = try await loadRemoteFeed(after: last)
150+
151+
let items = try await cachedItems + newItems
152+
153+
await store.schedule { [store] in
154+
let localFeedLoader = LocalFeedLoader(store: store, currentDate: Date.init)
155+
try? localFeedLoader.save(items)
156+
}
157+
158+
return try await makePage(items: items, last: newItems.last)
159+
}
146160

147161
private func makeRemoteLoadMoreLoader(last: FeedImage?) -> AnyPublisher<Paginated<FeedImage>, Error> {
148-
localFeedLoader.loadPublisher()
149-
.zip(makeRemoteFeedLoader(after: last))
150-
.map { (cachedItems, newItems) in
151-
(cachedItems + newItems, newItems.last)
162+
Deferred {
163+
Future { completion in
164+
Task.immediate {
165+
do {
166+
let feed = try await self.loadMoreRemoteFeed(last: last)
167+
completion(.success(feed))
168+
} catch {
169+
completion(.failure(error))
170+
}
171+
}
152172
}
153-
.map(makePage)
154-
.receive(on: scheduler)
155-
.caching(to: localFeedLoader)
156-
.subscribe(on: scheduler)
157-
.eraseToAnyPublisher()
173+
}
174+
.eraseToAnyPublisher()
158175
}
159176

160177
private func makeRemoteFeedLoader(after: FeedImage? = nil) -> AnyPublisher<[FeedImage], Error> {

EssentialApp/EssentialAppTests/FeedAcceptanceTests.swift

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,27 @@ import EssentialFeediOS
1111
class FeedAcceptanceTests: XCTestCase {
1212

1313
func test_onLaunch_displaysRemoteFeedWhenCustomerHasConnectivity() throws {
14-
let feed = try launch(httpClient: .online(response), store: .empty)
14+
let store = try CoreDataFeedStore.empty
15+
let feed = try launch(httpClient: .online(response), store: store)
1516

1617
XCTAssertEqual(feed.numberOfRenderedFeedImageViews(), 2)
1718
XCTAssertEqual(feed.renderedFeedImageData(at: 0), makeImageData0())
1819
XCTAssertEqual(feed.renderedFeedImageData(at: 1), makeImageData1())
1920
XCTAssertTrue(feed.canLoadMoreFeed)
2021

21-
feed.simulateLoadMoreFeedAction()
22+
try store.withWaitingChanges {
23+
feed.simulateLoadMoreFeedAction()
24+
}
2225

2326
XCTAssertEqual(feed.numberOfRenderedFeedImageViews(), 3)
2427
XCTAssertEqual(feed.renderedFeedImageData(at: 0), makeImageData0())
2528
XCTAssertEqual(feed.renderedFeedImageData(at: 1), makeImageData1())
2629
XCTAssertEqual(feed.renderedFeedImageData(at: 2), makeImageData2())
2730
XCTAssertTrue(feed.canLoadMoreFeed)
2831

29-
feed.simulateLoadMoreFeedAction()
32+
try store.withWaitingChanges {
33+
feed.simulateLoadMoreFeedAction()
34+
}
3035

3136
XCTAssertEqual(feed.numberOfRenderedFeedImageViews(), 3)
3237
XCTAssertEqual(feed.renderedFeedImageData(at: 0), makeImageData0())
@@ -41,7 +46,9 @@ class FeedAcceptanceTests: XCTestCase {
4146
let onlineFeed = try launch(httpClient: .online(response), store: sharedStore)
4247
onlineFeed.simulateFeedImageViewVisible(at: 0)
4348
onlineFeed.simulateFeedImageViewVisible(at: 1)
44-
onlineFeed.simulateLoadMoreFeedAction()
49+
try sharedStore.withWaitingChanges {
50+
onlineFeed.simulateLoadMoreFeedAction()
51+
}
4552
onlineFeed.simulateFeedImageViewVisible(at: 2)
4653

4754
let offlineFeed = try launch(httpClient: .offline, store: sharedStore)
@@ -186,6 +193,25 @@ class FeedAcceptanceTests: XCTestCase {
186193

187194
@MainActor
188195
extension CoreDataFeedStore {
196+
private struct Timeout: Error {}
197+
198+
func withWaitingChanges(_ action: () -> Void, timeout: TimeInterval = 1) throws {
199+
let state = try retrieve()?.timestamp
200+
action()
201+
202+
let maxDate = Date() + timeout
203+
204+
while Date() <= maxDate {
205+
if try retrieve()?.timestamp != state {
206+
return
207+
}
208+
209+
RunLoop.current.run(until: Date())
210+
}
211+
212+
throw Timeout()
213+
}
214+
189215
static var empty: CoreDataFeedStore {
190216
get throws {
191217
try CoreDataFeedStore(storeURL: URL(fileURLWithPath: "/dev/null"), contextQueue: .main)

0 commit comments

Comments
 (0)