Swift Callback API assistance - swift

When calling getItems function, XCode auto completes to:
getItems(success: (([File]) -> ())?, fail: ((Error) -> ())?)
I just need some assistance understanding what I need to input in order to get the list of items.
I've created simple callbacks and read up on Apple's closure documentation to better understand what's going on but am still a little confused by this in particular.

You'll have to put closures as parameters like below to use this function:
getItems(success: { (files) in
// files: [File]
for file in files {
print(file)
}
}, fail: { (error) in
// error: Error
print(error)
})

Related

How do I correctly handle & complete this function that takes in an #escaping function correctly?

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

How do you escape a completionBlock in Swift to return to normal application flow?

One of the things that's throwing me in Swift is the never-ending chain of completionBlocks in my program; and I'm not sure how to get Swift to say "Ok, the completion block is now done -- come back to the main program."
In my project I am writing a simple board/card game which loads its data from a very small plist.
I wrote a fairly simple Plist loader, it loads the plist and kicks back Data via a completionBlock. It doesn't do any parsing; it doesn't care about how you want to parse it, it simply returns NSData (Data) or errors.
I have a Parser which fires the Plist loader, gets the Data and then parses it using the new Swift Codable protocol.
// A static function to try and find the plist file on bundle, load it and pass back data
static func loadBoard(completionHandler: #escaping (Board?, Error?) -> Void) {
PListFileLoader.getDataFrom(filename: "data.plist") { (data, error) in
if (error != nil) {
print ("errors found")
completionHandler(nil, error)
}
else {
guard let hasData = data else {
print ("no data found")
completionHandler(nil, error)
return
}
do {
print ("found board")
let board = try decodeBoard(from: hasData) // Call a function that will use the decoder protocol
completionHandler(board, nil)
} catch {
print ("some other board error occured")
completionHandler(nil, error)
}
}
}
}
and then returns this parsed data to the main program, or whatever called it -- for example; an XCTest
My XCTest:
func testBoardDidLoad() -> Board? { // The return bit will show an error; but its fine without the return part
BoardParsePlist.loadBoard { (board, error) in
XCTAssertNotNil(board, "Board is nil")
XCTAssertNotNil(error, error.debugDescription)
// How do I now escape this and return flow to the normal application?
// Can I wrap this in a try-catch?
}
}
From a hierarchical view it sort of looks like this.
XCTest
... Calls the parser (completionBlock ...)
.... Parser calls the PListLoader (completionHandler: ...)
Now it feels like I'm stuck in the block for the rest of the app
BoardParsePlist.loadBoard { (board, error) in
// ... rest of the app now lives here?
})
It seems I'm in a never ending loop of completionBlocks.
How do you "escape" or break out of the completion block and return flow back to the main app?
I'm not sure if I explained it correctly, but would appreciate any assistance on this.
Thank you for your time.
not sure how to get Swift to say "Ok, the completion block is now done -- come back to the main program."
There's no way and no need to say that - it happens automatically.
static func loadBoard(completionHandler: #escaping (Board?, Error?) -> Void) {
PListFileLoader.getDataFrom(filename: "data.plist") { (data, error) in
// completion handler code here
}
// "back to main"
}
In your example, execution will get "back to main" in one of two ways, depending on whether PListFileLoader.getDataFrom is running asynchronously or not.
If it's running synchronously, the execution order will be:
your app calls loadBoard
this calls getDataFrom
which in turn calls its completion handler parameter
the completion handler runs
"back to main" runs
OTOH, if getDataFrom is asynchronous (e.g. because it does a network request), the order will be:
your app calls loadBoard
this calls getDataFrom
which starts its asynchronous work and returns
"back to main" runs
at some later point in time, the work started by getDataFrom is done, and it calls the completion handler parameter
the completion handler runs
Either way, you get back to main without special effort.

Swift 5: JSON from Alamofiire

