Refer to variable from do block in catch - swift

I need to access a variable inside a do statement. Will it behave like the if-else statement in the sense that you con't use variables outside of the if statement?
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
do {
//let url = URL?("https://www.hackingwithswift.com")
let TECIONEXContent = try String(contentsOf: URL("https://www.hackingwithswift.com"))
} catch { print("error")}
//I need to access TECIONEXContent variable outside the do statement
// Error: Use of unresolved identifier 'TECIONEXContent'
var TECGrid = TECIONEXContent.components(separatedBy: "\n")
}
}
The error is on the last line, 'unresolved identifier'.

Will it behave like the if-else statement in the sense that you con't use variables outside of the if statement?
Yes. But just like an if-else statement, you can define the variable before the do-catch:
E.g. in an if-else statement:
let foo: String
if bar > 1 {
foo = "bigger than one"
} else {
foo = "one or smaller"
}
Or, in your case:
let url = URL(string: "https://www.hackingwithswift.com")!
let contents: String
do {
contents = try String(contentsOf: url)
} catch {
print(error)
return
}
let grid = contents.components(separatedBy: "\n")
Or, you aren’t really doing anything with the error message, you can eliminate the do-catch altogether:
guard let contents = try? String(contentsOf: url) else {
print("error")
return
}
let grid = contents.components(separatedBy: "\n")
Frankly, all of that having been said, using String(contentsOf:) is probably not the best pattern, anyway, because that performs a synchronous network request, which risks having the OS “watchdog” process kill your app unceremoniously if the main thread is blocked; and even if that doesn’t happen, it’s not a good user experience to freeze the app while the network request is in progress. Usually we’d use URLSession:
let url = URL(string: "https://www.hackingwithswift.com")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let httpResponse = response as? HTTPURLResponse,
let string = String(data: data, encoding: .utf8) else {
print(error ?? "Unknown error")
return
}
guard 200 ..< 300 ~= httpResponse.statusCode else {
print("Expected 2xx response, but got \(httpResponse.statusCode)")
return
}
let grid = string.components(separatedBy: "\n")
DispatchQueue.main.async {
// use `grid` here
}
}.resume()
Unrelated, but:
The convention is to start variable names with lowercase letters.
You implemented loadView. It’s rare that we do that, and instead we implement viewDidLoad, making sure to call super.viewDidLoad(), too.
If you’re doing this in a playground, you’d obviously also set needsIndefiniteExecution, if you haven’t already.
Thus:
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
performRequest()
}
func performRequest() {
let url = URL(string: "https://www.hackingwithswift.com")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let httpResponse = response as? HTTPURLResponse,
let string = String(data: data, encoding: .utf8) else {
print(error ?? "Unknown error")
return
}
guard 200 ..< 300 ~= httpResponse.statusCode else {
print("Expected 2xx response, but got \(httpResponse.statusCode)")
return
}
let grid = string.components(separatedBy: "\n")
DispatchQueue.main.async {
print(grid)
// use `grid` here
}
}.resume()
}
}
PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true

