Dealing with NSProgress properties #544
-
Hi, I have been trying to wrap func load(tags: Set<String>, bundle: Bundle) -> Effect<Never, Error> {
.run { subscriber in
let request = NSBundleResourceRequest(tags: tags, bundle: bundle)
request.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent
request.conditionallyBeginAccessingResources { available in
if available {
subscriber.send(completion: .finished)
} else {
request.beginAccessingResources { error in
if let error = error {
subscriber.send(completion: .failure(error))
} else {
subscriber.send(completion: .finished)
}
}
}
}
return request.progress
}
}
extension Progress: Cancellable { } However, I am stumped by how to get the Thanks, |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
Found the answer in reusable offline downloads example. Here is the code if anyone is interested. public struct ResourceLoader {
public enum Action: Equatable {
case loaded
case updateProgress(Double)
case updateProgressMessage(String)
}
var load: (_ id: AnyHashable, _ tags: Set<String>, _ bundle: Bundle) -> Effect<Action, Error>
var cancel: (_ id: AnyHashable) -> Effect<Never, Never>
}
public extension ResourceLoader {
static let live = Self { id, tags, bundle in
.run { subscriber in
let request = NSBundleResourceRequest(tags: tags, bundle: bundle)
request.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent
dependencies[id] = [
request,
request.progress.publisher(for: \.fractionCompleted)
.map(Action.updateProgress)
.sink(receiveValue: subscriber.send),
request.progress.publisher(for: \.localizedDescription)
.map(Action.updateProgressMessage)
.sink(receiveValue: subscriber.send),
]
request.conditionallyBeginAccessingResources { available in
if available {
subscriber.send(.loaded)
subscriber.send(completion: .finished)
} else {
request.beginAccessingResources { error in
if let error = error {
subscriber.send(completion: .failure(error))
} else {
subscriber.send(.loaded)
subscriber.send(completion: .finished)
}
}
}
}
return AnyCancellable {
dependencies[id]?.forEach { $0.cancel() }
dependencies[id] = nil
}
}
} cancel: { id in
.fireAndForget {
dependencies[id]?.forEach { $0.cancel() }
dependencies[id] = nil
}
}
}
private var dependencies = [AnyHashable: [Cancellable]]()
extension NSBundleResourceRequest: Cancellable {
public func cancel() {
progress.cancel()
}
} |
Beta Was this translation helpful? Give feedback.
-
Apologies for the noise. I was able to separate out the progress reporting part and move it to a component. I am putting the complete working example here for future reference. import Foundation
import Combine
import ComposableArchitecture
public struct ProgressReporter: Cancellable {
public struct State: Equatable {
public var fractionCompleted: Double
public var localizedDescription: String
public init(fractionCompleted: Double, localizedDescription: String) {
self.fractionCompleted = fractionCompleted
self.localizedDescription = localizedDescription
}
}
public enum Action: Equatable {
case fractionCompletedDidChange(Double)
case localizedDescriptionDidChange(String)
}
var value: ProgressReporting
var fractionCompletedCancellation: AnyCancellable
var localizedDescriptionCancellation: AnyCancellable
public init(value: ProgressReporting, sink: @escaping (Action) -> ()) {
self.value = value
fractionCompletedCancellation = value.progress.publisher(for: \.fractionCompleted)
.map(Action.fractionCompletedDidChange)
.sink(receiveValue: sink)
localizedDescriptionCancellation = value.progress.publisher(for: \.localizedDescription)
.map(Action.localizedDescriptionDidChange)
.sink(receiveValue: sink)
}
public func cancel() {
value.progress.cancel()
fractionCompletedCancellation.cancel()
localizedDescriptionCancellation.cancel()
}
}
public struct ResourceLoader {
public struct ID: Equatable, Hashable {
public var bundle: Bundle
public var tags: Set<String>
public init(bundle: Bundle, tags: Set<String>) {
self.bundle = bundle
self.tags = tags
}
}
public struct State: Equatable {
public var id: ID
public var isLoaded = false
public var progress = ProgressReporter.State(fractionCompleted: 0, localizedDescription: "")
public var error: Error?
public init(id: ID) {
self.id = id
}
}
public enum Action: Equatable {
case startLoading
case progress(ProgressReporter.Action)
case failed(Error)
case loaded
}
public struct Environment {
var loader: ResourceLoader
var mainQueue: AnySchedulerOf<DispatchQueue>
}
public struct Error: Swift.Error, Equatable {
public var message: String
}
public var load: (_ id: ID) -> Effect<Action, Error>
public var cancel: (_ id: ID) -> Effect<Never, Never>
}
private extension ResourceLoader {
static var liveRequests = [ID: ProgressReporter]()
}
public extension ResourceLoader {
static let live = Self { id in
.run { subscriber in
let request = NSBundleResourceRequest(tags: id.tags, bundle: id.bundle)
request.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent
Self.liveRequests[id] = .init(value: request, sink: { subscriber.send(.progress($0)) })
request.beginAccessingResources { error in
if let error = error {
subscriber.send(completion: .failure(Error(message: error.localizedDescription)))
} else {
subscriber.send(.loaded)
id.bundle.setPreservationPriority(1, forTags: id.tags)
subscriber.send(completion: .finished)
}
}
return AnyCancellable {
Self.liveRequests[id]?.cancel()
Self.liveRequests[id] = nil
}
}
} cancel: { id in
.fireAndForget {
Self.liveRequests[id]?.cancel()
Self.liveRequests[id] = nil
}
}
}
public extension Reducer {
func loadResource(state: WritableKeyPath<State, ResourceLoader.State>,
action: CasePath<Action, ResourceLoader.Action>,
environment: @escaping (Environment) -> ResourceLoader.Environment)
-> Reducer {
.combine(
Reducer<ResourceLoader.State, ResourceLoader.Action, ResourceLoader.Environment> {
state, action, environment in
switch action {
case .startLoading:
return environment.loader.load(state.id)
.receive(on: environment.mainQueue)
.catchToEffect()
.map { result in
switch result {
case .success(let action):
return action
case .failure(let error):
return .failed(error)
}
}
case .progress(.fractionCompletedDidChange(let fractionCompleted)):
state.progress.fractionCompleted = fractionCompleted
return .none
case .progress(.localizedDescriptionDidChange(let localizedDescription)):
state.progress.localizedDescription = localizedDescription
return .none
case .failed(let error):
state.isLoaded = false
state.error = error
return .none
case.loaded:
state.isLoaded = true
return .none
}
}
.pullback(state: state, action: action, environment: environment),
self
)
}
} |
Beta Was this translation helpful? Give feedback.
Found the answer in reusable offline downloads example. Here is the code if anyone is interested.