I just started learning Swift and i'm a little confused. I'm trying to set a variable within an if statement but it's not doing so and it's not returning any errors. I'm not sure if i'm explaining this correctly but here's my code and the output:
var lol:String = "123";
func initiateconnection(){
let url = NSURL(string: "http://www.google.com/")
let session = NSURLSession.sharedSession().dataTaskWithURL(url!){
(data,response, error) in
if error == nil{
var htmldata = NSString(data: data, encoding: NSUTF8StringEncoding)
lol += "0";
}
else{
println("error in connecting");
}
}
session.resume();
println(lol);
}
Output is 123
How come it isn't 1230?
If i do my println within the if statement it works, so my statement does initiate.
(My goal is to store the html in a variable and use it in other functions, but i did the number stuff to make it easier to understand.)
The URL request you are making is async so it will happen after your println command happens, to see you can add another print statement inside the competition block (where you have lol += '0' and you will see that Xcode will print first 123 and just after 1230 as it will happen just after Xcode get a response from the URL request. I hope that answer your question
Related
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.
I am new to programming in general and have started with Swift. I have a feeling what I'm attempting to do is a bit outside of my scope, but I've come so far so here's the ask:
I am adding a tracker to a program for macOS X I've already created. The end user inputs a number and hits "Add to tracker" which then takes that number, the timestamp from the button click and writes that to the appropriate entity in Core Data. Everything works perfectly, my NSTable displays the data and I my batch delete works, but I cannot for the life of me work out the best way to take the results from the NSFetchRequest and print them to a text file.
Here is the code for my fetch request that occurs when the "print" button is hit:
#IBAction func printTracker(_ sender: Any) {
fetchRequest.propertiesToFetch = ["caseDate","caseNumber"]
fetchRequest.returnsDistinctResults = true
fetchRequest.resultType = NSFetchRequestResultType.dictionaryResultType
do {
let results = try context.fetch(fetchRequest)
let resultsDict = results as! [[String:String]]
} catch let err as NSError {
print(err.debugDescription)
}
}
After the "resultsDict" declaration is where I just can't seem to come to a workable solution for getting it to string, then to txt file.
If I add a print command to the console as is, I can see that resultsDict pulls correctly with the following format:
[["caseNumber": "12345", "caseDate": "3/22/21, 5:48:18 PM"]]
Ideally I need it in plaintext more like
"3/22/21, 5:48:18 PM : 12345"
Any advice or help on the conversion would be greatly appreciated.
A simple way if there is not a huge amount of data returned is to create a string from the fetched data and then write that string to disk
First create the string by getting the values from the dictionary and adding them in the right order into a string and joining the strings with a new line character
let output = results.reduce(into: []) { $0.append("\($1["caseDate", default: ""]) : \($1["caseNumber", default: ""])") }
.joined(separator: "\n")
Then we can write them to file, here I use the Document directory as the folder to save the file in
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let path = paths[0].appendingPathComponent("results.txt")
do {
try String(output).write(to: path, atomically: true, encoding: .utf8)
} catch {
print("Failed to write to file, error: \(error)")
}
When calling my function to fetch some locations from an API I'm getting the error "Type of expression is ambiguous without more context".
As I'm relatively new to iOS development and swift I have to ask you guys. Hope its just some simple thing I've overseen. I already searched the web, but nothing helped.
Thank you in advance!
Here is how I'm calling the function:
fetchLocation(latitude: latitude, longitude: longitude, locationsCompletionHandler: {
shopSites in
ForEach (shopSites){ }
})
And here is the function:
func fetchLocation(latitude: Double, longitude: Double, locationsCompletionHandler: #escaping ([ShopSite]) -> Void) {
let semaphore = DispatchSemaphore (value: 0)
let domain = "http://e2cadda8d178.ngrok.io/api/v1/"
let urlString = domain + "public/location/" + String(latitude) + "/" + String(longitude) + "/50"
//let url = URL(string: "e2cadda8d178.ngrok.io/api/v1/public/location/66.68994/10.249066/50")!
let url = URL(string: urlString)!
var request = URLRequest(url: url,timeoutInterval: Double.infinity)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { json, response, error in
guard let json = json else {
print(String(describing: error))
semaphore.signal()
locationsCompletionHandler([])
return
}
let str = String(decoding: json, as: UTF8.self)
print(str)
let shopSites: [ShopSite] = try! JSONDecoder().decode([ShopSite].self, from: json)
print(shopSites.count)
semaphore.signal()
locationsCompletionHandler(shopSites)
}
task.resume()
semaphore.wait()
Let me now if you need further details.
So, the debug information you get in Swift is based on the output from the compiler. Unfortunately, that means sometimes you get really terrible error messages like this when the compiler cannot figure out what you're trying to do.
I think your problem boils down to the fact that you're using ForEach which is a type of view from SwiftUI when you really want a simple for loop like:
for site in shopSites.
You could also do shopSites.forEach { }, which would be another way of iterating over that array.
Anyways, I think using a view that looks like it is named like it should be a loop where you really want some kind of actual loop construct is your problem here.
In general, when you get an error like that there's no one answer. Take a step back, try to add more specific type annotations, or rephrase what you're trying to say and usually a better error message will shake out.
I can't get rid of an error in the program using Core ML image recognize!
My code is basically debugged and works with a large number of files, but on 421 files the EXC_BAD_ACCESS error occurs (and I changed the order of processing files to eliminate the problem of broken files)
I have already translated all potentially dangerous objects into singletons to avoid leaks, debugged and tidied the sqlite database, rewritten the code using the recommended generateCGImagesAsynchronously operator to get a picture from a video file, everything is cleaned up and works without leaks, but the error still occurs
Directly the code for extracting text objects is taken from the network and slightly upgraded, maybe there is a problem with it?, help!
CreateAltModel3.shared.handler = VNImageRequestHandler(ciImage: inputImage!)
CreateAltModel3.shared.request = VNCoreMLRequest(model: CreateAltModel3.shared.visionModel!) { [weak self] req3, error in
output_handler(req3.results) }
do {try CreateAltModel3.shared.handler!.perform([CreateAltModel3.shared.request!])} catch {print ("hadler3 error - request3")}
here is the code for the output_handler function
func output_handler(_ results: [Any]?) {
guard let results = results as? [VNClassificationObservation]
else {return}
for class1 in results {
if (class1.confidence > 0.05) { // Only show greater than 5%
let fileID = filesArray[currentFileIndex]?.index
if percentRecogn <= Int(round(class1.confidence*100)) {
findKeyStr = (class1.identifier + " " + String(round(class1.confidence*100)) + "%")
resultKeyStr.0 = Int64(fr)
let comma = (resultKeyStr.1 == "") ? "" : ", "
let cutDubbing = (resultKeyStr.1 == class1.identifier) ? resultKeyStr.1 : resultKeyStr.1 + comma + class1.identifier
resultKeyStr.1 = cutDubbing
resultKeyStr.2 = Int64(fileID!)
}
}
}
}
but the error as I understand it occurs after exiting this function
on the next line of code
more code to spread does not make sense because I will only confuse you with extra information, I will only add that this code works in a loop in the global stream and outputs information to the label and ImageView in the main stream as expected, and everything seems to be debugged but I can not understand the nature of the error, perhaps something related to streams but there is not even an idea where to dig)
You cannot (or at least should not) use a single VNCoreMLRequest from multiple threads at a time.
I need help figuring out how to write repeatedly to the same, open, output file.
I am using Swift 4.2. My searches and tests have turned up only code that writes a single text string to a file and then closes the file. The next opening overwrites the last one. An example is shown below.
The problem is that I need to be able to write large numbers of records (say, 1.5 million) and perform calculations on each record just before it is written to a file. That’s not feasible when the code will only write once before closing. I'm calling this "writing line by line", much like the opposite, to "read line by line."
I tried to find an option in various Swift write statements and SO posts, but everything seems to be geared toward writing once then closing the file. I tried an open for append, but that did not work and anyway it seems inefficient to open, close, reopen-append each time I want to write to a file. I tried some C code in Swift, using open(… and freopen(… but could not get something that the compiler wouldn't complain about. Hopefully, there is a way to do this all in Swift. The following code works nicely for one write.
let file0 = “test_file.txt”
let s0 = ("This is a test line of text")
do {
try s0.write(to: NSURL(fileURLWithPath: file0) as URL, atomically: false, encoding: String.Encoding.utf8)
} catch {
print("Problem writing to file0")
}
How can I adapt this code snippet to write a string, and then another and another etc, and before closing the file when it’s all done? If not with this, is there Swift code that will do the job?
Following are the essential code components needed to write to a file, line-by-line in Swift. First is some file management code to create a file if it does not exist, then there is code to print a series of example statements, followed by code to print to the file in a loop, and finally close the file. This code worked correctly in Swift 4.2. The difference between this and the method in the question is that the write statements in this code use a method of fileHandle! and the question shows a method of a Swift string.
print("Swift_Write_to_File_Test_1")
var outFilename: NSString = "test_file.txt"
// Begin file manager segment
// Check for file presence and create it if it does not exist
let filemgr = FileManager.default
let path = filemgr.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).last?.appendingPathComponent(outFilename as String)
if !filemgr.fileExists(atPath: (path?.absoluteString)!) {
filemgr.createFile(atPath: String(outFilename), contents:Data(" ".utf8), attributes: nil)
}
// End file manager Segment
// Open outFilename for writing – this does not create a file
let fileHandle = FileHandle(forWritingAtPath: outFilename as String)
if(fileHandle == nil)
{
print("Open of outFilename forWritingAtPath: failed. \nCheck whether the file already exists. \nIt should already exist.\n");
exit(0)
}
var str: NSString = "2. Test string from NSString.\n";
var str0: String = "3. Test string from a Swift String.\n"
var str1: NSString = "4. Test string from NSString.\n";
fileHandle!.write("1. Text String in-line with code statement.\n".data(using: .utf8)!)
fileHandle!.write(String(str).data(using: .utf8)!)
fileHandle!.write(str0.data(using: .utf8)!)
fileHandle!.write(String(str1).data(using: .utf8)!)
fileHandle!.write("5. Text String in-line with code statement.\n".data(using: .utf8)!)
fileHandle!.write("6. Text in a loop follows: \n".data(using: .utf8)!)
for i in 0...5
{
//Assemble a string then write it to the file.
var s0: String = ""
s0 = String(i)
//s0.append(" ... some text here.\n") // See improvement below
s0 += " ... some text here.\n" // This is a better than .append
fileHandle!.write(s0.data(using: .utf8)!)
}
// End of file-writing segment
fileHandle!.closeFile()
This worked for me in Swift 5:
func writeFile() -> Bool
{
let outFilename: String = "test_file.txt"
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let outFilePath = documentsURL!.appendingPathComponent(outFilename).path
let fileManager = FileManager.default
// If file exists, remove it
if fileManager.fileExists(atPath: outFilePath)
{
do { try fileManager.removeItem(atPath: outFilePath) }
catch { return false }
}
// Create file and open it for writing
fileManager.createFile(atPath: outFilePath, contents:Data(" ".utf8), attributes: nil)
let fileHandle = FileHandle(forWritingAtPath: outFilePath)
if fileHandle == nil
{
return false
}
else
{
// Write data
fileHandle!.write("Test line 1\n".data(using: .utf8)!)
fileHandle!.write("Test line 2\n".data(using: .utf8)!)
fileHandle!.write("Test line 3\n".data(using: .utf8)!)
// Close file
fileHandle!.closeFile()
return true
}
}