In your code are four(!) very bad practices.
Never load data synchronously with API like String(contentsOf from a remote URL. Use asynchronous API like URLSession.
Never print a meaningless literal string in a catch block. Print the error instance.
According to the naming convention variable names should be lowerCamelCased.
In a do - catch block put always all good code in the do scope. This solves your issue.
do {
let tecionexContent = try String(contentsOf: URL("https://www.hackingwithswift.com")!)
let tecGrid = tecionexContent.components(separatedBy: "\n")
} catch { print(error) }
recommended
URLSession.shared.dataTask(with: URL("https://www.hackingwithswift.com")!) { data, _ , error in
if let error = error { print(error); return }
let tecionexContent = String(data: data!, encoding: .utf8)!
let tecGrid = tecionexContent.components(separatedBy: "\n")
}.resume()

The Issue
The problem with your code is that you are defining the variable inside a block/closure, which is a local scope. Your variable should be at a scope where both the blocks are able to see it. This means that in the catch block, the variable does not exist. Specifically you had attempted to reference the string variable you called TECIONEXContent from outside of the closure in which it was instantiated.
On some general style points: please stick with the swift naming convention for variables (i.e. camel case) whereas your classes etc should be capitalized. (For the purposes of running the below code in the playground I've used an arbitrary function name, but you could employ it from your lifecycle methods).
Basic Playground Demo Code
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private var tecGrid: [String]? {
didSet {
// Update some UI Here (insuring your on the main thread)
print(self.tecGrid)
}
}
func test() {
do {
var texionicContent = try String(contentsOf:URL(string: "https://www.hackingwithswift.com")!)
tecGrid = texionicContent.components(separatedBy: "\n")
}
catch let error {
print("Catch the error")
}
}
}
let play = MyViewController(nibName: nil, bundle: nil)
play.test()

Related

Why my DateTask code block does not work?

I create a request to the server, and in the end I expect to receive data, which I then transform into a model using a function, for this I created a session
func fetchNewsData(forCoutry country: String, category: String, complition: #escaping (NewsDataModel) -> ()) {
let urlString = "some url string"
guard let url = URL(string: urlString) else { return }
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
print ("ERROR: \(error)")
guard let data = data else { return }
guard let newsData = self.parseJSON(withData: data) else { return }
complition(newsData)
}
task.resume()
}
but the following code just doesn't work
print ("ERROR: \(error)")
guard let data = data else { return }
guard let newsData = self.parseJSON(withData: data) else { return }
complition(newsData)
I used breakpoints to find out until what point everything is going well, and I realized that this particular block of code is not working.
when I set a breakpoint between the let session and the let task, the code stopped there, but when I set my code to an print(error), this breakpoint did not work
I used the function fetchNewsData in viewDidLoad and I want to work to fill the array with elements that I expect to receive from the data that will come on this request, but my array does not receive any elements, and it remains empty, because of this my application does not work
why part of the code doesn't work, and how can I get the data I need from it?
The problem turned out to be a poor understanding of closures
I was not calling my method correctly to get the data. Having figured it out, I realized that the problem is precisely in a different approach when calling this method

Asynchronous thread in Swift - How to handle?

I am trying to recover a data set from a URL (after parsing a JSON through the parseJSON function which works correctly - I'm not attaching it in the snippet below).
The outcome returns nil - I believe it's because the closure in retrieveData function is processed asynchronously. I can't manage to have the outcome saved into targetData.
Thanks in advance for your help.
class MyClass {
var targetData:Download?
func triggerEvaluation() {
retrieveData(url: "myurl.com") { downloadedData in
self.targetData = downloadedData
}
print(targetData) // <---- Here is where I get "nil"!
}
func retrieveData(url: String, completion: #escaping (Download) -> ()) {
let myURL = URL(url)!
let mySession = URLSession(configuration: .default)
let task = mySession.dataTask(with: myURL) { [self] (data, response, error) in
if error == nil {
if let fetchedData = data {
let safeData = parseJSON(data: fetchedData)
completion(safeData)
}
} else {
//
}
}
task.resume()
}
}
Yes, it’s nil because retrieveData runs asynchronously, i.e. the data hasn’t been retrieved by the time you hit the print statement. Move the print statement (and, presumably, all of the updating of your UI) inside the closure, right where you set self.targetData).
E.g.
func retrieveData(from urlString: String, completion: #escaping (Result<Download, Error>) -> Void) {
let url = URL(urlString)!
let mySession = URLSession.shared
let task = mySession.dataTask(with: url) { [self] data, response, error in
guard
let responseData = data,
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkError.unknown(response, data))
}
return
}
let safeData = parseJSON(data: responseData)
DispatchQueue.main.async {
completion(.success(safeData))
}
}
task.resume()
}
Where
enum NetworkError: Error {
case unknown(URLResponse?, Data?)
}
Then the caller would:
func triggerEvaluation() {
retrieveData(from: "https://myurl.com") { result in
switch result {
case .failure(let error):
print(error)
// handle error here
case .success(let download):
self.targetData = download
// update the UI here
print(download)
}
}
// but not here
}
A few unrelated observations:
You don't want to create a new URLSession for every request. Create only one and use it for all requests, or just use shared like I did above.
Make sure every path of execution in retrieveData calls the closure. It might not be critical yet, but when we write asynchronous code, we always want to make sure that we call the closure.
To detect errors, I'd suggest the Result pattern, shown above, where it is .success or .failure, but either way you know the closure will be called.
Make sure that model updates and UI updates happen on the main queue. Often, we would have retrieveData dispatch the calling of the closure to the main queue, that way the caller is not encumbered with that. (E.g. this is what libraries like Alamofire do.)

