rxSwift Observable dependency - ios

I’m new to rxSwift. I have 3 observables, the checkAccount, fetchMails and fetchFolders.
fetchMails and fetchFolders depend on the checkAccount result. How can I invoke the fetchMails and fetchFolders operation using the UIButton Tap? And if the checkAccount success, I don't what it to run each time I fetchMails and fetchFolders. If checkAccount Failed, I want to retry checkAccount when fetchMails and fetchFolders. How can I To achieve this purpose? and This is my code:
#IBOutlet weak var btn1: UIButton!
#IBOutlet weak var btn2: UIButton!
var checkAccountO: Observable<Bool>?
let bag = DisposeBag()
let fetchO: Observable<[String]> = Observable.create { observer in
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
observer.onNext(["1","2"])
}
return Disposables.create()
}
let fetchFolderO: Observable<[String]> = Observable.create { observer in
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
observer.onNext(["folder1","folder2"])
}
return Disposables.create()
}
override func viewDidLoad() {
super.viewDidLoad()
fetchFolders().subscribe { (evetn) in
print("folders \(evetn)")
}.addDisposableTo(bag)
fetchMails().subscribe { (evetn) in
print("mails \(evetn)")
}.addDisposableTo(bag)
}
func checkAccount() -> Observable<Bool> {
if let ob = checkAccountO {
return ob
}
checkAccountO = Observable.create { (observer) -> Disposable in
print("checking...")
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
let i = arc4random() % 2
if i == 0 {
print("succ")
observer.onNext(true)
observer.onCompleted()
}else {
print("failed:\(i)")
let err = NSError.init(domain: "err", code: 1001, userInfo: nil)
observer.onError(err)
}
}
return Disposables.create()
}.retry(3).shareReplay(1)
return checkAccountO!
}
func fetchMails() -> Observable<[String]> {
return checkAccount().flatMap({ (_) -> Observable<[String]> in
return self.fetchO
})
}
func fetchFolders() -> Observable<[String]> {
return checkAccount().flatMap({ (_) -> Observable<[String]> in
return self.fetchFolderO
})
}
according to #Timofey Solonin's answer, I change the fetchFoders and fetchMails observable to flatmap from button.rx.tap like this, but still not known how to use retrywhen
func fetchMails() -> Observable<[String]> {
let rxtap = btn1.rx.tap
return rxtap.flatMap { (_) -> Observable<[String]> in
return self.checkAccount().flatMap({ (_) -> Observable<[String]> in
return self.fetchO
})
}
}
func fetchFolders() -> Observable<[String]> {
let rxtap = btn2.rx.tap
return rxtap.flatMap { (_) -> Observable<[String]> in
return self.checkAccount().flatMap({ (_) -> Observable<[String]> in
return self.fetchFolderO
})
}
}

You can use retryWhen operator. flatMap the error stream from retryWhen to checkAccount and if checkAccount will be successful your operation will retry.
To start a stream from button you use button.rx.tap and flatMap it.
For example if you want to fetchMail() from button.rx.tap and checkAcount() multiple times if fetchMail() errored out you can use:
btn1.rx.tap.flatMapLatest {
fetchMails().retryWhen{ errors in
errors.flatMapLatest{ _ in
checkAccount().retry() //or you can use retry(n) if you want to retry checkAccount limited amount of times.
//fetchMails() will repeat if checkAccount() will return anything. Just keep in mind that retryWhen block is not going to be called if checkAccount() was successful.
}
}
}

Related

How to apply [weak self] to a swift function (not closure)

Say I have a network function that has a completion and I use it multiple times in my consumer like this:
class Network {
func getNumber(completion: #escaping (Int) -> ()) {
//some network code
completion(5)
}
}
class MyClass {
var num = 0
let network = Network()
func myFunc() {
network.getNumber { [weak self] (number) in
self?.num = number
}
}
func myFunc2() {
network.getNumber { [weak self] (number) in
self?.num = number
}
}
}
and to avoid duplicate code i replace the closures with a single function
like this:
class MyClass {
var num = 0
let network = Network()
func myFunc() {
network.getNumber(completion: self.handleData)
}
func myFunc2() {
network.getNumber(completion: self.handleData)
}
func handleData(_ number: Int) -> () {
self.num = number
}
}
The problem With this approach is that I am unable to capture self as weak in the handleData function.
The problem could be easily avoided by changing the handleData to be a closure like this:
lazy var handleData: (Int) -> () = { [weak self] in
self?.num = $0
}
So my question is: is there a way to apply weak self for a function and not only a closure?
You could do this if you want to use weak reference to self on it's funcitons:
class MyClass {
var num = 0
func myFunc() {
Network.getNumber { [weak self] in
self?.handleData($0)
}
}
func myFunc2() {
Network.getNumber { [weak self] in
self?.handleData($0)
}
}
func handleData(_ number: Int) {
self.num = number
}
}
Also, you don't have to provide -> () for a function that returns nothing.
Taking into account that handleData can be really big, how about
func myFunc() {
Network.getNumber { [weak self] i in self?.handleData(i) }
}

