Method call asks for method as parameter - swift

I'm pretty new to swift so this might be a really simple question, but I am trying to create a method that returns a list upon completion but when I try to call the method, it says I am missing the escaping parameter which I do not know how to satisfy.
Here is the method:
func fillFromFile(completionBlock: #escaping ([Asset_Content]) -> ()) {
let url = "URL STRING"
LoadJSONFile(from: url) { (result) in
// The code inside this block would be called when LoadJSONFile is completed. this could happen very quickly, or could take a long time
//.map is an easier way to transform/iterate over an array
var newContentArray = [Asset_Content]()
for json in result{
let category = json["BIGCATEGORY"] as? String
let diagnosis = json["DIAGNOSIS"] as? String
let perspective = json["PERSPECTIVE"] as? String
let name = json["NAME"] as? String
let title = json["Title"] as? String
let UnparsedTags = json["TAGS"] as? String
let filename = json["FILENAME"] as? String
let tagArray = UnparsedTags?.characters.split(separator: ",")
for tag in tagArray!{
if(!self.ListOfTags.contains(String(tag))){
self.ListOfTags.append(String(tag))
}
}
let asset = Asset_Content(category!, diagnosis!, perspective!, name!, title!, filename!)
// This is a return to the map closure. We are still in the LoadJSONFile completion block
newContentArray.append(asset)
}
print("return count ", newContentArray.count)
// This is the point at which the passed completion block is called.
completionBlock(newContentArray)
}
}
here is the method call:
self.ListOfFiles = fillFromFile()
and the error is "Missing argument for parameter 'completionblock' in call"

The way you expect the response of a method with completionBlock is like this:
fillFromFile { (response) in
self.ListOfFiles = response
}
Like this you are setting your ´ListOfFiles´ variable, with the new variable that comes in the method.
In the return of your function you should have a DispatchQueue
DispatchQueue.main.async {
completionBlock(newContentArray)
}

Notice that the fillFromFile function doesn't return anything. It's an asynchronous function. This means that it does its work independently of the main control flow of the thread it was called from. It'll return (nothing) almost immediately, and perform its work some unknown time thereafter.
To obtain the result of this function, you're expected to given a completion handler. This is a closure that will be called by the code when it eventually completes its work. As a parameter to this closure, it will pass in the result of the work (an Array<Asset_Content>).
Here's simple example of how to satisfy this method signature:
fillFromFile { (response) in
print(response)
}
I suggest you read the language guide, especially its section on closures.

Related

