I'm trying to use the Google MLKitTranslate framework for offline translation in my app. I have written a function for downloading a language model.
public func downloadModel(forLanguage language: TranslateLanguage) -> Observable<Double> {
let model = TranslateRemoteModel.translateRemoteModel(language: language)
return Observable<Double>.create { _ -> Disposable in
// Observe the download progress
self.progress = ModelManager.modelManager().download(
model,
conditions: ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
)
return Disposables.create()
}
}
progress here has the Progress type.
However I want to create an Observable that sends out a .next event when I have an update in the .fractionComplete and a .completed event when .fractionComplete is 1.0.
I have tried using .rx.observe and .rx.observeWeakly (KVO) on the progress object but it didn't work.
So how can I create an observable from this Progress event? Help is much appreciated.
This should work:
func downloadModel(forLanguage language: TranslateLanguage) -> Observable<Double> {
let model = TranslateRemoteModel.translateRemoteModel(language: language)
return Observable.create { observer -> Disposable in
let progress = ModelManager.modelManager().download(
model,
conditions: ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
)
return progress.rx.observe(Double.self, "fractionCompleted")
.compactMap { $0 }
.takeUntil(.inclusive, predicate: { $0 >= 1.0 })
.subscribe(observer)
}
}
A couple of things to note. This is not a method, do not put it in a class. It is a free function. The progress object does not need to be retained by any class, the model manager will retain it and release it when it isn't needed anymore.
If you are using the create(_:) operator, do not ignore the parameter being passed into your closure. If you do that you will not be able to emit anything.
Related
Between these 2 codes
( returns -> Void)
static func dropboxWorkoutList ( userCompletionHandler: #escaping ([String]) -> Void) {
let client = DropboxClientsManager.authorizedClient
var files: [String] = []
_ = client?.files.listFolder(path: "/workouts")
.response { response, error in
if let result = response {
for entry in result.entries {
if let file = entry as? Files.FileMetadata {
let ext = (file.name as NSString).pathExtension
switch ext {
case "txt", "mrc", "zwo":
// print("filename:\(file.name) ext:\(ext) path:\(file.pathLower)")
files.append(file.pathLower!)
default:
continue
}
}
}
files = files.sorted(by: { $0 < $1 } )
// print("Contents of Workout Folder:\(files)")
userCompletionHandler(files)
} else if let error = error {
print(error)
}
}
}
calling it
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
results in:
value:["/1-01.txt", "/1-17.txt"]
value[0] : /1-01.txt
value.count : 5
however when changing it
from Return -> Void
to Return -> Any
trying to execute the below will have swift telling me:
"Missing return in a closure expected to return 'Any'"
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
I can only allowed to execute 1 print statement. Just want to understand the difference.
Note: Asked this
Return list of files from function call
and was given this as possible answer
How I can return value from async block in swift
Specifying a closure of ([String]) -> Any means that the closure is going to return something, and it is of type Any. But in your examples, (a) your closures are not returning anything at all; and (b) the dropboxWorkoutList does not appear to need/use an object returned by the closure supplied by the caller, anyway. This is a “completion handler closure” pattern, and completion handler closures almost always have a Void return type.
I actually want to use the return values from dropboxWorkoutList to populate a tableview which I've not coded yet
OK, then I think you may be conflating the closure parameter (what dropboxWorkoutList will be supplying back to the caller) and the closure’s return value (the far less common scenario, where the caller needs to supply dropboxWorkoutList some value based upon the closure’s parameter). In this case, you want the former (the closure parameter), not the latter (the closure’s return value).
You likely do not want to change the closure to be ([String]) -> Any, at all, but rather leave it as ([String]) -> Void. The caller should just take the parameter of the closure and use that. For example:
dropboxFunc.dropboxWorkoutList { values in
self.strings = values // update whatever model object your table view data source is using
self.tableview.reloadData() // and tell the table view to reload the data
}
In short, your question here (and in that other post) was, effectively, “how do I return a value using a closure?”, to which the answer is that you should not think of it as “returning” a value. Rather, in asynchronous routines with completion handler closures, results are supplied as parameter(s) to the closure. Essentially, dropboxWorkoutList is calling a function (in this case, a closure) to say “hey, here is your data”. It is providing data to that closure, not returning data from that closure.
just trying to implement SwiftUI and Combine in my new project.
But stuck in this:
func task() -> AnyPublisher<Int, Error> {
return AnyPublisher { subscriber in
subscriber.receive(Int(arc4random()))
subscriber.receive(completion: .finished)
}
}
This produces the following compiler error:
Type '(_) -> ()' does not conform to protocol 'Publisher'
Why?
Update
Actually Random here is just as an example. The real code will look like this:
func task() -> AnyPublisher<SomeCodableModel, Error> {
return AnyPublisher { subscriber in
BackendCall.MakeApiCallWithCompletionHandler { response, error in
if let error == error {
subscriber.receive(.failure(error))
} else {
subscriber.receive(.success(response.data.filter))
subscriber.receive(.finished)
}
}
}
}
Unfortunately, I don't have access to BackendCall API since it is private.
It's kind of pseudocode but, it pretty close to the real one.
You cannot initialise an AnyPublisher with a closure accepting a Subscriber. You can only initialise an AnyPublisher from a Publisher. If you want to create a custom Publisher that emits a single random Int as soon as it receives a subscriber and then completes, you can create a custom type conforming to Publisher and in the required method, receive(subscriber:), do exactly what you were doing in your closure.
struct RandomNumberPublisher: Publisher {
typealias Output = Int
typealias Failure = Never
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(Int.random(in: 0...Int.max))
subscriber.receive(completion: .finished)
}
}
Then in your task method, you simply need to create a RandomNumberPublisher and then type erase it.
func task() -> AnyPublisher<Int, Never> {
return RandomNumberPublisher().eraseToAnyPublisher()
}
If all you want is a single random value, use Just
fun task() -> AnyPublisher<Int, Never> {
return Just(Int.random(in: 0...Int.max)).eraseToAnyPublisher()
}
Sidenote: don't use Int(arc4random()) anymore.
You're likely better off wrapping this in a Future publisher, possibly also wrapped with Deferred if you want it to response when subscriptions come in. Future is an excellent way to wrap external async API calls, especially ones that you can't fully control or otherwise easily adapt.
There's an example in Using Combine for "wrapping an async call with a Future to create a one-shot publisher" that looks like it might map quite closely to what you're trying to do.
If you want it to return more than a single value, then you may want to compose something out of PassthoughSubject or CurrentValueSubject that gives you an interface of -> AnyPublisher<YourType, Error> (or whatever you're looking for).
I am trying to achieve something similar in rxswift example project from RxSwift repo. But in my case there are dependent observables. I couldn't find any solution without using binding in viewmodel
Here is the structure of my viewmodel:
First the definitions of input, output and viewmodel
typealias UserListViewModelInput = (
viewAppearAction: Observable<Void>,
deleteAction: Observable<Int>
)
typealias UserListViewModelOutput = Driver<[User]>
typealias UserListViewModel = (UserListViewModelInput, #escaping UserApi) -> UserListViewModelOutput
Then there is actual implementation which doesn't compile.
let userListViewModel: UserListViewModel = { input, loadUsers in
let loadedUserList = input.viewAppearAction
.flatMapLatest { loadUsers().materialize() }
.elements()
.asDriver(onErrorDriveWith: .never())
let userListAfterDelete = input.deleteAction
.withLatestFrom(userList) { index, users in
users.enumerated().compactMap { $0.offset != index ? $0.element : nil }
}
.asDriver(onErrorJustReturn: [])
let userList = Driver.merge([loadedUserList, userListAfterDelete])
return userList
}
Viewmodel has two job. First load the user list. Second is delete a user at index. The final output is the user list which is downloaded with UserApi minus deleted users.
The problem in here in order the define userList I need to define userListAfterDelete. And in order to define userListAfterDelete I need to define userList.
So is there a way to break this cycle without using binding inside view model? Like a placeholder observable or operator that keeps state?
This is a job for a state machine. What you will see in the code below is that there are two actions that can affect the User array. When the view appears, a new array is downloaded, when delete comes in, a particular user is removed.
This is likely the most common pattern seen in reactive code dealing with state. So common that there are whole libraries that implement some variation of it.
let userListViewModel: UserListViewModel = { input, loadUsers in
enum Action {
case reset([User])
case delete(at: Int)
}
let resetUsers = input.viewAppearAction
.flatMapLatest { loadUsers().materialize() }
.compactMap { $0.element }
.map { Action.reset($0) }
let delete = input.deleteAction.map { Action.delete(at: $0) }
return Observable.merge(resetUsers, delete)
.scan(into: [User](), accumulator: { users, action in
switch action {
case let .reset(newUsers):
users = newUsers
case let .delete(index):
users.remove(at: index)
}
})
.asDriver(onErrorJustReturn: [])
}
I have a Completable being returned from a simple function.
This is not an async call, so I just need to return a succcessful completion or error depending on a conditional (using Rx here so I can tie into other Rx usages):
func exampleFunc() -> Completable {
if successful {
return Completable.just() // What to do here???
} else {
return Completable.error(SomeErrorType.someError)
}
}
The error case works pretty easily, but am having a block on how to just return a successful completable (without needing to .create() it).
I was thinking I just need to use Completable's .just() or .never(), but just is requiring a parameter, and never doesn't seem to trigger the completion event.
.empty() is the operator I was looking for!
Turns out, I had mixed up the implementations of .never() and .empty() in my head!
.never() emits no items and does NOT terminate
.empty() emits no items but does terminates normally
So, the example code above works like this:
func exampleFunc() -> Completable {
if successful {
return Completable.empty()
} else {
return Completable.error(SomeErrorType.someError)
}
}
Here is the documentation on empty/throw/never operators.
I would be more inclined to do the following:
func example() throws {
// do something
if !successful {
throw SomeErrorType.someError
}
}
Then in order to tie it into other Rx code, I would just use map as in:
myObservable.map { try example() }
But then, mapping over a Completable doesn't work because map's closure only gets called on next events. :-(
I tend to avoid Completable for this very reason, it doesn't seem to play well with other observables. I prefer to use Observable<Void> and send an empty event before the completed...
Something like this:
let chain = Observable<Void>.just()
let foo = chain.map { try example() }
foo.subscribe { event in print(event) }
I would like to hide my Realm implementation and instead of working on RLMNotificationBlock I would like to use RXSwift. Below how my method looks like now (RLMNotificationBlock is a block that takes String and RLMRealm):
func addNotificationBlock(block: RLMNotificationBlock) -> RLMNotificationToken? {
let rlmObject = ...
return rlmObject.addNotificationBlock(block)
}
But I would like to switch to more reactive observer-pattern way. I looked at RxSwift docs and source code of rx_clickedButtonAtIndex, but I cannot figure out how I should put all these things together. I guess my code at the end would look like:
public var rx_realmContentChanged: ControlEvent<Int> {
let controlEvent = ControlEvent()
// My code go here
return controlEvent
}
I'm new with RXSwift and know only the basics. Any help will be appreciated.
There is an Rx Realm extension available on GitHub you can use: https://github.com/RxSwiftCommunity/RxRealm
It allows you to get an Observable out of a single Realm object or a Realm Collection. Here's an example from the README:
let realm = try! Realm()
let laps = realm.objects(Lap.self))
Observable.changesetFrom(laps)
.subscribe(onNext: { results, changes in
if let changes = changes {
// it's an update
print(results)
print("deleted: \(changes.deleted) inserted: \(changes.inserted) updated: \(changes.updated)")
} else {
// it's the initial data
print(results)
}
})
There is also an additional library especially built for binding table and collection views called RxRealmDataSources
If I understood you correctly, you just want to return Observable<RLMNotificationToken>
In this case you just need to do something like this
func addNotificationBlock(block: RLMNotificationBlock) -> Observable<RLMNotificationToken> {
return create { observer -> Disposable in
let rlmObject = ...
let token = rlmObject.addNotificationBlock(block)
// Some condition
observer.onNext(token)
// Some other condition
observer.onError(NSError(domain: "My domain", code: -1, userInfo: nil))
return AnonymousDisposable {
// Dispose resources here
}
// If you have nothing to dipose return `NopDisposable.instance`
}
}
In order to use it bind it to button rx_tap or other use flatMap operator