Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9c45e66
Duplicate RemoteFeedLoader as RemoteImageCommentsLoader
PortoCode Mar 12, 2025
c44604c
Delivers proper results on 2xx response
PortoCode Mar 12, 2025
7299a47
Add ImageComment data model
PortoCode Mar 12, 2025
c4169b0
Implement ImageComment mapping
PortoCode Mar 12, 2025
2053a1f
Move HTTPClient protocol and implementation to standalone folders rep…
PortoCode Mar 13, 2025
1e2b9af
Move Image Comments API to standalone folders representing modules
PortoCode Mar 13, 2025
94108e4
Move FeedImage mapping to the FeedItemsMapper
PortoCode Mar 13, 2025
d802731
Duplicate RemoteFeedLoader as RemoteLoader
PortoCode Mar 13, 2025
2340e6e
Implement generic RemoteLoader
PortoCode Mar 13, 2025
f6275d2
Replace RemoteImageCommentsLoader with generic RemoteLoader to remove…
PortoCode Mar 13, 2025
8ca3c38
Replace RemoteFeedLoader with generic RemoteLoader to remove duplication
PortoCode Mar 13, 2025
73a04db
Make RemoteLoader conform to FeedLoader in the Composition Root
PortoCode Mar 13, 2025
379aca1
Test FeedItemsMapper in isolation
PortoCode Mar 13, 2025
44c27c6
Move test helpers to shared scope
PortoCode Mar 13, 2025
e19e98b
Test ImageCommentsMapper in isolation
PortoCode Mar 13, 2025
40020bf
Move RemoteLoader composition to the Composition Root
PortoCode Mar 13, 2025
f48e83a
Replace RemoteLoader composition with HTTPClient publisher composed w…
PortoCode Mar 13, 2025
4b8e821
Remove unused RemoteLoader
PortoCode Mar 13, 2025
c3f7f1f
Remove FeedLoader protocol as we don't need it anymore - we're compos…
PortoCode Mar 13, 2025
8db30c0
Replace RemoteFeedImageDataLoader with HTTPClient publisher composed …
PortoCode Mar 13, 2025
a0bf944
Remove unused test spy
PortoCode Mar 13, 2025
83e1d2f
Remove blank spaces
PortoCode Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion EssentialApp/EssentialApp/CombineHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ import Foundation
import Combine
import EssentialFeed

public extension HTTPClient {
typealias Publisher = AnyPublisher<(Data, HTTPURLResponse), Error>

func getPublisher(url: URL) -> Publisher {
var task: HTTPClientTask?

return Deferred {
Future { completion in
task = self.get(from: url, completion: completion)
}
}
.handleEvents(receiveCancel: { task?.cancel() })
.eraseToAnyPublisher()
}
}

public extension FeedImageDataLoader {
typealias Publisher = AnyPublisher<Data, Error>

Expand Down Expand Up @@ -37,7 +53,7 @@ private extension FeedImageDataCache {
}
}

public extension FeedLoader {
public extension LocalFeedLoader {
typealias Publisher = AnyPublisher<[FeedImage], Error>

func loadPublisher() -> Publisher {
Expand Down
4 changes: 2 additions & 2 deletions EssentialApp/EssentialApp/FeedLoaderPresentationAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import EssentialFeed
import EssentialFeediOS

final class FeedLoaderPresentationAdapter: FeedViewControllerDelegate {
private let feedLoader: () -> FeedLoader.Publisher
private let feedLoader: () -> AnyPublisher<[FeedImage], Error>
private var cancellable: Cancellable?
var presenter: FeedPresenter?

init(feedLoader: @escaping () -> FeedLoader.Publisher) {
init(feedLoader: @escaping () -> AnyPublisher<[FeedImage], Error>) {
self.feedLoader = feedLoader
}

Expand Down
2 changes: 1 addition & 1 deletion EssentialApp/EssentialApp/FeedUIComposer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public final class FeedUIComposer {
private init() {}

public static func feedComposedWith(
feedLoader: @escaping () -> FeedLoader.Publisher,
feedLoader: @escaping () -> AnyPublisher<[FeedImage], Error>,
imageLoader: @escaping (URL) -> FeedImageDataLoader.Publisher
) -> FeedViewController {
let presentationAdapter = FeedLoaderPresentationAdapter(feedLoader: feedLoader)
Expand Down
19 changes: 9 additions & 10 deletions EssentialApp/EssentialApp/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
LocalFeedLoader(store: store, currentDate: Date.init)
}()

private let url = URL(string: "https://ile-api.essentialdeveloper.com/essential-feed/v1/feed")!

private lazy var remoteFeedLoader = RemoteFeedLoader(url: url, client: httpClient)
private let remoteURL = URL(string: "https://ile-api.essentialdeveloper.com/essential-feed/v1/feed")!

convenience init(httpClient: HTTPClient, store: FeedStore & FeedImageDataStore) {
self.init()
Expand Down Expand Up @@ -56,22 +54,23 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
localFeedLoader.validateCache { _ in }
}

private func makeRemoteFeedLoaderWithLocalFallback() -> FeedLoader.Publisher {
return remoteFeedLoader
.loadPublisher()
private func makeRemoteFeedLoaderWithLocalFallback() -> AnyPublisher<[FeedImage], Error> {
return httpClient
.getPublisher(url: remoteURL)
.tryMap(FeedItemsMapper.map)
.caching(to: localFeedLoader)
.fallback(to: localFeedLoader.loadPublisher)
}

private func makeLocalImageLoaderWithRemoteFallback(url: URL) -> FeedImageDataLoader.Publisher {
let remoteImageLoader = RemoteFeedImageDataLoader(client: httpClient)
let localImageLoader = LocalFeedImageDataLoader(store: store)

return localImageLoader
.loadImageDataPublisher(from: url)
.fallback(to: {
remoteImageLoader
.loadImageDataPublisher(from: url)
.fallback(to: { [httpClient] in
httpClient
.getPublisher(url: url)
.tryMap(FeedImageDataMapper.map)
.caching(to: localImageLoader, using: url)
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,33 @@
import Foundation
import EssentialFeed
import EssentialFeediOS
import Combine

extension FeedUIIntegrationTests {

class LoaderSpy: FeedLoader, FeedImageDataLoader {
class LoaderSpy: FeedImageDataLoader {

// MARK: - FeedLoader

private var feedRequests = [(FeedLoader.Result) -> Void]()
private var feedRequests = [PassthroughSubject<[FeedImage], Error>]()

var loadFeedCallCount: Int {
return feedRequests.count
}

func load(completion: @escaping (FeedLoader.Result) -> Void) {
feedRequests.append(completion)
func loadPublisher() -> AnyPublisher<[FeedImage], Error> {
let publisher = PassthroughSubject<[FeedImage], Error>()
feedRequests.append(publisher)
return publisher.eraseToAnyPublisher()
}

func completeFeedLoading(with feed: [FeedImage] = [], at index: Int = 0) {
feedRequests[index](.success(feed))
feedRequests[index].send(feed)
}

func completeFeedLoadingWithError(at index: Int = 0) {
let error = NSError(domain: "an error", code: 404)
feedRequests[index](.failure(error))
feedRequests[index].send(completion: .failure(error))
}

// MARK: - FeedImageDataLoader
Expand Down
Loading