Using data from a URLSession and understanding Completion Handlers in Swift 5 [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 9 months ago.
This post was edited and submitted for review 6 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have code where I am creating a URLSession and trying to return the data obtained from this so it can be used outside of the function that the URLSession is in. My code looks like:
var nameStr = ""
var ingStr = ""
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
let str = String(data: data, encoding: .utf8)!
let str2 = convertStringToDictionary(text: str)
nameStr = "Name: \(str2!["title"] as! NSString)"
ingStr = "Ingredients: \((str2!["ingredientList"] as! NSString).components(separatedBy: ", "))"
print("First ", nameStr)
print(ingStr)
}
task.resume()
print("Second ", nameStr)
print(ingStr)
The first print statement works as expected, but the second only prints "Second ". I looked at Swift return data from URLSession and although I realize it does have a viable solution, I do not understand the whole completion block part and have a feeling others might be on the same boat. Any suggestions?
You said:
The first print statement works as expected, but the second only prints "Second ". I looked at Swift return data from URLSession …
The accepted answer to that question is the correct answer here, too.
What you’ve labeled as “first” and “second” in your code snippet are actually backwards. If you look carefully, you will see that “second” print statement (with no results yet) is actually happening before you see your “first” print statement! More to the point, your execution path will reach that “second” print statement before you’ve received and parsed the response from the network call.
That is the root of the issue, namely that the code inside the closure passed to dataTask method happens “asynchronously”, i.e. later. You can’t print your strings right after the resume statement (your “second” print statement) because those strings have not been set yet! Likewise, obviously cannot return these strings, either, because, again, they are not populated until well after you’ve returned from this function. This is all because the dataTask closure has not run yet! The completion handler closure, described in that other answer, is the solution.
Consider this code snippet:
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
guard
let data = data,
let string = String(data: data, encoding: .utf8),
let dictionary = convertStringToDictionary(text: string),
let title = dictionary["title"] as? String,
let ingredientsString = dictionary["ingredientList"] as? String
else { return }
let ingredients = ingredientsString.components(separatedBy: ", ")
// you’ve got `title` and `ingredients` here …
}
task.resume()
// … but not here, because the above runs asynchronously
This is basically your code, refactored to eliminate the ! forced unwrapping operators and the NSString references. But that’s not the relevant piece of the story. It is the those two comments, where I’m showing where you have results (i.e., in the closure) and where you do not (i.e., immediately after the resume). If you’re not familiar with asynchronous programming, this might look deeply confusing. But it is the important lesson, to understand exactly what “asynchronous” means.
… but I do not understand the whole completion block part. Any suggestions?
Yep, the answer to your question rests in understanding this “completion handler” pattern. The dataTask method has a completion handler closure parameter, a pattern that you have to repeat in your own method.
So, going back to my code snippet above, how precisely do you “return” something that is retrieved later? The answer: You technically do not return anything. Instead, add a completion handler closure parameter to your method and call that method with a Result, which is either some model object upon success (e.g., perhaps a Recipe object), or an Error upon failure. And to keep it simple for the caller, we will call that completion handler on the main queue.
Thus, perhaps:
func fetchRecipe(with url: URL, completion: #escaping (Result<Recipe, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
guard
let data = data,
let string = String(data: data, encoding: .utf8),
let dictionary = convertStringToDictionary(text: string),
let title = dictionary["title"] as? String,
let ingredientsString = dictionary["ingredientList"] as? String
else {
DispatchQueue.main.async {
completion(.failure(error ?? URLError(.badServerResponse)))
}
return
}
let ingredients = ingredientsString.components(separatedBy: ", ")
let recipe = Recipe(title: title, ingredients: ingredients)
DispatchQueue.main.async {
completion(.success(recipe))
}
}
task.resume()
}
This is basically the same as my above code snippet, except that I:
Wrapped it in a fetchRecipe method;
I don’t return anything;
I gave that method a completion parameter, which is a completion handler closure;
I call this completion closure when the network request completes, passing back either a .failure() or a .success(), obviously depending upon whether it failed or succeeded.
For the sake of completeness, this is the Recipe model object that I used, to wrap everything returned from the server in a nice, simple object:
struct Recipe {
let title: String
let ingredients: [String]
}
And you would call it like so:
fetchRecipe(with: url) { result in
// use `result` here …
switch result {
case .failure(let error):
print(error)
case .success(let recipe):
print(recipe)
// update your model and UI using `result` here …
}
}
// … but not here, because, again, the above runs asynchronously
I have to say, it feels deeply wrong to convert the Data to a String and then to a dictionary and then use dictionary key string literals. Usually, for example, we’d have a server that returned JSON and we would just parse the Data directly with JSONDecoder. But you did not share this convertStringToDictionary, so I wasn’t able to offer any meaningful advice on that score. But that’s something for you to tackle once you have your immediate problem behind you.

