Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prefer task api
Browse files Browse the repository at this point in the history
Iron-Ham committed Jan 8, 2024
1 parent 86c4885 commit a6f3da2
Showing 2 changed files with 53 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Apollo
import ApolloAPI
import Combine
import Dispatch

/// Type-erases a query pager, transforming data from a generic type to a specific type, often a view model or array of view models.
public class AnyGraphQLQueryPager<Model> {
@@ -87,46 +86,34 @@ public class AnyGraphQLQueryPager<Model> {
/// Load the next page, if available.
/// - Parameters:
/// - cachePolicy: The Apollo `CachePolicy` to use. Defaults to `returnCacheDataAndFetch`.
/// - callbackQueue: The `DispatchQueue` that the `completion` fires on. Defaults to `main`.
/// - completion: A completion block that will always trigger after the execution of this operation. Passes an optional error, of type `PaginationError`, if there was an internal error related to pagination. Does not surface network errors. Defaults to `nil`.
public func loadNext(
cachePolicy: CachePolicy = .returnCacheDataAndFetch,
callbackQueue: DispatchQueue = .main,
completion: ((PaginationError?) -> Void)? = nil
) {
pager.loadNext(cachePolicy: cachePolicy, callbackQueue: callbackQueue, completion: { error in
callbackQueue.async { completion?(error) }
})
pager.loadNext(cachePolicy: cachePolicy, completion: completion)
}

/// Load the previous page, if available.
/// - Parameters:
/// - cachePolicy: The Apollo `CachePolicy` to use. Defaults to `returnCacheDataAndFetch`.
/// - callbackQueue: The `DispatchQueue` that the `completion` fires on. Defaults to `main`.
/// - completion: A completion block that will always trigger after the execution of this operation. Passes an optional error, of type `PaginationError`, if there was an internal error related to pagination. Does not surface network errors. Defaults to `nil`.
public func loadPrevious(
cachePolicy: CachePolicy = .returnCacheDataAndFetch,
callbackQueue: DispatchQueue = .main,
completion: ((PaginationError?) -> Void)? = nil
) {
pager.loadPrevious(cachePolicy: cachePolicy, callbackQueue: callbackQueue, completion: { error in
callbackQueue.async { completion?(error) }
})
pager.loadPrevious(cachePolicy: cachePolicy, completion: completion)
}

/// Loads all pages.
/// - Parameters:
/// - fetchFromInitialPage: Pass true to begin loading from the initial page; otherwise pass false. Defaults to `true`. **NOTE**: Loading all pages with this value set to `false` requires that the initial page has already been loaded previously.
/// - callbackQueue: The `DispatchQueue` that the `completion` fires on. Defaults to `main`.
/// - completion: A completion block that will always trigger after the execution of this operation. Passes an optional error, of type `PaginationError`, if there was an internal error related to pagination. Does not surface network errors. Defaults to `nil`.
public func loadAll(
fetchFromInitialPage: Bool = true,
callbackQueue: DispatchQueue = .main,
completion: ((PaginationError?) -> Void)? = nil
) {
pager.loadAll(fetchFromInitialPage: fetchFromInitialPage, callbackQueue: callbackQueue, completion: { error in
callbackQueue.async { completion?(error) }
})
pager.loadAll(fetchFromInitialPage: fetchFromInitialPage, completion: completion)
}

/// Discards pagination state and fetches the first page from scratch.
Original file line number Diff line number Diff line change
@@ -12,17 +12,14 @@ public protocol PagerType {
func cancel()
func loadPrevious(
cachePolicy: CachePolicy,
callbackQueue: DispatchQueue,
completion: ((PaginationError?) -> Void)?
)
func loadNext(
cachePolicy: CachePolicy,
callbackQueue: DispatchQueue,
completion: ((PaginationError?) -> Void)?
)
func loadAll(
fetchFromInitialPage: Bool,
callbackQueue: DispatchQueue,
completion: ((PaginationError?) -> Void)?
)
func refetch(cachePolicy: CachePolicy)
@@ -34,6 +31,7 @@ public class GraphQLQueryPager<InitialQuery: GraphQLQuery, PaginatedQuery: Graph
let pager: AsyncGraphQLQueryPager<InitialQuery, PaginatedQuery>
private var subscriptions = Subscriptions()
private var completionManager = CompletionManager()
private let callbackPriority: CallbackPriority

public var publisher: AnyPublisher<Result<PaginationOutput<InitialQuery, PaginatedQuery>, Error>, Never> {
get async { await pager.$currentValue.compactMap { $0 }.eraseToAnyPublisher() }
@@ -43,9 +41,11 @@ public class GraphQLQueryPager<InitialQuery: GraphQLQuery, PaginatedQuery: Graph
client: ApolloClientProtocol,
initialQuery: InitialQuery,
watcherDispatchQueue: DispatchQueue = .main,
callbackPriority: CallbackPriority = .main,
extractPageInfo: @escaping (PageExtractionData<InitialQuery, PaginatedQuery>) -> P,
pageResolver: ((P, PaginationDirection) -> PaginatedQuery?)?
) {
self.callbackPriority = callbackPriority
pager = .init(
client: client,
initialQuery: initialQuery,
@@ -74,8 +74,9 @@ public class GraphQLQueryPager<InitialQuery: GraphQLQuery, PaginatedQuery: Graph

/// Convenience initializer
/// - Parameter pager: An `AsyncGraphQLQueryPager`.
public init(pager: AsyncGraphQLQueryPager<InitialQuery, PaginatedQuery>) {
public init(pager: AsyncGraphQLQueryPager<InitialQuery, PaginatedQuery>, callbackPriority: CallbackPriority = .main) {
self.pager = pager
self.callbackPriority = callbackPriority
}

/// Allows the caller to subscribe to new pagination results.
@@ -108,11 +109,9 @@ public class GraphQLQueryPager<InitialQuery: GraphQLQuery, PaginatedQuery: Graph
/// Loads the previous page, if we can.
/// - Parameters:
/// - cachePolicy: The Apollo `CachePolicy` to use. Defaults to `fetchIgnoringCacheData`.
/// - callbackQueue: The `DispatchQueue` that the `completion` fires on. Defaults to `main`.
/// - completion: A completion block that will always trigger after the execution of this operation. Passes an optional error, of type `PaginationError`, if there was an internal error related to pagination. Does not surface network errors. Defaults to `nil`.
public func loadPrevious(
cachePolicy: CachePolicy = .fetchIgnoringCacheData,
callbackQueue: DispatchQueue = .main,
completion: ((PaginationError?) -> Void)? = nil
) {
execute(completion: completion) { [weak self] in
@@ -123,11 +122,9 @@ public class GraphQLQueryPager<InitialQuery: GraphQLQuery, PaginatedQuery: Graph
/// Loads the next page, if we can.
/// - Parameters:
/// - cachePolicy: The Apollo `CachePolicy` to use. Defaults to `fetchIgnoringCacheData`.
/// - callbackQueue: The `DispatchQueue` that the `completion` fires on. Defaults to `main`.
/// - completion: A completion block that will always trigger after the execution of this operation. Passes an optional error, of type `PaginationError`, if there was an internal error related to pagination. Does not surface network errors. Defaults to `nil`.
public func loadNext(
cachePolicy: CachePolicy = .fetchIgnoringCacheData,
callbackQueue: DispatchQueue = .main,
completion: ((PaginationError?) -> Void)? = nil
) {
execute(completion: completion) { [weak self] in
@@ -138,11 +135,9 @@ public class GraphQLQueryPager<InitialQuery: GraphQLQuery, PaginatedQuery: Graph
/// Loads all pages.
/// - Parameters:
/// - fetchFromInitialPage: Pass true to begin loading from the initial page; otherwise pass false. Defaults to `true`. **NOTE**: Loading all pages with this value set to `false` requires that the initial page has already been loaded previously.
/// - callbackQueue: The `DispatchQueue` that the `completion` fires on. Defaults to `main`.
/// - completion: A completion block that will always trigger after the execution of this operation. Passes an optional error, of type `PaginationError`, if there was an internal error related to pagination. Does not surface network errors. Defaults to `nil`.
public func loadAll(
fetchFromInitialPage: Bool = true,
callbackQueue: DispatchQueue = .main,
completion: ((PaginationError?) -> Void)? = nil
) {
execute(completion: completion) { [weak self] in
@@ -170,13 +165,47 @@ public class GraphQLQueryPager<InitialQuery: GraphQLQuery, PaginatedQuery: Graph

private func execute(completion: ((PaginationError?) -> Void)?, operation: @escaping () async throws -> Void) {
Task<_, Never> { [weak self] in
let completionHandler = Completion(completion: completion)
await self?.completionManager.append(completion: completionHandler)
guard let self else { return }
let completionHandler = Completion(priority: callbackPriority, completion: completion)
await self.completionManager.append(completion: completionHandler)
do {
try await operation()
await self?.completionManager.execute(completion: completionHandler, with: nil)
await self.completionManager.execute(completion: completionHandler, with: nil)
} catch {
await self?.completionManager.execute(completion: completionHandler, with: error as? PaginationError ?? .unknown(error))
await self.completionManager.execute(completion: completionHandler, with: error as? PaginationError ?? .unknown(error))
}
}
}
}

public enum CallbackPriority {
case main, high, medium, low, userInitiated, utility, background

var taskPriority: TaskPriority? {
switch self {
case .main:
return nil
case .background:
return .background
case .high:
return .high
case .medium:
return .medium
case .low:
return .low
case .userInitiated:
return .userInitiated
case .utility:
return .utility
}
}

func perform(_ body: @Sendable @escaping () throws -> Void) async rethrows {
if self == .main {
try await MainActor.run(body: body)
} else {
Task.detached(priority: taskPriority) {
try body()
}
}
}
@@ -192,14 +221,18 @@ private actor Subscriptions {

private class Completion {
var completion: ((PaginationError?) -> Void)?
var priority: CallbackPriority

init(completion: ((PaginationError?) -> Void)?) {
init(priority: CallbackPriority, completion: ((PaginationError?) -> Void)?) {
self.priority = priority
self.completion = completion
}

func execute(error: PaginationError?) async {
completion?(error)
completion = nil
await priority.perform { [weak self] in
self?.completion?(error)
self?.completion = nil
}
}
}

0 comments on commit a6f3da2

Please sign in to comment.