Skip to content

Commit

Permalink
Combine support for ApexyLoader (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
subdan authored Aug 23, 2021
1 parent 50991f5 commit 8f455aa
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 4 deletions.
28 changes: 28 additions & 0 deletions Documentation/loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,34 @@ final class ProfileViewController: UIViewController {
}
```

## Observing loading state via Combine

To keep track of the loader state via Combine use `statePublisher`.

```swift
final class ProfileViewController: UIViewController {
private var bag = Set<AnyCancellable>()
...
override func viewDidLoad() {
super.viewDidLoad()
userProfileLoader.statePublisher.sink { [weak self] newState in
guard let self = self else { return }

switch newState {
case .initial:
//
case .loading(let cache):
//
case .success(let content):
//
case .failure(let error, let cache):
//
}
}.store(in: &bag)
}
}
```

## Use cases

ApexyLoader used in the following scenarios:
Expand Down
28 changes: 28 additions & 0 deletions Documentation/loader_ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,34 @@ final class ProfileViewController: UIViewController {
}
```

## Отслеживание состояния загрузки через Combine

Чтобы следить за состоянием загрузчика с помощью Combine используйте паблишер `statePublisher`.

```swift
final class ProfileViewController: UIViewController {
private var bag = Set<AnyCancellable>()
...
override func viewDidLoad() {
super.viewDidLoad()
userProfileLoader.statePublisher.sink { [weak self] newState in
guard let self = self else { return }

switch newState {
case .initial:
//
case .loading(let cache):
//
case .success(let content):
//
case .failure(let error, let cache):
//
}
}.store(in: &bag)
}
}
```

## Сценарии использования

ApexyLoader применяется когда:
Expand Down
18 changes: 17 additions & 1 deletion Sources/ApexyLoader/ContentLoader.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#if canImport(Combine)
import Combine
#endif
import Foundation

private final class StateChangeHandler {
Expand Down Expand Up @@ -28,11 +31,24 @@ open class ContentLoader<Content>: ObservableLoader {
public var state: LoadingState<Content> = .initial {
didSet {
stateHandlers.forEach { $0.notify() }

if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
stateSubject.send(state)
}
}
}

@available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *)
private lazy var stateSubject = CurrentValueSubject<LoadingState<Content>, Never>(.initial)

/// Content loading status. The default value is `.initial`.
///
/// - Remark: To change state use `update(_:)`.
@available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *)
public lazy var statePublisher: AnyPublisher<LoadingState<Content>, Never> = stateSubject.eraseToAnyPublisher()

public init() {}

// MARK: - ObservableLoader

/// Starts state observing.
Expand Down
80 changes: 77 additions & 3 deletions Tests/ApexyLoaderTests/ContentLoaderTests.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
@testable import ApexyLoader
import Combine
import XCTest

final class ContentLoaderTests: XCTestCase {

private var contentLoader: ContentLoader<Int>!
private var numberOfChanges = 0
private var observation: LoaderObservation!


private var bag = Set<AnyCancellable>()
private var receivedValues = [LoadingState<Int>]()

override func setUp() {
super.setUp()

Expand All @@ -15,7 +19,13 @@ final class ContentLoaderTests: XCTestCase {
observation = contentLoader.observe { [weak self] in
self?.numberOfChanges += 1
}


receivedValues.removeAll()

contentLoader.statePublisher.sink(receiveCompletion: { _ in }) { loadingState in
self.receivedValues.append(loadingState)
}.store(in: &bag)

XCTAssertTrue(
contentLoader.observations.isEmpty,
"No observation of other loaders")
Expand All @@ -32,6 +42,15 @@ final class ContentLoaderTests: XCTestCase {
numberOfChanges, 0,
"The change handler didn‘t triggered because the observation was canceled")
}

func testCancelObservationCombine() {
bag.removeAll()
contentLoader.state = .success(content: 10)
XCTAssertEqual(
receivedValues,
[.initial],
"The change handler didn‘t triggered because the observation was canceled")
}

func testStartLoading() {
XCTAssertTrue(
Expand All @@ -54,12 +73,29 @@ final class ContentLoaderTests: XCTestCase {
numberOfChanges, 1,
"The change handler did NOT triggered")
}

func testStartLoadingCombine() {
XCTAssertTrue(
contentLoader.startLoading(),
"Loading has begun")
XCTAssertEqual(
receivedValues,
[.initial, .loading(cache: nil)],
"State of the loader must be loading")
XCTAssertFalse(
contentLoader.startLoading(),
"The second loading didn‘t start before the end of the first one.")
XCTAssertEqual(
receivedValues,
[.initial, .loading(cache: nil)],
"The load status has NOT changed")
}

func testFinishLoading() {
contentLoader.finishLoading(.success(12))
XCTAssertTrue(
contentLoader.state == .success(content: 12),
"Succesfull loading state")
"Successfully loading state")
XCTAssertEqual(
numberOfChanges, 1,
"The change handler triggered")
Expand All @@ -73,6 +109,24 @@ final class ContentLoaderTests: XCTestCase {
numberOfChanges, 2,
"The handler triggered")
}

func testFinishLoadingCombine() {
contentLoader.finishLoading(.success(12))
XCTAssertEqual(
receivedValues,
[.initial, .success(content: 12)],
"Successfully loading state")

receivedValues.removeAll()

let error = URLError(.networkConnectionLost)
contentLoader.finishLoading(.failure(error))

XCTAssertEqual(
receivedValues,
[.failure(error: error, cache: 12)],
"The state must me failure with cache")
}

func testUpdate() {
contentLoader.update(.initial)
Expand All @@ -90,4 +144,24 @@ final class ContentLoaderTests: XCTestCase {
numberOfChanges, 1,
"The state didn't changed and the handler didn't triggered")
}

func testUpdateCombine() {
contentLoader.update(.initial)
XCTAssertEqual(
receivedValues,
[.initial],
"The state didn't change and the handler didn't triggered")

contentLoader.update(.success(content: 1))
XCTAssertEqual(
receivedValues,
[.initial, .success(content: 1)],
"The state changed and the handler triggered")

contentLoader.update(.success(content: 1))
XCTAssertEqual(
receivedValues,
[.initial, .success(content: 1)],
"The state didn't changed and the handler didn't triggered")
}
}

0 comments on commit 8f455aa

Please sign in to comment.