How can i wait until download will be finished? - swift

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.

Related

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.

Method call asks for method as parameter

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.

Cannot change variable from a closure in the same class [swift 3.0]

var serviceTypeName = [String]()
override func viewDidLoad() {
super.viewDidLoad()
getServiceType()
debugPrint(serviceTypeName)
}
I try to get an array of names from web API, and store it into the array serviceTypeName. Below is my function of get service name (I used Alamorfire lib)
func getServiceType() {
Alamofire.request(...
.responseJSON { response in
switch response.result {
case .success:
if let responseValue = response.result.value {
var serviceName: [String] = []
let json = JSON(responseValue)
for (key, subJson):(String, JSON) in json["results"] {
if let name = subJson["name"].string {
serviceName.insert(name, at: Int(key)!)
}
}
self.serviceTypeName = serviceName
} ...
However, I got null array [] after this function, just like the function didn't do anything. I make sure that I got correct data from server and put into serviceName correctly every loop. But I cannot set it to the serviceTypeName. Would you please point out the mistakes?
I got null array [] after this function, just like the function didn't do anything.
That's right, the function did not do anything. More precisely, the function did not do anything yet. Checking the value after the function is premature, because the callback is executed asynchronously, when the data arrives.
You need to split your code into two parts:
The first part initiates the call, and gives it a callback that sets properties to the results received from the call
The second part processes the results, displaying the properties or doing whatever else you planned to do
Once the callback is done setting the properties, it needs to initiate the call for the second part, which makes use of these properties.
func getServiceType() { // Part 1
...
self.serviceTypeName = serviceName
dispatch_async(dispatch_get_main_queue()) {
processServiceName()
}
}
func processServiceName() { // Part 2
... // Do things with self.serviceTypeName
}
Alamofire requests are asynchronous. That means that you code in getServiceType() will be running separately from the rest of the code in your viewDidLoad() function.
At the time debugPrint(serviceTypeName) is called the getServiceType() function has not finished processing.
A way to fix that is to add a completionHandler to your getServiceType() function :
func getServiceType(completionHandler: () -> ()) {
Alamofire.request(...
.responseJSON { response in
switch response.result {
case .success:
if let responseValue = response.result.value {
var serviceName: [String] = []
let json = JSON(responseValue)
for (key, subJson):(String, JSON) in json["results"] {
if let name = subJson["name"].string {
serviceName.insert(name, at: Int(key)!)
}
}
self.serviceTypeName = serviceName
completionHandler()
} ...
Then you can call the func like that :
override func viewDidLoad() {
super.viewDidLoad()
getServiceType() {
debugPrint(serviceTypeName)
}
}
here the debug print will be called when the completion handler is triggered by your asynchronous function : at the right time. :)

How i can return value from function use Alamofire

How i can return value from function used Alamofire . i try to print outside .responseJSON the value in ArrData is not set but i try print inside it work
this code:
func getDept()->NSMutableArray
{
var ArrData:NSMutableArray = []
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET, url).responseJSON { response in
let json = JSON(response.result.value!)
let count = json.count
for var index = 0; index < count;index++
{
ArrData.addObject(json[index]["dept"].stringValue)
}
}
return ArrData
}
it i good idea to check at least README.md of the framework which you are going to use in your code
Networking in Alamofire is done asynchronously. Asynchronous
programming may be a source of frustration to programmers unfamiliar
with the concept, but there are very good reasons for doing it this
way.
Rather than blocking execution to wait for a response from the server,
a callback is specified to handle the response once it's received. The
result of a request is only available inside the scope of a response
handler. Any execution contingent on the response or data received
from the server must be done within a handler.
Try to use a handler like this and a callback:
func getopt(callback:(array: [String]) -> void ){
func completion(request: NSURLRequest?, response:NSHTTPURLResponse?,result:Result<AnyObject>){
if let rdata = result.value{
let data = JSON(rdata)
print(data)
let myArray = [String]
let objects = data.array
for object in objects{
myArray.append(object)
}
callback(myArray)
}
}
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET,url),
encoding: .JSON).responseJSON(completionHandler: completion)
}
You pass the array to your callback So when you call getopt where you call it you can print the array. Some like this:
func something (){
getopt(callback)
}
func callback(array:[String]){
print array[0]
}