I've been using ReactiveSwift for a few months now but there is a something that I dont fully understand: lifetime objects.
For example, lets say I have a SignalProducer which will make an API call, this is wrapped on a class:
class ServiceWrapped {
private let service: Service // the method called on this object returns the SignalProducer
private let (lifetime, token) = Lifetime.make()
// more stuff
func fetchSomething(completion: #escaping (Value?, Error?) -> Void) {
lifetime += service.fetchSomething()
.startWithResult { result in
switch result {
case .success(let value):
completion(value, nil)
case .failure(let error):
completion(nil, error)
}
}
}
}
My question is: Is it necessary to use lifetime on this case?
I understood that lifetime will retain the service call so it has something when it return but since this is also wrapped on ServiceWrapped I don't think using lifetime is really necessary.
Thanks in advance.
You're correct that you don't need to keep the result of startWithResult in order to keep the subscription alive. The relevant part of the documentation says:
A Signal must be publicly retained for attaching new observers, but not necessarily for keeping the stream of events alive. Moreover, a Signal retains itself as long as there is still an active observer.
So as long as you don't dispose the object returned from startWithResult, the operation will continue even if you don't retain it.
Rather, Lifetime is about cancelling operations. In this case, because you've attached the result of startWithResult to ServiceWrapped's lifetime, the operation will be cancelled when the ServiceWrapped object is deallocated. If you omit lifetime +=, then the operation will continue even if ServiceWrapped is deallocated.
A practical example of why this is useful is if you have a view controller that loads an image from the web. If the user dismisses the view controller before the image is finished loading then you probably want to cancel the web request. You can do that by tying the image load producer to the lifetime of the view controller. It's not about keeping the web request alive, it's about cancelling it when it's no longer necessary.
As an aside about style, the documentation recommends you use operators rather than handling the result of the startWithResult:
func fetchSomething(completion: #escaping (Value?, Error?) -> Void) {
service.fetchSomething()
.take(during: lifetime)
.startWithResult { result in
switch result {
case .success(let value):
completion(value, nil)
case .failure(let error):
completion(nil, error)
}
}
}
Related
I know there are a lot of questions out there that have been answered on how to use #escaping functions in general. My issue is a bit more niche as I am working with an API that gives me a function that takes in an #escaping function (or so I think). I need help decoding both (1) the function declaration I am working with and what it means and (2) how I write a function to effectively call it, and complete it and be able to exit.
The API function is declared as so (with some stuff hidden), wrapped in a larger struct, i'll call specialStruct:
public func context(completion: #escaping ((Result<String, SpecialClassError>) -> Void)) {
class.something() { result in
switch result {
case .success(let response):
completion(.success(response.cid))
case.failure(let error):
completion(.failure(.network(error: error), data: nil)))
}
}
}
Currently, I am running this:
specialStruct.context(completion: {result in
switch result {
case .success(let str):
let _ = print(str)
case .failure(let error):
let _ = print(error.localizedDescription)
}
})
This is what happens as I step through my handler, which is a bit confusing to me:
It is wrapped in an init() in a SwiftUI View. It goes through once at the start, but doesn't actually step into context? It seems to start, but doesn't do anything with result.
Code keeps running...eventually comes back to my call at case .success(let str):.
Runs the next line, and this successfully prints the expected value from the API after connecting to it. let _ = print(str)
Goes to end of call line at bottom })
Which brings me back to the context() declaration shown above, at completion(.success(response.cid))
Jumps to the second to last } in the function declaration.
Jumps into the something() call, specifically a line that is completion(.success(decoded))
Continues in something() call, eventually landing back at an Apple Module FPRNSURL...nInstrument and line 307 completionHandler(data, response, error);
Here it stays for good.
Let me know if that made it more confusing that it needs to be! Thanks!
EDIT:
Added a note that in step (2) above, the API has already been connected to and returned the expected value. For some reason it goes back to the completion handler and gets hung up, even though I am already done.
The steps you wrote exactly describes Asynchronous API calls, this is the expected behavior as the api takes time to give you the requested result & Swift won't wait until it does..
Also, in the function context, you don't need all that code, you can simply do:
public func context(completion: #escaping ((Result<String, SpecialClassError>) -> Void)) {
class.something() { result in
completion(result)
}
}
I have a function like this.
class func getAccountBalances(completionHandler:#escaping ((_ balances:Any) -> Void)){
//Alamofire request and we get the result. But sometimes the result fails.
switch response.result {
case .success(let value):
completionHandler(value)
case .failure(let error):
print ("error is: \(error)")
}
I am not putting code to handle the result if it fails. Is that a bad thing? Do I need to have a completion handler in the case that the call fails so that this function does not stay in the memory waiting for that completion handler to be called? What is the best practice?
In general, it is a good practice to call the completion on every case. The reason for this is that you usually want to let the upper lever(business logic layer) decide if it should mark some balances(for example) as saved, or maybe show a dialog when an error has occurred. That is a good practice with the thinking that everything should be a module. That being said, if another module will want to call the same function at some point, it may be a good thinking to let that module what happened with the result. That can be implemented in several ways, I won't enter here, it's your decision.
However, it's not a must to do it. If a block won't be called it should be deallocated, and then everything is good memory-wise. So in your example, if you don't retain the block somewhere else(for example holding it in a variable inside the class that makes getAccountBalances call), you should be just fine.
Another important part is when you call the function be careful to not create a memory leak where you retain the self inside the block:
getAccountBalances() { _ in
self.updateUI()
}
This block will create a retain to self and if everything goes okay with the call, but user left the screen, you may end up using variables that were deallocated and crash the app. A good practice here is to not retain the self in the callback, but make it weak before that:
getAccountBalances() { [weak self] _ in
// This is not necessarily needed, and you could use self?.updateUI() instead.
// However, this is usually another thing i like to do and consider it a good practice
guard let `self` = self else { return }
self.updateUI()
}
Your title: Completion handler never called
Your code: does not return any value to the completion handler if there is an error.
How would you expect to see a result of the competion handler if there is a failure? Would you like to crash the app? This way is better because it handles both cases:
class func getAccountBalances(completionHandler:#escaping ((_ balances:Any?) -> Void)){ //make Any an optional
//Alamofire request and we get the result. But sometimes the result fails.
switch response.result {
case .success(let value):
completionHandler(value)
case .failure(let error):
print ("error is: \(error)")
completionHandler(nil)
}
Your new call to this function:
getAccountBalances() { value in
guard let _value = value else { // anticipate on the error return }
// use _value, the balances are inside.
}
An other approach would be not making it nil, but downcasting the value inside of it. That would look like this:
class func getAccountBalances(completionHandler:#escaping ((_ balances:Any) -> Void)){
//Alamofire request and we get the result. But sometimes the result fails.
switch response.result {
case .success(let value):
completionHandler(value)
case .failure(let error):
print ("error is: \(error)")
completionHandler(error) //notice this change
}
Then your function would look like this:
getAccountBalances() { value in
if let error = value as? Error { //or whatever type your error is in the function
//there is an error
}
}
I understand that you can not register a Realm .observe block on an object or collection if the Realm is in a write transaction.
This is easier to manage if everything is happening on the main thread however I run into this exception often because I prefer to hand my JSON parsing off to a background thread. This works great because I don't have to bog down the main thread and with Realm's beautiful notification system I can get notified of all modifications if I have already registered to listen for those changes.
Right now, if I am about to add an observation block I check to make sure my Realm is not in a write transaction like this:
guard let realm = try? Realm(), !realm.isInWriteTransaction else {
return
}
self.myToken = myRealmObject.observe({ [weak self] (change) in
//Do what ever
}
This successfully guards against this exception. However I never get a chance to re - register this token unless I get a little creative.
Does the Realm team have any code examples/ suggestions on a better pattern to avoid this exception? Any tricks I'm missing to successfully register the token?
In addition to the standard function, I do use an extension for Results to avoid this in general. This issue popped up, when our data load grew bigger and bigger.
While we do now rewrite our observe functions logic, this extension is an interims solution to avoid the crashes at a first place.
Idea is simple: when currently in a write transaction, try it again.
import Foundation
import RealmSwift
extension Results {
public func safeObserve(on queue: DispatchQueue? = nil,
_ block: #escaping (RealmSwift.RealmCollectionChange<RealmSwift.Results<Element>>) -> Void)
-> RealmSwift.NotificationToken {
// If in Write transaction, call it again
if self.realm?.isInWriteTransaction ?? false {
DispatchQueue.global().sync {
Thread.sleep(forTimeInterval: 0.1) // Better to have some delay than a crash, hm?
}
return safeObserve(on: queue, block)
}
// Aight, we can proceed to call Realms Observe function
else {
return self.observe(on: queue, block)
}
}
}
Then call it like
realmResult.safeObserve({ [weak self] (_: RealmCollectionChange<Results<AbaPOI>>) in
// Do anything
})
When we get table view datasource, we will ask a network request. It is asynchronous. I have no idea to test the result operation. There is a method to get the points.
func loadPoints() {
API().retrievePoints{ [weak self](pointsSet, error) in
DispatchQueue.main.async(execute: {
// Make sure the call succeeded; return an error if it didn't
guard error == nil else {
self?.showErrorMessage()
Device.debugLog(item:"Error loading Points: \(String(describing: error))")
return
}
self?.pointsSet = pointsSet
self?.tableView.reloadData()
})
}
}
I have known that if we want to test the retrievePoints method, we can test like bellow
//points
func testRetrievePoints() {
let expectation = self.expectation(description: "RetrievePoints")
API().retrievePoints{ (pointsSet, error) -> Void in
XCTAssertNil(pointsSet)
XCTAssertNotNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 15.0, handler: nil)
}
Now I want to test the codes
self?.pointsSet = pointsSet
self?.tableView.reloadData()
self?.showErrorMessage()
For now I just use sleep(15) to wait the method. But it is inaccurate.
Could you please help me? Thanks in advance.
Just as what you have said, it's asynchronous. So it will take time before finish. That is to say that you need to wait before it can success.
Also note that it's just timeout value. All your task must finish within this value. Or it will be treated as failure.
You want to test your data source — not your web service.
there for you should mock the api call.
To achieve this you could use a mocking framework. But I'd rather go another route:
create a protocol that declares the public interface of API, let API conform to that protocol
pass the API as a dependency into the data source. either as an init parameter or via a property. Passing objects is easier that classes, I'd change retrievePoints to be an instance method.
For your test write an APIMock that implements the protocol. Let retrievePoints' callback return prepared points.
Now the points will be returned immediately, no need for timeouts. If you want to defer that your mock can use a DispatchQueue.main.asyncAfter call.
Here is the scenario, everything works but I get hanged up on the main queue. I have:
singleton class to manage API connection. Everything works (execution time aside....)
a number of view controllers calling GET API via the above singleton class to get the data
I normally call the above from either viewDidLoad or viewWillAppear
they all work BUT ....
if I call a couple of API methods implemented with Alamofire.request() with a closure (well, I need to know when it is
time to reload!), one of the two gets hung waiting for the default
(main) queue to give it a thread and it can take up to 20 seconds
if I call only one, do my thing and then call a POST API, this
latter one ends up in the same situation as (5), it takes a long
time to grab a slot in the default queue.
I am not specifying a queue in Alamofiore.request() and it sounds to me like I should so I tried it. I added a custom concurrent queue to my singleton API class and I tried adding that to my Alamofiore.request() .....and that did absolutely nothing. Help please, I must be missing something obvious?!
Here is my singleton API manager (excerpt) class:
class APIManager {
// bunch of stuff here
static let sharedInstance = APIController()
// more stuff here
let queue = DispatchQueue(label: "com.teammate.response-queue", qos: .utility, attributes: [.concurrent])
// more stuff
func loadSports(completion: #escaping (Error?) -> Void) {
let parameters: [String: Any?] = [:]
let headers = getAuthenticationHeaders()
let url = api_server+api_sport_list
Alamofire.request(url, method: .get, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString (queue: queue) { response in
if let json = response.result.value {
if let r = JSONDeserializer<Response<[Sport]>>.deserializeFrom(json: json) {
if r.status == 200 {
switch r.content{
case let content as [Sport]:
self.sports = content
NSLog("sports loaded")
completion(nil)
default:
NSLog("could not read the sport list payload!")
completion(GenericError.reportError("could not read sports payload"))
}
}
else {
NSLog("sports not obtained, error %d %#",r.status, r.error)
completion(GenericError.reportError(r.error))
}
}
}
}
}
// more stuff
}
And this is how I call the methods from APIManager once I get the sigleton:
api.loadSports(){ error in
if error != nil {
// something bad happened, more code here to handle the error
}
else {
self.someViewThingy.reloadData()
}
}
Again, it all works it is just that if I make multiple Alamofire calls from the same UIViewController, the first is fast, every other call sits for ever to get a spot in the queue an run.
UI updates must happen on the main queue, so by moving this stuff to a concurrent queue is only going to introduce problems. In fact, if you change the completion handler queue to your concurrent queue and neglect to dispatch UI updates back to the main queue, it's going to just make it look much slower than it really is.
I actually suspect you misunderstand the purpose of the queue parameter of responseString. It isn't how the requests are processed (they already happen concurrently with respect to the main queue), but merely on which queue the completion handler will be run.
So, a couple of thoughts:
If you're going to use your own queue, make sure to dispatch UI updates to the main queue.
If you're going to use your own queue and you're going to update your model, make sure to synchronize those updates with any interaction you might be doing on the main queue. Either create a synchronization queue for that or, easier, dispatch all model updates back to the main queue.
I see nothing here that justifies the overhead and hassle of running the completion handler on anything other than the main queue. If you don't supply a queue to responseString, it will use the main queue for the completion handlers (but won't block anything, either), and it solves the prior two issues.