Is there a way of using a completion handler passed through as an argument to detect when a long request is completed - swift

I'm having trouble understanding how to use a closure to handle completed events when passing in a function as a parameter.
Here's a very contrived example:
class MessageService {
func sendMessage(s: String) {
print(s)
}
var messenger: Messenger {
createMessenger(completion: sendMessage(s:))
}
}
func createMessenger(completion: #escaping (String) -> Void) -> Messenger {
return Messenger { completion("This is a hardcoded message.") }
}
struct Messenger {
let sendMessage: () -> Void
init(sendMessage: #escaping () -> Void) {
self.sendMessage = sendMessage
}
}
let service = MessageService()
let messenger = service.messenger
messenger.sendMessage()
I want to find out when sendMessage is finished (if for example it was performing something like a network request), so is there a way of having a completion handler for sendMessage so that I could write something along the lines of:
messenger.sendMessage {
print("I finished sending a message!")
}
I've tried adding a completion handler like this in the service class:
func sendMessage(s: String, completion: #escaping () -> Void) {
MessageRequest(with: s) {
completion()
}
}
But things started getting very confusing when I'm trying to use the createMessenger method, because the above function has some crazy type of (String, () -> ()) -> () which I don't know how to handle. Any help would be greatly appreciated, thanks.

So, it sounds like what you want is an arbitrary Messenger type, whose creator tell it what action to do, and once the action is done, it invokes its caller's completion handler.
It helps if you use typealias with descriptive names to keep track of all the closures. And if you don't mind, I'll name it more generically as Agent:
struct Agent {
typealias Completion = () -> Void
typealias Action = (Completion) -> Void
private let action: Action
static func create(action: #escaping Action) -> Agent {
Agent(action: action)
}
func execute(_ completion: #escaping Completion) {
action(completion)
}
}
So, Agent can be created with an arbitrary action that accepts a completion handler to signal when it's done:
let agent = Agent.create { completion in
print("started executing action")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { completion() }
}
agent.execute { print("done") }
Now, you can adapt it to your MessengerService class:
class MessageService {
func sendMessage(s: String) {
print(s)
}
var messenger: Agent {
Agent.create { completion in
sendMessage("This is a hardcoded message.")
completion()
}
}
}

Related

Making custom Deffered Future Publisher in Swift Combine?

Like the title says I would like to make custom publisher that will basically function like deffered future. Normally when I want to encapsulate code in some Future, but want it to execute on subscription, I would need to write something like this:
Deffered {
Future { promise in
}
}
Now I was thinking of making custom publisher, something along the lines DefferedFuture that will have exact same functionality as Future, but will execute promise only on subscription?
The most obvious answer is this:
func single<Output, Failure>(_ promise: #escaping (#escaping (Result<Output, Failure>) -> Void) -> Void) -> Deferred<Future<Output, Failure>> where Failure: Error {
Deferred {
Future<Output, Failure>(promise)
}
}
If it absolutely must be a type rather than a function then:
extension Publishers {
struct Single<Output, Failure>: Publisher where Failure: Error {
let promise: (#escaping (Result<Output, Failure>) -> Void) -> Void
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
Deferred { Future(promise) }
.subscribe(subscriber)
}
}
}

Swift Combine alternative to Rx Observable.create