RXswift, repeat observable until complete

I am trying to upload firmware to a BLE device. Therefore I push some data, wait for an acknowledgment and then push some more, and so on.
But I am struggling with repeating the observer until the observer returns complete.
This is the observer, so if there are no more packets to be sent, it should complete and stop repeating.
let transferImage = Observable<Void>.create { (obs) -> Disposable in
guard let nextPacket = dataSendingTracker.getNextPacket() else {
obs.onCompleted()
return Disposables.create()
}
return self.instrument()
.flatMap{ $0.sendFWpacket(packet: nextPacket) }
.subscribe(onNext: { () in
obs.onNext(())
}, onError: { (error) in
obs.onError(error)
})
}
Any suggestion on how I can achieve this?
Try something like this but I consider this ugly way around.... not Rx Solution
transferImage.takeWhile will stop when getNextPacket returns nil or this case Integer smaller than zero..
func test() {
let transferImage = Observable<Int>.create { (obs) -> Disposable in
if let nextPacket = self.getNextPacket() {
obs.onNext(nextPacket)
} else{
obs.onNext(-1)
obs.onCompleted()
}
return Disposables.create()
}
transferImage.takeWhile{$0 > 0}.subscribe(onNext: { nextPacket in
print(nextPacket)
let completed = self.sendFWpacket()
if !completed {
self.test()
}
}, onError: { error in
print(error)
}, onCompleted: {
print("onCompleted")
}) {
print("disposed")
}.disposed(by: disposeBag)
}
func sendFWpacket()-> Bool {
return false
}
func getNextPacket() -> Int? {
return 1
}
Assuming you have a function that writes a block of data like this test function:
func writeDataBlock(offset: Int, blockSize: Int) -> Observable<Int> {
let writtenBytesCount = min(Int(arc4random_uniform(5) + 5), blockSize)
return Observable<Int>.just(writtenBytesCount)
}
In reality this function would also use some data buffer and try to push a block of a given size from that data at a given offset, and return the number of bytes written when done. Here you could use your logic from transferImage.
Then the complete transfer function could be written using recursion like so:
func writeAllDataRec(offset: Int, totalSize: Int, observer: AnyObserver<String>) {
guard offset < totalSize else {
observer.onNext("writeAllData completed")
observer.onCompleted()
return
}
var subscriber: Disposable?
let blockSize = min(10, totalSize - offset)
subscriber = writeDataBlock(offset: offset, blockSize: blockSize).subscribe { ev in
switch ev {
case let .next(writtenBytesCount):
debugPrint("writeNextBlock from offset: \(offset); writtenBytesCount = \(writtenBytesCount)")
let newOffset = offset + writtenBytesCount
writeAllDataRec(offset: newOffset, totalSize: totalSize, observer: observer)
case .completed:
subscriber?.dispose()
//debugPrint("writeAllData block completed")
case let .error(err):
subscriber?.dispose()
observer.onError(err)
observer.onCompleted()
}
}
}
func writeAllData(totalSize: Int) -> Observable<String> {
return Observable<String>.create { (observer) -> Disposable in
writeAllDataRec(offset: 0, totalSize: totalSize, observer: observer)
return Disposables.create()
}
}
This could be tested like so:
var subscriber: Disposable?
...
self.subscriber = writeAllData(totalSize: 100).subscribe(onNext: { (message) in
print("\(message)")
})
In this solution I assume that your next block of data depends on how much of the data was written previously and that there's no shared global state.
This could be simplified a lot with the logic of RxJS expand, but unfortunately this function is not present in RxSwift (4.1.2).

how to filter Driver<[Repository]> objects in rxswift

