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

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) }
}

Related

Cannot convert value of type 'HRes' to expected argument type 'Res'

private var reservations: [Res]?
func prepareForUse(completion: #escaping () -> Void) {
service.fetchReservationsFromDatabase { [weak self] (reservations4) in
DispatchQueue.global(qos: .default).async { [weak self] in
var reservations3 = [Reservation]()
for reservation in reservations4 {
if let reservation3 = reservation.oldReservation() {
reservations3.append(reservation3)//error in this line
}
}
}
}
}
func oldReservation() -> HRes? { }
Since reservation.oldReservation() has return type HRes, I'm getting this error.
How do I convert HRes value to expected argument type Res?
I tried adding, still doesn't help:
if let reservation3 = reservation.oldReservation() {
reservations3.append(reservation3) as? Res
}

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).

rxSwift Observable dependency

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.
}
}
}

Callbacks in iOS swift 3

Hello is it possible to make a callback like such? I want to pass a function as a parameter to be able run the callback function after some task is finished.
class ConnectBLE {
var callBackFunc: ()->()
init(callFunc: #escaping () -> ()){
callBackFunc = callFunc
}
func runCallBackFunc() {
callBackFunc()
}
}
class DelegateARC {
private let object = ConnectBLE(callFunc: RaspakHC05)
func RaspakHC05() {
print("hello from a callback")
}
}
But I'm having an error.
Cannot convert value of type '(DelegateARC) -> () -> ()' to expected argument type '() -> ()'
You cannot run non-lazy code on the top level of the class which requires self (RaspakHC05).
Apart from that you have to call runCallBackFunc() in ConnectBLE somewhere to execute the closure.
You could do (in a Playground)
class ConnectBLE {
var callBackFunc: ()->()
init(callFunc: #escaping () -> ()){
callBackFunc = callFunc
}
func runCallBackFunc() {
callBackFunc()
}
}
class DelegateARC {
init() {
ConnectBLE(callFunc: RaspakHC05).runCallBackFunc()
}
func RaspakHC05() {
print("hello from a callback")
}
}
DelegateARC() // prints "hello from a callback"
Another way (use Optional to delay giving object its real value until initialization has otherwise fully taken place):
class ConnectBLE {
var callBackFunc: ()->()
init(callFunc: #escaping () -> ()){
callBackFunc = callFunc
}
func runCallBackFunc() {
callBackFunc()
}
}
class DelegateARC {
private var object : ConnectBLE! //*
init() {
self.object = ConnectBLE(callFunc: RaspakHC05) // *
}
func RaspakHC05() {
print("hello from a callback")
}
}
You are trying to pass a function instead a closure. You have to use a closure and lazy instantiation to make it work:
class DelegateARC {
private lazy var object: ConnectBLE = {
return ConnectBLE(callFunc: self.RaspakHC05)
}()
var RaspakHC05: () -> () {
return {
print("hello from a callback")
}
}
}

RxSwift+Moya+Moya_ObjectMapper+MJRefresh, refresh failed?

I'm newer to RxSwift. I want to refresh the tableview to show new data.The first request that I can get the data. but when I pull down the tableview, the request didn't finished. I have no ideas about this? My code is belowing:
1: My viewController's code:
class RecommendViewController: UIViewController {
lazy var tableView = DefaultManager.createTableView(HomeImageCell.self,
HomeImageCell.idenfitier)
let disposeBag = DisposeBag()
lazy var viewModel = HomeViewModel()
lazy var dataSource: [HomeListDetailModel] = []
override func viewDidLoad() {
super.viewDidLoad()
viewModel.fetchRecommendList("answer_feed",0)
setupTableView()
configureRefresh()
bindDataToTableView()
}
func setupTableView() {
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.edges.equalTo(0)
}
tableView.estimatedHeight(200)
}
func bindDataToTableView() {
viewModel.recommend
.observeOn(MainScheduler.instance)
.do(onNext: { [unowned self] model in
print("endAllRefresh")
self.endAllRefresh()
}, onError: { (error) in
self.endAllRefresh()
print("error = \(error)")
})
.map { [unowned self] model in
return self.handleData(model)
}.bind(to: tableView.rx.items(cellIdentifier: HomeImageCell.idenfitier , cellType: HomeImageCell.self )) { index, model, cell in
cell.updateCell(data: model)
}.disposed(by: disposeBag)
}
func configureRefresh() {
tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: { [unowned self] in
let model = self.dataSource[0]
self.viewModel.fetchRecommendList("answer_feed",model.behot_time)
})
tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { [unowned self] in
let model = self.dataSource[self.dataSource.count - 1]
self.viewModel.fetchRecommendList("answer_feed",model.behot_time)
})
}
func endAllRefresh() {
self.tableView.mj_header.endRefreshing()
self.tableView.mj_footer.endRefreshing()
}
func handleData(_ model: HomeListModel) -> [HomeListDetailModel] {
guard let data = model.detailData else {
return dataSource
}
self.dataSource = data
return data
}
}
2: My ViewModel
protocol HomeProtocol {
func fetchRecommendList(_ category: String, _ behot_time: Int)
}
class HomeViewModel: HomeProtocol {
lazy var provider = HTTPServiceProvider.shared
var recommend: Observable<HomeListModel>!
init() {}
init(_ provider: RxMoyaProvider<MultiTarget>) {
self.provider = provider
}
func fetchRecommendList(_ category: String, _ behot_time: Int) {
recommend = provider.request(MultiTarget(HomeAPI.homeList(category: category,behot_time: behot_time)))
.debug()
.mapObject(HomeListModel.self)
}
}
When I made a breakpoint at request method, it didn't do a request? Does anyone know it ? Thanks first
SomeOne told me the reason,So I write it here. In my ViewModel recommend should be backed by PublishSubject or BehaviourSubject or ReplaySubject and then I should share this for View as Observable. In fetchRecommentList method I should bind request to created Subject.
Now I have created observable, but request will run after subsribe or bind

Resources