I have some code that is built using RxSwift, and I'm playing around with converting it to use Apple's Combine framework.
One pattern which is very common is the use of Observable.create for one-shot observables (usually network requests). Something like this:
func loadWidgets() -> Observable<[Widget]> {
return Observable.create { observer in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
observer.onNext(widgets)
observer.onComplete()
}, error: { error in
// publish error on failure
observer.onError()
})
// allow cancellation
return Disposable {
loadTask.cancel()
}
}
}
I'm trying to map that across to Combine and I haven't been able to quite figure it out. The closest I've been able to get is using Future for something like this:
func loadWidgets() -> AnyPublisher<[Widget], Error> {
return Future<[Widget], Error> { resolve in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
resolve(.success(widgets))
}, error: { error in
// publish error on failure
resolve(.failure(error))
})
// allow cancellation ???
}
}
As you can see, it does most of it, but there's no ability to cancel.
Secondarily, future doesn't allow multiple results.
Is there any way to do something like the Rx Observable.create pattern which allows cancellation and optionally multiple results?
I think I found a way to mimic Observable.create using a PassthroughSubject in Combine. Here is the helper I made:
struct AnyObserver<Output, Failure: Error> {
let onNext: ((Output) -> Void)
let onError: ((Failure) -> Void)
let onComplete: (() -> Void)
}
struct Disposable {
let dispose: () -> Void
}
extension AnyPublisher {
static func create(subscribe: #escaping (AnyObserver<Output, Failure>) -> Disposable) -> Self {
let subject = PassthroughSubject<Output, Failure>()
var disposable: Disposable?
return subject
.handleEvents(receiveSubscription: { subscription in
disposable = subscribe(AnyObserver(
onNext: { output in subject.send(output) },
onError: { failure in subject.send(completion: .failure(failure)) },
onComplete: { subject.send(completion: .finished) }
))
}, receiveCancel: { disposable?.dispose() })
.eraseToAnyPublisher()
}
}
And here is how it looks in usage:
func loadWidgets() -> AnyPublisher<[Widget], Error> {
AnyPublisher.create { observer in
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
observer.onNext(widgets)
observer.onComplete()
}, error: { error in
observer.onError(error)
})
return Disposable {
loadTask.cancel()
}
}
}
From what I've learned, the support for initializing an AnyPublisher with a closure has been dropped in Xcode 11 beta 3. This would be a corresponding solution for Rx's Observable.create in this case, but for now I believe that the Future is a goto solution if you only need to propagate single value. In other cases I would go for returning a PassthroughSubject and propagating multiple values this way, but it will not allow you to start a task when the observation starts and I believe it's far from ideal compared to Observable.create.
In terms of cancellation, it does not have an isDisposed property similar to a Disposable, so it's not possible to directly check the state of it and stop your own tasks from executing. The only way that I can think of right now would be to observe for a cancel event, but it's surely not as comfortable as a Disposable.
Also, I'd assume that cancel might in fact stop tasks like network requests from URLSession based on the docs here: https://developer.apple.com/documentation/combine/cancellable
Add an isCancelled operation outside the closure and check it in the future's closure. isCancelled can be toggled with the handleEvent() operator.
var isCancelled = false
func loadWidgets() -> AnyPublisher<[Widget], Error> {
return HandleEvents<Future<Any, Error>> { resolve in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
resolve(.success(widgets))
}, error: { error in
// publish error on failure
resolve(.failure(error))
}
if isCancelled {
loadTask.cancel()
}
).handleEvents(receiveCancel: {
isCancelled = true
})
}
}
and somewhere in the app you do this to cancel the event
loadWidgets().cancel()
Also check this article
Thanks to ccwasden for the inspiration. This replicates Observable.create semantics with a pure Combine implementation without any superfluous entities.
AnyPublisher.create() Swift 5.6 Extension
public extension AnyPublisher {
static func create<Output, Failure>(_ subscribe: #escaping (AnySubscriber<Output, Failure>) -> AnyCancellable) -> AnyPublisher<Output, Failure> {
let passthroughSubject = PassthroughSubject<Output, Failure>()
var cancellable: AnyCancellable?
return passthroughSubject
.handleEvents(receiveSubscription: { subscription in
let subscriber = AnySubscriber<Output, Failure> { subscription in
} receiveValue: { input in
passthroughSubject.send(input)
return .unlimited
} receiveCompletion: { completion in
passthroughSubject.send(completion: completion)
}
cancellable = subscribe(subscriber)
}, receiveCompletion: { completion in
}, receiveCancel: {
cancellable?.cancel()
})
.eraseToAnyPublisher()
}
}
Usage
func doSomething() -> AnyPublisher<Int, Error> {
return AnyPublisher<Int, Error>.create { subscriber in
// Imperative implementation of doing something can call subscriber as follows
_ = subscriber.receive(1)
subscriber.receive(completion: .finished)
// subscriber.receive(completion: .failure(myError))
return AnyCancellable {
// Imperative cancellation implementation
}
}
}