How to use a JSON file from an Onine server like turbo360?

I am using a do catch scenario to try a JSONDecoder(), the only >problem is that I keep catching the error, but when I review my code I >can't see the error, maybe I need another set of eyes to help me out >of this one!
I placed my JSON file in a storage folder in turbo360, I've also tried gitHub, but neither is working, I believe.
import UIKit
class ViewController: UIViewController {
final let url = URL(string: "https://storage.turbo360.co/portfolio-website-hxoc6m/actors")
override func viewDidLoad() {
super.viewDidLoad()
downloadJson()
}
func downloadJson() {
guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong")
return
}
print("downloaded")
print(downloadURL)
do
{
let decoder = JSONDecoder()
let actors = try decoder.decode(Actors.self, from: data)
print(actors)
} catch {
print("Something wrong after downloaded")
}
}.resume()
}
}
I supposed to get: JSONDonloadingSwift4.Actors
as confirmation that my JSON file has been accessed and decoded
Your JSON is invalid. You are missing an opening " on the last image URL. Fix that and as long as your Actor definition matches you should be good. jsonlint is very useful for checking JSON structure.

MACOS App closure never executed

I've created a macOS console app in swift, but the code is never executed, =I have to use Semaphore but is there another way to do this ?
my purpose is to create a method returning a json file
class test{
func gizlo(){
let config = URLSessionConfiguration.default // Session Configuration
let session = URLSession(configuration: config) // Load configuration into Session
let url = URL(string: "https://itunes.apple.com/fr/rss/topmovies/limit=25/json")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error != nil {
print(error!.localizedDescription)
} else {
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
{
print(json)
}
} catch {
print("error in JSONSerialization")
}
}
})
task.resume()
}
}
let tr=test()
tr.gizlo()
Thanks
To avoid Semaphores you can use simple readLine() that will wait for input from the keyboard. Yes it is not obvious but it is woking because it prevent terminal app from exit.
Just add in the and of the file:
_ = readLine()
As Oleg points out, putting readLine() at the end of the top-level code will prevent the program for exiting until you hit Enter in the terminal or wherever FileHandle.standardInput is pointing. That's probably fine for just testing the code quickly in the debugger or in a Playground. An infinite loop would also work, though you'd have to actually terminate it in the debugger or with kill from the command line.
The real issue is why you don't want to use a semaphore. Since they're not difficult to use, I'm going to hazard a guess that it's just because you don't want to pollute your asynchronous data task completion handler with a semaphore when you probably only need it to wait for the data for testing purposes.
Assuming my guess is correct, the real issue isn't actually using a semaphore, it's where you think you need to put them. As David Wheeler once said, "Any problem can be solved by adding a layer of indirection."
You don't want the semaphore explicitly in the completion handler you pass to dataTask. So one solution would be to make gizlo accept a completion handler of its own, and then create a method that calls gizlo with a closure that handles the semaphore. That way you can decouple the two and even add some flexibility for other uses. I've modified your code to do that:
import Foundation
import Dispatch // <-- Added - using DispatchSemaphore
class test{
func gizlo(_ completion: ((Result<[String: Any]?, Error>) -> Void)? = nil) { // <-- Added externally provided completion handler
let config = URLSessionConfiguration.default // Session Configuration
let session = URLSession(configuration: config) // Load configuration into Session
let url = URL(string: "https://itunes.apple.com/fr/rss/topmovies/limit=25/json")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
let result: Result<[String: Any]?, Error>
if let responseError = error { // <-- Changed to optional binding
print(responseError.localizedDescription)
result = .failure(responseError) // <-- Added this
} else {
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
{
print(json)
result = .success(json) // <-- Added this
}
else { // <-- Added this else block
result = .success(nil)
}
} catch {
print("error in JSONSerialization")
result = .failure(error) // <-- Added this
}
}
completion?(result) // <-- Added this call
})
task.resume()
}
func blockingGizlo() throws -> [String: Any]? // <-- Added this method
{
let sem = DispatchSemaphore(value: 1)
sem.wait()
var result: Result<[String: Any]?, Error>? = nil
gizlo {
result = $0
sem.signal()
}
sem.wait() // This wait will block until the closure calls signal
sem.signal() // Release the second wait.
switch result
{
case .success(let json) : return json
case .failure(let error) : throw error
case .none: fatalError("Unreachable")
}
}
}
let tr=test()
do {
let json = try tr.blockingGizlo()
print("\(json?.description ?? "nil")")
}
catch { print("Error: \(error.localizedDescription)") }