swift function doesnt return a value [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed last year.
I'm new at Swift and that's why i need your help. So I have a function which should send request and return a value
func getAnswer() -> String? {
var answer: String?
guard let url = URL(string: "https://8ball.delegator.com/magic/JSON/_") else { return nil }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
return
}
guard let response = response as? HTTPURLResponse else { return }
guard response.statusCode == 200 else { return }
do {
let model = try JSONDecoder().decode(Answer.self, from: data)
DispatchQueue.main.async {
answer = model.magic.answer
}
} catch let error {
fatalError(error.localizedDescription)
}
}.resume()
return answer
}
but it always returns nil.
I suppose problem is here
DispatchQueue.main.async {
answer = model.magic.answer
}
How can I fix it?
In order to know what is happening here, you need to learn about #escaping functions in swift, here is some link1 together with taking function as another functions parameter link2 written in part "Function Types as Parameter Types" , closures in Swift link3 and
Here is what is happening simplified and explained step by step :
you call getAnswer()
variable answer gets initialized with value nil by declaring answer: String?
URLSession.shared.dataTask is called and it is taking as an argument another function - closure (Data?, URLResponse?, Error?) -> Void . Also URLSession.shared.dataTask is executed on different thread and is not returning yet, but will return right after it receives response from server, which can take any time (but usually milliseconds) and will basically happen after your getAnswer() function is returning value.
your getAnswer() immediately returns value of answer which is currently nil
if you get any data from server, or server could not be reached, your URLSession.shared.dataTask function executes your code in closure. This is the code it will execute:
guard let data = data, error == nil else {
return
}
guard let response = response as? HTTPURLResponse else { return }
guard response.statusCode == 200 else { return }
do {
let model = try JSONDecoder().decode(Answer.self, from: data)
DispatchQueue.main.async {
answer = model.magic.answer
}
} catch let error {
fatalError(error.localizedDescription)
}
Your problem lies in how swift executes closures. When you call
URLSession.shared.dataTask(with: url) {
// Closure code here
}
return answer
Your "Closure code here" doesn't get called until the endpoint "https://8ball.delegator.com/magic/JSON/_" actually gives a response. However, you've promised swift that your function will return an optional string immediately after the serial code of your function has completed. For this reason, by the time your "Closure code here" has run, and your "answer" variable has been updated with the correct value, your function is long gone, and has already returned a value (which in this case is whatever you've set it to at the beginning - nil).
You can fix this issue in one of two ways.
Swift's new concurrency system
By defining your own closure.
Swift's new concurrency system
You can define your function as async, meaning that the function won't have to return a value in serial, as follows.
enum GetAnswerError: Error {
case invalidURL
}
func getAnswer() async throws -> String {
var answer: String?
guard let url = URL(string: "https://8ball.delegator.com/magic/JSON/_") else {
throw GetAnswerError.invalidURL
}
// Your function will suspend here and probably be moved to a different thread. It will resume once a response has been received from the endpoint.
let (data, _) = try await URLSession.shared.dataTask(with: url)
let parsedData = try JSONDecoder().decode(Answer.self, from: data)
return parsedData.magic.answer
}
When you call this function, you'll have to do so from an environment which swift can suspend. This means you'll call the function from either another async function like so
func anotherFunction() async throws -> Bool {
let answer = try await getAnswer()
// Run some code here
return answer == "YES" // Return some useful value
}
or from a Task object like so
Task {
// Note that because the function getAnswer() can throw errors, you'll have to handle them when you call the function. In this case, I'm handling them by using try?, which will simply set answer to nil if an error is thrown.
let answer = try? await getAnswer()
}
Note that when you call code in a task, you must be using the return value's from within the scope of the task. If you try to do something like this
func getAnswerTheSecond() -> String? {
var answer: String? = nil
Task {
let receivedAnswer = try? await getAnswer()
answer = receivedAnswer
}
return answer
}
You'll just end up back where you started, where swift immediately returns the nil value because your code is ran in serial. To fix this, run the relevant code on the "answer" from wherever it is needed within the task. If you are using the "answer" to update a SwiftUI view that might look like this.
struct ContentView: View {
#State var answer: String = ""
// This is the function that I've written earlier
func getAnswer() async throws -> String {
// Make URL Request
// Return the value
}
var body: some View {
Text(self.answer)
.onAppear{
Task{
let result = try? await self.getAnswer()
self.answer = result
}
}
}
}
Defining your own closure
You can define your own closure to handle the URL response; however, because of swift's new concurrency framework, this is probably not the right way to go.
If you'd like to go this way, do a google search for "Swift closures", and you'll find what you need.

Can't get data returned from dataTask()

For one week I have been trying to get a string returned from dataTask().
I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.
So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:
func requestOGD(code gtin: String, completion: #escaping (_ result: String) -> String) {
// MARK: Properties
var answerList: [String.SubSequence] = []
var answerDic: [String:String] = [:]
var product_name = String()
var producer = String()
// Set up the URL request
let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")
guard let url = URL(string: ogdAPI) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result, which is String. It willbecome split and placed in a dictionary
do {
let answer = (String(decoding: responseData, as: UTF8.self))
answerList = answer.split(separator: "\n")
for entry in answerList {
let entry1 = entry.split(separator: "=")
if entry1.count > 1 {
let foo = String(entry1[0])
let bar = String(entry1[1])
answerDic[foo] = "\(bar)"
}
}
if answerDic["error"] == "0" {
product_name = answerDic["detailname"]!
producer = answerDic["vendor"]!
completion(product_name)
} else {
print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
return
}
}
}
task.resume()
Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:
// Configure the cell...
var foo:String = ""
requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
print(result)
foo = result
return result
}
print("Foo:", foo)
cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""
return cell
}
So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.
You used a completion handler in your call to requestOGD:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
// result comes back here
}
But then you tried to capture and return that result:
foo = result
return result
So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.
To put it in simple terms, this is the order of operations:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
foo = result // 2
}
print("Foo:", foo) // 1
You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.
In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.

