Who passed the arguments to the parameters of the complition handler closure? - ios

func startUpdates(from start: Date,
withHandler handler: #escaping CMPedometerHandler)
typealias CMPedometerHandler = (CMPedometerData?, Error?) -> Void
The above function retrieves the pedometer data from your iOS device. When I called the function the only argument I need passed to is the parameter from start.
Who actually initialized the parameter list of the completion handler closure? The startUpdates function I've called?

When I called the function the only argument I need to passed to is the parameter from start
That's not true.
You have to pass also the closure as second parameter. The closure itself is called by the startUpdates function after doing its work and passes two parameters back, an optional Data and an optional Error instance.
The functional programming is a very convenient way to be able to run arbitrary code (in the closure).
You can declare the closure separately
let result : CMPedometerHandler = { data, error in
if let error = error { print(error); return }
// do something with the data
}
startUpdates(from: Date(), withHandler: result)
or inline
startUpdates(from: Date(), withHandler: { data, error in
if let error = error { print(error); return }
// do something with the data
})
or with trailing closure syntax
startUpdates(from: Date()) { data, error in
if let error = error { print(error); return }
// do something with the data
}

Related

How to flatMap two Publishers with different Failure types in Combine

I follow a pattern in my Rx code, I usually have an Observable trigger which I flatMap to create another Observable for a network request. A simplified example:
enum ViewModelError: Error {
case bang
}
enum DataTaskError: Error {
case bang
}
func viewModel(trigger: Observable<Void>,
dataTask: Observable<Result<SomeType, DataTaskError>>) -> Observable<Result<AnotherType, ViewModelError>> {
let apiResponse = trigger
.flatMap { dataTask }
}
The Combine equivalent I'm having some trouble with. I could use a Result as the Output type and use Never as the Failure type but that feels like a misuse of the API.
func viewModel(trigger: AnyPublisher<Void, Never>,
dataTask: AnyPublisher<SomeType, DataTaskError>) -> AnyPublisher<AnotherType, ViewModelError> {
let apiResponse = trigger
.flatMap { dataTask }
}
I get a compilation error:
Instance method 'flatMap(maxPublishers:_:)' requires the types 'Never' and 'DataTaskError' be equivalent
I could use mapError and cast both of the errors to Error, but I need a DataTaskError to be able to create my ViewModelError.
This feels like it shouldn't be so difficult, and it seems like a fairly common use case. I'm likely just misunderstanding some fundamentals, a point in the right direction would be greatly appreciated.
When you have a publisher with Never as failure type, you can use setFailureType(to:) to match the failure type of another publisher. Note that this method can only be used when the failure type is Never, according to the doc. When you have an actual failure type you can convert the error with mapError(_:). So you can do something like this:
func viewModel(trigger: AnyPublisher<Void, Never>,
dataTask: AnyPublisher<SomeType, DataTaskError>) -> AnyPublisher<AnotherType, ViewModelError> {
trigger
.setFailureType(to: ViewModelError.self) // Publisher<Void, ViewModelError>
.flatMap {
dataTask // Publisher<SomeType, DataTaskError>
.mapError { _ in ViewModelError.bang } // Publisher<SomeType, ViewModelError>
.map { _ in AnotherType() } // Publisher<AnotherType, ViewModelError>
}
.eraseToAnyPublisher()
}

Cast value to enum that inherits from Error

I have a service class that offers several methods that make calls to the backend, and a common theme for those methods is that I can pass callbacks for the success and error cases:
func makeCall(onSuccess: #escaping APIResponse, onError: #escaping APIError)
The type APIError is defined like so:
typealias APIError = (Error) -> Void
Furthermore I have an enum APICallError like this:
enum APICallError: Error {
case insufficientCredentials
case malformedResponse
case forbidden
}
So in my service methods I can call onError(response.result.error!) if the result contained an error object (the force unwrap is just for brevity, I'm not really doing that) and also pass my own enum value if the HTTP result is not specific enough, like onError(APICallError.insufficientCredentials).
This works fine.
The problem is, I can't figure out how to evaluate in my client code whether the error parameter that's coming in is of type APICallError and which one of those specifically. If I do:
if let callError = error as? APICallError {
if callError == .forbidden {
// ...
}
}
execution doesn't make it past the typecast. if error is APICallError also does not seem to work.
How can I cast the error parameter to my APICallError enum value that I know is in there, because when I print(error) it gives me myapp.APICallError.forbidden?
I tried to simulate what you have posted in your question in Playground, and what you are already doing should work fine for you.
if error is APICallError is also working. One possibility why the if let condition fails might be due to the error being nil. Check if that is the case by using breakpoints.
typealias APIError = (Error) -> Void
//The error type used in your question
enum APICallError: Error {
case insufficientCredentials
case malformedResponse
case forbidden
}
//A different error type for comparison
enum AnotherError: Error {
case error1
case error2
case error3
}
let apiCallError: Error = APICallError.insufficientCredentials
let anotherError: AnotherError = AnotherError.error1
//Closure definition
var onError: APIError? = { inputError in
if let apiError = inputError as? APICallError {
print("APICallError")
}
if let anotherError = inputError as? AnotherError {
print("AnotherError")
}
}
//Above defined closure is called here below...
onError?(apiCallError)
onError?(anotherError)
Console Output (Works as expected):
APICallError
AnotherError
You need to use the enum rawValue constructor.
if let callError = APICallError(rawValue: error) {
if callError == .forbidden {
...
}
} else {
// was not an APICallError value
}

Code being executed after completion handler is called

Why is this continuing after the completion handler is called?
See the comments in the code. See the code path to get to #1. At this point I'm expecting the code to call the completion handler complete(), and return from the function, preventing execution of #2. However, code at #2 still appears to be getting triggered. Any ideas why this os occurring?
func syncSessionLog(withCompletion complete: #escaping ((Bool, String?) -> Void)) {
... bunch of code
managedObjectContext.performAndWait {
let trackFetchRequest: NSFetchRequest<NSFetchRequestResult> = Track.fetchRequest()
let trackPredicate = NSPredicate(format: "id == \(session.track_id)")
trackFetchRequest.predicate = trackPredicate
trackFetchRequest.fetchLimit = 1;
do {
let foundTrack = try self.managedObjectContext.fetch(trackFetchRequest) as! [Track]
if foundTrack.count < 1 {
self.debug.log(tag: "SessionManager", content: "not found tID: \(session.track_id)")
//#1 When not found, complete is called, yet the code still manages to reach "do stuff" down the bottom.
complete(false, "Not found")
return
}
associatedTrack = foundTrack[0]
}
catch {
self.debug.log(tag: "SessionManager", content: "Failed to get Track object from Core Data: \(error.localizedDescription)")
fatalCoreDataError(error)
complete(false, "Failed to retrieve")
}
}
//#2 do stuff with associatedTrack
return will exit out of the current context, which is the closure associated with the performAndWait. After that closure returns, execution continues with the next statement after performAndWait which is whatever is at #2.
You can move the code from point #2 inside the closure
Its quite simple - the return statement is inside a block, so it returns from the block, not from the outside method. It would be more visible if the block had some return value.
As such, this return is not needed in your code. You will need to set a Bool flag to indicate the result of block execution and act accoridngly in #2.

Completion Handler - Parse + Swift

I'm trying to generate an array of PFObjects called 'areaList'. I've been researching this quite a bit and understand that I could benefit from using a completion handler to handle the asynchronous nature of the loaded results. My ask, specifically, is to get some guidance on what I'm doing wrong as well as potential tips on how to achieve the result "better".
Here is my query function with completion handler:
func loadAreasNew(completion: (result: Bool) -> ()) -> [Area] {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for area in areas! {
let areaToAdd = area as! Area
areaList.append(areaToAdd)
// print(areaList) // this prints the list each time
// print(areaToAdd) // this prints the converted Area in the iteration
// print(area) // this prints the PFObject in the iteration
if areaList.count == areas!.count {
completion(result: true)
} else {
completion(result: false)
}
}
} else {
print("There was an error")
}
}
return areaList
}
Here is how I'm attempting to call it in viewDidLoad:
loadAreasNew { (result) -> () in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
I assigned this variable before viewDidLoad:
var areaList = [Area]()
In the console, I get the following:
Didn't Work
Didn't Work
Didn't Work
Didn't Work
[]
Representing the 5 items that I know are there in Parse...
This is an interesting question. First off, PFQuery basically has a built in completion handler, which is quiet nice! As you probably know, all of the code within the areaQuery.findObjectsInBackgroundWithBlock {...} triggers AFTER the server response. A completion most often serves the purpose of creating a block, with the ability of asynchronously returning data and errors.
Best practice would (IMO) to just call the code that you want to use with the results from your PFQuery right after your area appending loop (which I'm gonna take out because I'm picky like that), like so:
func loadAreasNew() {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let areasFormatted = areas! As [Areas]
areasList += areasFormatted
//Something like this
self.codeINeedAreasFor(areasList)
}
} else {
print(error)
}
}
}
HOWEVER! If you really feel the need to use some completion handlers, check out this other answer for more info on how to use them. But keep in mind all tools have a time and a place...
There are a few issues here.
Your completion handler doesn't require you to define the name for your completion handler's parameters, so you could easily use completion: (Bool) -> ()
Further in your function, you're returning areaList. This should be put through the completion handler like this onComplete(areaList) and change your completion handler parameter to expect your area list.
Then, when you call your function, it could look more like this :
loadAreasNew { result in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
Here is my concern:
1) Don't pass in a local variable and make the function return it, it's meaningless and danger.
You may want to initiate an empty array and make your fetch, then "return" it.
2) The fetch request is processed in background, you will have no idea when it will have finished. If you return the array immediately it will always be an empty array.
Put the "return" in your completion too.
3) Parse already has a distance checking method, you don't have to do it manually. aPARSEQUERRY.where(key:,nearGeoPoint:,inKilometers:)
I will rewrite the function as:
func loadNewAreas(completion:([Area],err?)->()){
let areaQuery = PFQuery(className: "Area")
areaQuery.where("location",nearGeoPoint:MYCURRENTLOCATION,inKilometers:50)
areaQuery.findObjectInBackgroundWithBlock(){objects,err
if objects.count == 0
{
completion([],err)
}
let areas = Area.areasFromPFObjects(objects)
completion(areas,err)
}
}

