Skip to content

Commit

Permalink
Add async operations and pagination completion (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattfaluotico authored Apr 3, 2024
1 parent bde93ca commit 7b36ee5
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 15 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file.
`FetchRequests` adheres to [Semantic Versioning](https://semver.org/).

## 6.1.0
Release TBD

* Adds async operations for fetch and resort
* Adds completion closure to performPagination, with a boolean value indicating if values were returned or not

## [6.0.3](https://github.com/square/FetchRequests/releases/tag/6.0.3)
Released on 2024-03-06

Expand All @@ -17,7 +23,7 @@ Released on 2023-06-23

* The demo now includes a SwiftUI example
* Fix for SwiftUI when a FetchDefinition request is synchronous

## [6.0](https://github.com/square/FetchRequests/releases/tag/6.0.0)
Released on 2023-04-05

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ public class FetchedResultsController<FetchedObject: FetchableObject>: NSObject,
return new
}

private let debounceInsertsAndReloads: Bool
internal let debounceInsertsAndReloads: Bool

private var objectsToReload: Set<FetchedObject> = []
private var objectsToInsert: OrderedSet<FetchedObject> = []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@ public protocol FetchedResultsControllerProtocol<FetchedObject>: DoublyObservabl
func indexPath(for object: FetchedObject) -> IndexPath?
}

// MARK: - Async

public extension FetchedResultsControllerProtocol {
@MainActor
func performFetch() async {
await withCheckedContinuation { continuation in
performFetch {
continuation.resume()
}
}
}

@MainActor
func resort(using newSortDescriptors: [NSSortDescriptor]) async {
await withCheckedContinuation { continuation in
resort(using: sortDescriptors) {
continuation.resume()
}
}
}
}

// MARK: - Index Paths

public extension FetchedResultsControllerProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ extension PausableFetchedResultsController: FetchedResultsControllerProtocol {
public var sections: [Section] {
sectionsSnapshot ?? controller.sections
}

internal var debounceInsertsAndReloads: Bool {
controller.debounceInsertsAndReloads
}
}

// MARK: - InternalFetchResultsControllerProtocol
Expand Down
38 changes: 34 additions & 4 deletions FetchRequests/Sources/Requests/PaginatingFetchDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,29 @@ public class PaginatingFetchDefinition<FetchedObject: FetchableObject>: FetchDef
private extension InternalFetchResultsControllerProtocol {
@MainActor
func performPagination(
with paginationRequest: PaginatingFetchDefinition<FetchedObject>.PaginationRequest
with paginationRequest: PaginatingFetchDefinition<FetchedObject>.PaginationRequest,
willDebounceInsertsAndReloads: Bool,
completion: @escaping @MainActor (_ hasPageResults: Bool) -> Void
) {
let currentResults = self.fetchedObjects
paginationRequest(currentResults) { [weak self] pageResults in
guard let pageResults else {
completion(false)
return
}

performOnMainThread {
self?.manuallyInsert(objects: pageResults, emitChanges: true)
}

if willDebounceInsertsAndReloads {
// force this to run on the _next_ run loop, at which point any debounced insertions should have happened
DispatchQueue.main.async {
completion(!pageResults.isEmpty)
}
} else {
completion(!pageResults.isEmpty)
}
}
}
}
Expand All @@ -77,8 +90,21 @@ public class PaginatingFetchedResultsController<
}

@MainActor
public func performPagination() {
performPagination(with: paginatingDefinition.paginationRequest)
public func performPagination(completion: @escaping @MainActor (_ hasPageResults: Bool) -> Void = { _ in }) {
performPagination(
with: paginatingDefinition.paginationRequest,
willDebounceInsertsAndReloads: debounceInsertsAndReloads,
completion: completion
)
}

@MainActor
public func performPagination() async -> Bool {
await withCheckedContinuation { continuation in
performPagination { hasPageResults in
continuation.resume(returning: hasPageResults)
}
}
}
}

Expand All @@ -105,6 +131,10 @@ public class PausablePaginatingFetchedResultsController<