Convert closure Result to Rx generically?

I have a bunch of functions with Result completion handlers that I’d like to convert to RxSwift.
They follow this convention:
func fetch(id: Int, completion: #escaping (Result<AuthorType, DataError>) -> Void) {...}
I could use the typical:
return Observable<AuthorType>.create { on(.next... }
Is there a more considerate generic way like PromiseKit does:
func fetch() -> Promise<AuthorType> {
return Promise { fetch(completion: $0.resolve) }
}
Anything like this possible in RxSwift?
There isn't a constructor like you are asking for out of the box, but it's easy enough to create:
extension ObservableType {
static func createFromResultCallback<E: Error>(_ fn: #escaping (#escaping (Result<Element, E>) -> Void) -> ()) -> Observable<Element> {
return Observable.create { observer in
fn { result in
switch result {
case .success(let value):
observer.onNext(value)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
For your example, it could be used like:
func fetch(id: Int) -> Observable<AuthorType> {
return .createFromResultCallback { fetch(id: id, $0) }
}
And if you have a function that only takes a callback like:
func shortFetch(_ completion: #escaping (Result<AuthorType, DataError>) -> Void)
Then you could create an Observable with the above by just doing:
Observable.createFromResultCallback(shortFetch)
Remember there is a major difference in behavior once you wrap the function in an Observable like this. Now it is cold which means it won't execute until after something subscribes to the observable and will execute every time something subscribes. This is unlike a Promise which will execute immediately and only once.

Are there any existing mechanisms in the swift language for cascading function calls through closures within a single queue?

I have a task to call functions with a certain order synchronously with respect to each other.
I realized my function, but for some reason when I checked it, I found an anomaly.
Let's say if you make a list of calls to resolve which rights and when you stop to resolve the first request for about 1 minute, the other permissions will slip through
class RunFuncs {
typealias TypeFuncCall<T> = ((_ completed: #escaping (T) -> Void) -> Void)
static func cascad<T>(dispatchQueue queue: DispatchQueue,
callFuncs funcs: [TypeFuncCall<T>],
indexFuncRun index: Int = 0,
completedAll: #escaping (([T]) -> Void)) {
queue.async {
guard (funcs.count > index) else {
completedAll([T]())
return
}
let callOne = funcs[index]
let indexNext = (index + 1)
callOne() { [completedAll, funcs, indexNext] (resultOne) in
RunFuncs.cascad(dispatchQueue: queue,
callFuncs: funcs,
indexFuncRun: indexNext,
completedAll: { (resultOther) in
var resultAll = resultOther
resultAll.insert(resultOne, at: 0)
completedAll(resultAll)
})
}
}
}
}

Create a Swift function with parameters that aren’t allowed to escape the function?

I have a function that is only valid to call within the context of another function, so I want swift stop consumers from capturing the inner function:
protocol Foo {
func bar(onlyValidToCallInsideBar: () -> Void)
}
class GoodFooImpl {
func bar(onlyValidToCallInsideBar: () -> Void) {
// do some stuff
onlyValidToCallInsideBar()
// do some more stuff
}
}
class CapturingBadFooImpl {
var badCaptureOfOnlyValidToCallInsideBar: (() -> Void)?
func bar(onlyValidToCallInsideBar: () -> Void) {
badCaptureOfOnlyValidToCallInsideBar = onlyValidToCallInsideBar
}
func fizz() {
badCaptureOfOnlyValidToCallInsideBar!()
}
}
class AsyncBadFooImpl {
func bar(onlyValidToCallInsideBar: () -> Void) {
dispatch_async(dispatch_get_main_queue()) {
onlyValidToCallInsideBar()
}
}
}
class FooConsumer {
func buzz(foo: Foo) {
bar() {
// This should only be called inside bar
}
}
}
I want to prevent impls like CapturingBadFooImpl and AsyncBadFooImpl.
Swift has nested functions. It sounds like you want to make your function nested inside another. Or, if you are creating a framework, you can make your function private. That prevents it from being used outside of its module (the framework).
If you don't mean one of those things you're going to have to explain what you want more clearly.