Return from nested function [duplicate]

This question already has answers here:
Run code only after asynchronous function finishes executing
(2 answers)
Closed 7 years ago.
I am trying to figure out how to return a value from inside a nested function to its parent function. The code that I am working with is:
func findObjectsInBackgroundFromLocalDataStoreIfPossible (query: PFQuery, toCallUponCompletion: () -> ()) -> [PFObject]{
var response = [PFObject]()
let queryCopy = query.copy() as! PFQuery
queryCopy.fromLocalDatastore()
queryCopy.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil{
if objects?.count == 0{
query.findObjectsInBackgroundWithBlock{
(objects2: [AnyObject]?, error: NSError?) -> Void in
if error == nil{
response = objects2 as! [PFObject]
}
}
}
else{
response = objects as! [PFObject]
}
}
toCallUponCompletion()
}
}
I need to return the response from inside the function because it can return only once the query has finished, but if I try to do this, the compiler complains that I cannot do this because the return type of the nested function is Void. Is there Swift syntax that allows you to return a value directly to the "parent" function from inside a nested function?
Thanks. Any help is greatly appreciated.
You misunderstand how async calls with completion blocks/closures work.
You can't return a result in your function because at the time the function returns, the result doesn't exist yet.
The function findObjectsInBackgroundFromLocalDataStoreIfPossible takes a closure (a block of code) as a parameter. It returns immediately, before the background network request has even been sent, much less processed.
You then forget about the background job and wait. Once the background task has completed, it calls your closure.

Resources