@MainActor
public func performPagination() {
performPagination(with: paginatingDefinition.paginationRequest)
performPagination(
with: paginatingDefinition.paginationRequest,
willDebounceInsertsAndReloads: debounceInsertsAndReloads,
completion: { _ in }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont
private(set) var fetchCompletion: (([TestObject]) -> Void)!

private var paginationCurrentResults: [TestObject]!
private var paginationCompletion: (([TestObject]?) -> Void)!
private var paginationRequestCompletion: (([TestObject]?) -> Void)!

private var performPaginationCompletionResult: Bool!

private var associationRequest: TestObject.AssociationRequest!

Expand All @@ -35,7 +37,7 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont
}
let paginationRequest: PaginatingFetchDefinition<TestObject>.PaginationRequest = { [unowned self] currentResults, completion in
self.paginationCurrentResults = currentResults
self.paginationCompletion = completion
self.paginationRequestCompletion = completion
}

let desiredAssociations = TestObject.fetchRequestAssociations(
Expand Down Expand Up @@ -72,7 +74,8 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont
controller = nil
fetchCompletion = nil
paginationCurrentResults = nil
paginationCompletion = nil
paginationRequestCompletion = nil
performPaginationCompletionResult = nil
associationRequest = nil
inclusionCheck = nil

Expand Down Expand Up @@ -124,7 +127,8 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont

fetchCompletion = nil
paginationCurrentResults = nil
paginationCompletion = nil
paginationRequestCompletion = nil
performPaginationCompletionResult = nil
changeEvents.removeAll()

// Trigger pagination
Expand All @@ -133,6 +137,8 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont

performPagination(paginationObjectIDs)

XCTAssertTrue(performPaginationCompletionResult)

XCTAssertEqual(controller.sections.count, 1)
XCTAssertEqual(controller.sections[0].fetchedIDs, objectIDs + paginationObjectIDs)

Expand Down Expand Up @@ -161,7 +167,8 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont

fetchCompletion = nil
paginationCurrentResults = nil
paginationCompletion = nil
paginationRequestCompletion = nil
performPaginationCompletionResult = nil
changeEvents.removeAll()

// Trigger pagination
Expand All @@ -170,9 +177,12 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont

performPagination(paginationObjectIDs)

XCTAssertTrue(performPaginationCompletionResult)

fetchCompletion = nil
paginationCurrentResults = nil
paginationCompletion = nil
paginationRequestCompletion = nil
performPaginationCompletionResult = nil
changeEvents.removeAll()

// Trigger insert
Expand All @@ -184,7 +194,7 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont

XCTAssertNil(fetchCompletion)
XCTAssertNil(paginationCurrentResults)
XCTAssertNil(paginationCompletion)
XCTAssertNil(paginationRequestCompletion)

XCTAssertEqual(controller.sections.count, 1)
XCTAssertEqual(controller.sections[0].fetchedIDs, ["a", "b", "c", "d", "e", "f"])
Expand All @@ -193,6 +203,71 @@ class PaginatingFetchedResultsControllerTestCase: XCTestCase, FetchedResultsCont
XCTAssertEqual(changeEvents[0].change, FetchedResultsChange.insert(location: IndexPath(item: 4, section: 0)))
XCTAssertEqual(changeEvents[0].object.id, "e")
}

func testPaginationHasObjects() async throws {
controller = PaginatingFetchedResultsController(
definition: createFetchDefinition(),
debounceInsertsAndReloads: false
)
controller.setDelegate(self)

// Fetch some objects

let objectIDs = ["a", "b", "c"]

try performFetch(objectIDs)

fetchCompletion = nil
paginationCurrentResults = nil
paginationRequestCompletion = nil
performPaginationCompletionResult = nil
changeEvents.removeAll()

// Trigger pagination

let paginationObjectIDs = ["d", "f"]

performPagination(paginationObjectIDs)

XCTAssertTrue(performPaginationCompletionResult)

XCTAssertEqual(controller.sections.count, 1)
XCTAssertEqual(controller.sections[0].fetchedIDs, objectIDs + paginationObjectIDs)

XCTAssertEqual(changeEvents.count, 2)
XCTAssertEqual(changeEvents[0].change, FetchedResultsChange.insert(location: IndexPath(item: 3, section: 0)))
XCTAssertEqual(changeEvents[0].object.id, "d")
XCTAssertEqual(changeEvents[1].change, FetchedResultsChange.insert(location: IndexPath(item: 4, section: 0)))
XCTAssertEqual(changeEvents[1].object.id, "f")
}

func testPaginationDoesNotHaveObjects() async throws {
controller = PaginatingFetchedResultsController(
definition: createFetchDefinition(),
debounceInsertsAndReloads: false
)
controller.setDelegate(self)

// Fetch some objects

let objectIDs = ["a", "b", "c"]

try performFetch(objectIDs)

fetchCompletion = nil
paginationCurrentResults = nil
paginationRequestCompletion = nil
performPaginationCompletionResult = nil
changeEvents.removeAll()

// Trigger pagination

let paginationObjectIDs: [String] = []

performPagination(paginationObjectIDs)

XCTAssertFalse(performPaginationCompletionResult)
}
}

// MARK: - FetchedResultsControllerDelegate
Expand All @@ -217,9 +292,11 @@ private extension PaginatingFetchedResultsControllerTestCase {
}

func performPagination(_ objects: [TestObject], file: StaticString = #file, line: UInt = #line) {
controller.performPagination()
controller.performPagination { hasPageResults in
self.performPaginationCompletionResult = hasPageResults
}

self.paginationCompletion(objects)
self.paginationRequestCompletion(objects)

let hasAllObjects = objects.allSatisfy { controller.fetchedObjects.contains($0) }
XCTAssertTrue(hasAllObjects, file: file, line: line)
Expand Down

0 comments on commit 7b36ee5

Please sign in to comment.