Result of call is unused

Right below the second comment, I receive an error of "Result of call to 'taskForDeleteMethod' is unused. Why is this when I use the results and error in the closure following the call?
func deleteSession(_ completionHandlerForDeleteSession: #escaping (_ success: Bool, _ error: NSError?) -> Void) {
/* 1. Specify parameters, method (if has {key}), and HTTP body (if POST) */
// There are none...
/* 2. Make the request */
taskForDELETEMethod { (results, error) in
/* 3. Send the desired value(s) to completion handler */
if let error = error {
print("Post error: \(error)")
completionHandlerForDeleteSession(false, error)
} else {
guard let session = results![JSONKeys.session] as? [String: AnyObject] else {
print("No key '\(JSONKeys.session)' in \(results)")
return
}
if let id = session[JSONKeys.id] as? String {
print("logout id: \(id)")
completionHandlerForDeleteSession(true, nil)
}
}
}
}
In earlier swift versions, you need not bother about the return value of a method. You may store it in any variable snd use it later or you may ignore it completely. Neither it gave any error nor a warning.
But in swift 3.0 you need to specify whether you want to ignore the returned value or use it.
1. If you want to use the returned value, you can create a variable/constant and store the value in it, i.e
let value = taskForDELETEMethod {
// Your code goes here
}
2. If you want to ignore the returned value, you can use _ ,i.e
let _ = taskForDELETEMethod {
// Your code goes here
}
You are confusing the results variable, which is, indeed, used inside the closure, and the result of the taskForDELETEMethod call itself, which is NSURLSessionDataTask object.
From the examples of using taskForDELETEMethod that I was able to find online it looks like it is perfectly OK to ignore the return value, so you can avoid this warning by assigning the result to _ variable, i.e.
let _ = taskForDELETEMethod {
... // The rest of your code goes here
}

How can i wait until download will be finished?

My idea is easy:
1. I have a ViewController with button
2. After click button i want get data from my site
3. I want put indicator info about data are downloading
4. After finished show message OK or NOT OK
I have a class CCCLass
class CCClass {
let CCClass BaseURL: NSURL?
var seed: String?
init() {
CCClassBaseURL = NSURL(string: "mysiteblabla")
}
func getData() {
if let CCClassURL = NSURL(string: "", relativeToURL: CCClassBaseURL ) {
let networkOperation = NetworkOperation(url: CCClassURL)
networkOperation.downloadJSONFromURL {
(let JSONDictionary) in
print(JSONDictionary?["seed"])
self.seed = JSONDictionary?["seed"] as? String
}
} else {
print("Cold not construct a valid URL")
}
}
}
On click button i have
let ccClass = CCClass()
ccClass.getData() {
(let test) in
dispatch_async(dispatch_get_main_queue()) {
// go to something on the main thread
print(ccClass.seed)
}
}
And i have error : Cannot invoke 'getData' with an argument list of type '((_) -> _)'
And i am not sure should I in function getData create Array and return to main thread ? after data will be download i want use this to next http query.
You're calling getData with a trailing closure, but the definition of getData doesn't take a closure parameter (or call it). What you have will work if you change the method to take the closure and use it (probably to pass the download result back).
You have to declare a closure parameter in your method if you want to call it with a trailing closure:
func getData( completion:()->() ) {
if let CCClassURL = NSURL(string: "", relativeToURL: CCClassBaseURL ) {
let networkOperation = NetworkOperation(url: CCClassURL)
networkOperation.downloadJSONFromURL {
(let JSONDictionary) in
print(JSONDictionary?["seed"])
self.seed = JSONDictionary?["seed"] as? String
completion()
}
} else {
print("Cold not construct a valid URL")
completion()
}
}
A good rule of thumb to follow is that any method that takes some kind of "completion" closure should always call that closure even if the operation fails. So make sure you call completion() in your else block where URL construction failed, or if a network error occurred, or if a JSON parsing error occurred.