I have a class that contains some Alamofire code to get JSON from a server, convert it into a pre-defined model and then return that model as an array.
Here is the code
func GetLights(completionHandler: #escaping (DataResponse<[LightList]>) -> Void) -> Alamofire.DataRequest {
return AF.request(APIString + "/lights").responseJSON { response in
let LightListResponse = response.flatMap { json in
try JSONDecoder().decode([LightList].self, from: response.data!)
}
completionHandler(LightListResponse)
}
}
func GetLightList() {
GetLights { response in
if let lights = response.value {
print(lights)
}
}
}
I can breakpoint through to the JSONDecoder and see the json via debug but the print line at the end prints nothing, it doesn't even hit a breakpoint.
Can anyone see what I'm doing wrong? I think I'm using the completion handler correctly?
I am calling the GetLightList via a SwiftUI file like so:
func InitList() {
let requests = Requests()
requests.GetLightList()
}
You shouldn't be doing this using responseJSON, as that method has already parsed the JSON using JSONSerialization and made it available to you as part of the response. Instead, you should use responseDecodable, since you already have a Decodable type.
return AF.request(apiString + "/lights").responseDecodable(of: [LightList].self) { response in
completionHandler(response)
}
However, it's often best not to expose the DataResponse type produced by Alamofire but instead use the Result from the response in your completion handler.
Additionally, updating your styling to match Swift's recommended style will help you write consistent code. Namely, methods and variable names should start with a lowercase letter to separate them from type declarations. You can see this in your code samples where it thinks things like "APIString" are types and not variables.
Finally, it's often helpful to not overload get as a method prefix. For network calls I like using fetch when requesting a resource. e.g. fetchLights.

Does throwing a asynchronous function make it synchronous in Swift?

This is a code example taken from Perfect Swift PostgresSTORM library.
do{
//Create a user object
let obj = User()
obj.name = "someUser"
//Save it to db
try obj.save({ id in
print(2..)
obj.id = id as! Int
})
print("1..")
}catch{
print("Something went wrong.")
}
//Go to next page
print("3..")
I expected to see the print logs to be
1..
3..
2..
but, The logs looked like this.
2..
1..
3..
It's highly unlikely for "2.." to print before "1..". Is it the "try" that's making it to run as a synchronous function?
It's totally up to PostgresSTORM implementation of save. I'm not familiar with it so I can't say whether it's actually asynchronous or not but I can offer you two dummy implementations, one asynchronous and one synchronous of a method with a similar signature.
Asynchronous:
func save(callback: #escaping (Int) -> Void) throws {
OperationQueue.main.addOperation {
callback(0)
}
}
Synchronous:
func save(callback: (Int) -> Void) throws {
callback(0)
}
(Note that it doesn't throw any exception in this example just for simplicity's sake).
try is required by Swift compiler when you call a function that might throw an exception and has no impact in (a)synchronous execution of the method. In fact try is just required to ensure that when we use the method we are well aware about it possibly throwing an exception.
I might be wrong but if this is SwiftORM's implementation of save method then callback is always synchronously called.

Swift capture value with same name

In a closure can we use the same name somehow inside as well as a value that is captured by the closure.
func load(withResource resource: Resource) {
var data: A?
var error: Error?
load(resource: resource) { (result, error) in
data = result // Ok!
error = error // error!
}
print("data: \(data), error: \(error)")
}
I am thinking if there is something like using self if we were talking about stored properties but these vars are are declared in the function scope.
The easiest way would just to rename error but I was wondering if there is another way.
First, If your load method is asynchronous, error and data will always be nil when it's printed.
The direct answer to your question is that within the scope of the closure, the value of "error" is the value from the parameter and there's no way to access the error from the function.
Obviously there are a lot of options to get around this, but one clean option would be to make your information into a Tuple:
func load(withResource resource: Resource) {
var closureData: (data: A?, error: Error?)?
load(resource: resource) { (result, error) in
closureData = (result, error)
}
//print(closureData) if you don't need to format it)
print("data: \(closureData.data), error: \(closureData.error)")
}