How To Update A Label In Swift 3 With JSON Data Inside Of A Function?

For some reason whenever I try to update my label with the current temperature using self.infoLabel.text = String(temp!) inside of the DispatchQueue code block, I get the following fatal error message:
unexpectedly found nil while unwrapping an Optional value.
I'd appreciate if someone could help me figure out why the code below isn't working. Thanks.
func getCurrentTemp(city: String){
let weatherRequestURL = URL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
// The data task retrieves the data.
URLSession.shared.dataTask(with: weatherRequestURL) { (data, response, error) in
if let error = error {
// Case 1: Error
print("Error:\n\(error)")
}
else {
//print("Raw data:\n\(data!)\n")
//let dataString = String(data: data!, encoding: String.Encoding.utf8)
//print("Human-readable data:\n\(dataString!)")
do {
// Try to convert that data into a Swift dictionary
let weather = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String:AnyObject]
if let main = weather["main"] as? [String: Any] {
let temp = main["temp"] as? Double
print("temp\(temp!)")
DispatchQueue.main.sync(execute: {
self.infoLabel.text = String(temp!)
})
//return temp as? String
//let temp_max = main["temp_max"] as? Double
//print("temp\(temp_max!)")
//let temp_min = main["temp_min"] as? Double
//print("temp\(temp_min!)")
}
}
catch let jsonError as NSError {
// An error occurred while trying to convert the data into a Swift dictionary.
print("JSON error description: \(jsonError.description)")
}
}
}
.resume()
}
There are two possibilities here: 1) either temp is nil (and it shouldn't be because you already force unwrap it in the print statement above) 2) or infoLabel is nil which happens if you broke your outlet connection.
Its easy to check; make a breakpoint above your assignment and in the debug console you can type:
po self.infoLabel
to see if its nil. For good measure you an also check temp.
You can also add a print statement to check self.infoLabel or an assert.
Alright, so I found a makeshift solution to this issue (See Below). Rather than placing the code inside of the function I made, I placed it in the viewDidLoad() function. For whatever reason, self.infoLabel? would be nil anywhere inside of the function I made.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("Sucessful launched weather page.")
let weatherRequestURL = URL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
// The data task retrieves the data.
URLSession.shared.dataTask(with: weatherRequestURL) { (data, response, error) in
if let error = error {
// Case 1: Error
print("Error:\n\(error)")
}
else {
//print("Raw data:\n\(data!)\n")
//let dataString = String(data: data!, encoding: String.Encoding.utf8)
//print("Human-readable data:\n\(dataString!)")
do {
// Try to convert that data into a Swift dictionary
let weather = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String:AnyObject]
if let main = weather["main"] as? [String: Any] {
let temp = main["temp"] as? Double
print("temp\(temp!)")
var tempInFarenheit = ((9/5)*((temp!)-273) + 32).rounded()
DispatchQueue.main.sync(execute: {
self.infoLabel.text = "\(tempInFarenheit) + °"
})
}
}
catch let jsonError as NSError {
// An error occurred while trying to convert the data into a Swift dictionary.
print("JSON error description: \(jsonError.description)")
}
}
}
.resume()
}
Although this isn't the most effective way of doing things, hopefully it can help others who are having the same problem. If I find a more effective way of doing this, I'll be sure to edit this post and include it.