Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion EssentialFeed/EssentialFeed/Feed Cache/LocalFeedLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public final class LocalFeedLoader: FeedLoader {

extension LocalFeedLoader {
public typealias LoadResult = FeedLoader.Result
public func load(completion: @escaping (LoadResult) -> Void) {
public func load(completion: @escaping (LoadResult) -> Void) -> FeedLoaderTask? {
store.retrieve { [weak self] result in
guard let self else { return }
switch result {
Expand All @@ -32,6 +32,7 @@ extension LocalFeedLoader {
completion(.success([]))
}
}
return nil
}
}

Expand Down
6 changes: 5 additions & 1 deletion EssentialFeed/EssentialFeed/Feed Feature/FeedLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

import Foundation

public protocol FeedLoaderTask {
func cancel()
}

public protocol FeedLoader {
typealias Result = Swift.Result<[FeedImage], Error>
func load(completion: @escaping (Result) -> Void)
@discardableResult
func load(completion: @escaping (Result) -> Void) -> FeedLoaderTask?
}
3 changes: 2 additions & 1 deletion EssentialFeed/EssentialFeed/FeedApi/RemoteFeedLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public final class RemoteFeedLoader: FeedLoader {
self.client = client
}

public func load(completion: @escaping (Result) -> Void) {
public func load(completion: @escaping (Result) -> Void) -> FeedLoaderTask? {
client.get(from: url) { [weak self] result in
guard self != nil else { return }
switch result {
Expand All @@ -33,6 +33,7 @@ public final class RemoteFeedLoader: FeedLoader {
completion(.failure(Error.connectivity))
}
}
return nil
}

private static func map(_ data: Data, from response: HTTPURLResponse) -> Result {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ final class FeedLoaderPresentationAdapter: FeedViewControllerDelegate {
private let feedLoader: FeedLoader
var presenter: FeedPresenter?

private var currentTask: FeedLoaderTask?
init(feedLoader: FeedLoader) {
self.feedLoader = feedLoader
}

func loadFeed() {
presenter?.didStartLoadingFeed()
feedLoader.load { [weak self] result in
currentTask = feedLoader.load { [weak self] result in
switch result {
case let .success(feed):
self?.presenter?.didFinishLoadingFeed(with: feed)
Expand All @@ -32,4 +33,9 @@ final class FeedLoaderPresentationAdapter: FeedViewControllerDelegate {
func didRequestFeedRefresh() {
loadFeed()
}
}

func didRequestFeedLoadCancel() {
currentTask?.cancel()
currentTask = nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ final class MainQueueDispatchDecorator<T> {
}

extension MainQueueDispatchDecorator: FeedLoader where T == FeedLoader {
func load(completion: @escaping (FeedLoader.Result) -> Void) {
func load(completion: @escaping (FeedLoader.Result) -> Void) -> FeedLoaderTask? {
decoratee.load { result in
Self.dispatch {
completion(result)
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import UIKit

protocol FeedViewControllerDelegate {
func didRequestFeedRefresh()
func didRequestFeedLoadCancel()
}

public final class FeedViewController: UITableViewControllerExtendedLifecycle, UITableViewDataSourcePrefetching {
Expand All @@ -28,6 +29,11 @@ public final class FeedViewController: UITableViewControllerExtendedLifecycle, U
super.viewDidLoad()
}

public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
delegate?.didRequestFeedLoadCancel()
}

public override func viewFirstAppearance() {
super.viewFirstAppearance()
refresh()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ import EssentialFeediOS

final class FeedUIIntegrationTests: XCTestCase {

func test_viewWillDisappear_requestsFeedLoadCancellation() {
let (sut, loader) = makeSUT()
sut.simulateAppearance()
sut.simulateDisappearance()
XCTAssertEqual(loader.feedCancelRequestsCount, 1)
}

func test_feedView_hasTitle() {
let (sut, _) = makeSUT()

Expand Down Expand Up @@ -397,10 +404,15 @@ final class FeedUIIntegrationTests: XCTestCase {

private(set) var feedRequests = [(FeedLoader.Result) -> Void]()
var loadFeedCallCount: Int { feedRequests.count }


var feedCancelRequestsCount = 0


func load(completion: @escaping (FeedLoader.Result) -> Void) {
func load(completion: @escaping (FeedLoader.Result) -> Void) -> FeedLoaderTask? {
feedRequests.append(completion)
return TaskSpy { [weak self] in
self?.feedCancelRequestsCount += 1
}
}


Expand All @@ -414,7 +426,7 @@ final class FeedUIIntegrationTests: XCTestCase {

// MARK: - FeedImageDataLoader

private struct TaskSpy: FeedImageDataLoaderTask {
private struct TaskSpy: FeedImageDataLoaderTask, FeedLoaderTask {
let cancelCallback: () -> Void
func cancel() {
cancelCallback()
Expand Down Expand Up @@ -517,6 +529,11 @@ private extension FeedViewController {
endAppearanceTransition()
}

func simulateDisappearance() {
beginAppearanceTransition(false, animated: false)
endAppearanceTransition()
}

func replaceRefreshControlWithFake() {
let fake = FakeRefreshControl()
refreshControl?.allTargets.forEach{ target in
Expand Down