struct SearchViewModel {
lazy var rx_SearchResults: Driver<[Repository]> = self.fetchSearchResults()
lazy var rx_FilteredSearchResults: Driver<[Repository]> = self.filteredSearchResults()
fileprivate var searchQuery: Observable<String>
fileprivate var scopeIndex: Observable<Int>
init( searchTextObservable: Observable<String>, changeInScopeIndex: Observable<Int>)
{
self.searchQuery = searchTextObservable
self.scopeIndex = changeInScopeIndex
}
fileprivate func fetchRepositories() -> Driver<[Repository]> {
return repositoryName
.subscribeOn(MainScheduler.instance) // Make sure we are on MainScheduler
.do(onNext: { response in
UIApplication.shared.isNetworkActivityIndicatorVisible = true
})
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.flatMapLatest { text in // .background thread, network request
return RxAlamofire
.requestJSON(.get, "https://api.github.com/users/\(text)/repos")
.debug()
.catchError { error in
return Observable.never()
}
}
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.map { (response, json) -> [Repository] in // again back to .background, map objects
if let repos = Mapper<Repository>().mapArray(JSONObject: json) {
return repos
} else {
return []
}
}
.observeOn(MainScheduler.instance) // switch to MainScheduler, UI updates
.do(onNext: { response in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
})
.asDriver(onErrorJustReturn: []) // This also makes sure that we are on MainScheduler
}
}
Now i am trying to filteredSearchResults like this
**fileprivate func filteredSearchResults() -> Driver<[FilesListSearchData]> {
return scopeIndex
.subscribeOn(MainScheduler.instance) // Make sure we are on MainScheduler
.do(onNext: { response in
UIApplication.shared.isNetworkActivityIndicatorVisible = true
})
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.map { index in // again back to .background, map objects
return self.rx_SearchResults.asDriver()
}
.observeOn(MainScheduler.instance) // switch to MainScheduler, UI updates
.do(onNext: { response in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
})
}**
// Above method is wrong i am stuck how can i filter data now based on scope index.
As you people understood i am using MVVM I have two quires.
How can show progress on my view controller which is doing here by this line
UIApplication.shared.isNetworkActivityIndicatorVisible = false/true
How can i filter Driver based on selected scope index.

Chain sequence observables with arguments

I'm fairly new to RxSwift, so I have the following problem, lets suppose I have 3 Observable example functions which return different observable types:
func observableFunc1(item1: DummyObject) -> Observable<AVURLAsset> {
return Observable.create { observer in
let something_with_AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: "file"))
observer.onNext(something_with_AVURLAsset)
observer.onCompleted()
return Disposables.create()
}
}
func observableFunc2(item: AVURLAsset) -> Observable<Data> {
return Observable.create { observer in
let something_with_data = Data()
observer.onNext(something_with_data)
observer.onCompleted()
return Disposables.create()
}
}
func observableFunc3(_ params: [String:Any]) -> Observable<Any> {
let disposeBag = DisposeBag()
return Observable.create { observer in
RxAlamofire.request(api.sendData(params)).debug().subscribe(
onNext: { reponse in
observer.onCompleted()
},
onError: { error in
observer.onError(customError.theError("error"))
}
)
.addDisposableTo(disposeBag)
return Disposables.create()
}
}
How can I execute these 3 functions sequentially with the return value of func1 to be used on func2 and then when func2 is completed finally run func3.
I hope I made the question clear enough, but then again I'm really new to RxSwift and I don't know if these operations are possible or not.
Here's an example...
Assuming you have the three functions:
func func1() -> Observable<Data1>
func func2(_ data: Data1) -> Observable<Data2>
func func3(_ data: Data2) -> Observable<Data3>
Then you can:
let a = func1()
let b = a.flatMap { func2($0) }
let c = b.flatMap { func3($0) }
or:
let c = func1()
.flatMap { func2($0) }
.flatMap { func3($0) }
That said, your observableFunc3 is quite broken. You need to remove the dispose bag from it. As it stands, the network call will cancel before it starts.
If you really don't want it to emit any values then:
func observableFunc3(_ params: [String:Any]) -> Observable<Void> {
return RxAlamofire.request(api.sendData(params))
.filter { false }
}
The above will emit either a completed or an error but no next values.
Better would be to write it like:
func observableFunc3(_ params: [String:Any]) -> Observable<Void> {
RxAlamofire.request(api.sendData(params))
.map { _ in }
}
The above will emit one next and then a completed or an error. This is better because you can map or flatMap after it to have things happen once it's done.

LongPolling With rxSwift

I studying rxSwift, and I want to do service for the interaction of c longpolling server to this service imitating a permanent connection. I wrote it, but it seems to me, is not that the decision could have been done better? Is it possible to somehow repeat the Observable, regardless of the error, and depending on longpoll server response.
Can anyone can share the solution? Or help with advice? How it is better to organize? I would like to see a better solution, since only began studying rxswift
class LongPollingService {
public var messageReciver: PublishSubject<EventProtocol> = PublishSubject<EventProtocol>()
private let transport = DefaultTransport()
private let disposeBag = DisposeBag()
private var currentRequestInfo = Variable<LongpollingServerInfo?>(nil)
private var currentRequestDisposable: Disposable?
private var currentLongpollingConnection: Disposable? // Subsribee for request server info
private var eventListener : Disposable?
private var currentReqursiveConnection: Disposable? // Subscriber for event listener from longpoll server
func startObservableEvents() {
getServerConnection()
subscribeServerInfo()
//testing listen events
eventListener = messageReciver.showMessagesInDebugMode().subscribe()
eventListener?.addDisposableTo(disposeBag)
}
func disconnect() {
currentRequestDisposable?.dispose()
currentLongpollingConnection?.dispose()
currentReqursiveConnection?.dispose()
}
private func subscribeServerInfo() {
currentLongpollingConnection = currentRequestInfo
.asObservable()
.filter({$0 != nil})
.subscribe(onNext: { [weak self] (info) in
guard let sSelf = self else { return }
sSelf.subscribeToEvents(timeStamp: info!.ts)
})
currentLongpollingConnection?.addDisposableTo(disposeBag)
}
private func subscribeToEvents(timeStamp: TimeInterval) {
if let serverInfo = currentRequestInfo.value {
currentReqursiveConnection?.dispose()
currentReqursiveConnection = getEventsFromLongpollServer(serverInfo: serverInfo, with: timeStamp)
.flatMap(parseUpdates)
.flatMap(reciveEvents)
.showErrorsSwiftMessagesInDebugMode()
.subscribe(onNext: { [weak self] updates in
guard let sSelf = self else { return }
sSelf.subscribeToEvents(timeStamp: updates)
},
onError: { [weak self] error in
guard let sSelf = self else { return }
if let error = error as? LongPollError {
switch error {
case .olderHistory(let ts): sSelf.subscribeToEvents(timeStamp: ts)
default: sSelf.getServerConnection()
}
}
})
currentReqursiveConnection?.addDisposableTo(disposeBag)
}
}
private func getServerConnection() {
//get longpolling server info for connection.
currentRequestDisposable = getLongpollServerInfo()
.subscribe(onNext: {[weak self] info in
guard let sSelf = self else { return }
sSelf.currentRequestInfo.value = info
})
currentRequestDisposable?.addDisposableTo(disposeBag)
}
private func parseUpdates(json: Any) throws -> Observable<LongPollingUpdates> {
let response = try Mapper<LongPollingUpdates>().map(JSONObject: json)
return .just(response)
}
private func reciveEvents(updates:LongPollingUpdates) throws -> Observable<TimeInterval> {
if let errors = updates.failed {
throw parseErrors(errors: errors)
}
if let events = updates.updates {
parseUpdates(updates: events)
}
return Observable.just(updates.timeStamp!)
}
private func parseUpdates(updates: [[Any]]) {
updates.forEach { (array) in
let firstElementInUpdate = array.first
if let update = firstElementInUpdate as? Int {
switch update {
case 1: break
case 2: break
case 3: break
case 4: messageReciver.onNext(NewMessage(array: array))
default: break
}
}
}
}
private func parseErrors(errors: [String: Any]) -> LongPollError {
if let error = errors["failed"] as? Int {
switch error {
case 1:
guard let ts = errors["ts"] as? TimeInterval else { return .unkownError }
return .olderHistory(ts: ts)
case 2: return .needNewkey
case 3: return .needCaseAndTs
case 4: return .unkownVersion
default:
return .unkownError
}
}
return .unkownError
}
private func getEventsFromLongpollServer(serverInfo: LongpollingServerInfo, with ts: TimeInterval) -> Observable<Any> {
let url = buildLongPollingServerRoute(from: serverInfo, with: ts)
let request = buldLongPollRequst(route: url)
let requestConvert = try? URLEncoding.default.encode(request!, with: nil)
return transport.makeRequest(request: requestConvert!)
}
private func getEventsFromLongpollServer(serverInfo: LongpollingServerInfo) -> Observable<Any> {
let url = buildLongPollingServerRoute(from: serverInfo)
let request = buldLongPollRequst(route: url)
let requestConvert = try? URLEncoding.default.encode(request!, with: nil)
return transport.makeRequest(request: requestConvert!)
}
private func getLongpollServerInfo() -> Observable<LongpollingServerInfo> {
let request = MessageRouter.getLongpollServer(useSsl: false, needPts: false)
return transport.makeModel(request: request)
}
}
So assuming you have a function like:
func getData() -> Observable<Data>
And you want to long poll it at a specific period, you can do something like this:
Observable<Int>.interval(period, scheduler: MainScheduler.instance)
.map { _ in return }
.flatMap(getData)
.subscribe( /* ... handle data ... */)
.disposed(by: disposeBag)
You can use other schedulers than MainScheduler if that is more appropriate.
Now if you want also handle Errors that getData might emit and you don't want that to necessarily unsubscribe the long polling, then you can do this:
func handleError(error: Error) -> Observable<Data> {
return Observable.empty()
}
Observable<Int>.interval(period, scheduler: MainScheduler.instance)
.map { _ in return }
.flatMap { return getData.catchError(handleError) }
.subscribe( /* ... handle data ... */)
.disposed(by: disposeBag)
You can also analyze the error in handleError and decide if you want to continue by emitting an empty Observable or cancel the long polling by emitting another error.

Resources