Say I have a function called:
func myFunction() throws -> Bool {
// ...
return true
}
Is there a syntax I am unaware of to use it like so:
if aFlag && try myFunction() {
// do this
}
When I compile this, it fails of course because it says a "try cannot appear to the right of a non-assignment operation". Is there a way to make this work or will I have to go with my current solution of:
if aFlag {
let result = try myFunction()
if result {
// do this
}
}
You use try? to handle an error by converting it to an optional value, and combine with the nil coalescing operator to supply e.g. false as concrete value in case the call throws (-> treated as nil)
func myFunction() throws -> Bool {
return true
}
let aFlag = true
if aFlag, (try? myFunction()) ?? false {
print("Function call succeeded and returned 'true'!")
}
/* or ...
if aFlag && (try? myFunction()) ?? false { ... } */
/* or, optionally bind the return value and use as predicate if non-nil ...
if aFlag, let anotherFlag = (try? myFunction()), anotherFlag { ... } */
This will, however, supress the error thrown, which makes it kind of questionable for myFunction() to be a throwing one in the first place. Perhaps you rather want a function that simply returns nil in the cases you treat as erroneous in your throwing function?
In Swift 3, you can use , rather than &&, e.g.
if aFlag, try myFunction() {
print("OK")
}
Related
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.
With return it is possible to exit the scope of the current function but is it also possible to exit the scope of the outer function that is calling the inner function?
func innerFunction() {
guard 1 == 2 else {
// exit scope of innerFunction and outerFunction
}
}
func outerFunction() {
innerFunction()
print("should be unreachable")
}
There could be one approach using a return value of the inner function that we can check for:
func innerFunction() -> Bool {
guard 1 == 2 else {
return false
}
return true
}
func outerFunction() {
guard innerFunction() else {
return
}
print("should be unreachable")
}
The problem with this approach is that it can clutter your code pretty quickly if the functions become more complicated and you have to use them over and over again.
Consider applying this approach with XCTest. It would look like this:
func testFoobar() {
guard let unwrappedObject = helperFunction() else {
XCTFail("A failure message that can change with each helperFunction call")
return
}
print("should be unreachable when helperFunction already failed")
}
I'd like to have something similar to this:
func testFoobar() {
let unwrappedObject = helperFunction()
print("should be unreachable when helperFunction already failed")
}
This is basically what Swift's error handling does:
func outer() throws
{
try inner()
print("Unreachable")
}
struct DoNotContinue : Error {}
func inner() throws
{
throw DoNotContinue()
}
do { try outer() }
catch _ { print("Skipped the unreachable") }
Note, of course, that the caller still has control over this: it could catch the thrown error itself instead of just exiting.
problem with this approach is that it can clutter your code
There's a much more serious problem with allowing callees to directly exit their callers, and that is that the flow of control very quickly becomes incomprehensible. Imagine that you have a couple of layers of this. Reading the top-level function, you no longer have any clear idea what can happen. You must yourself recurse into every callee's callee to make sure that the original code will continue on its course.
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
}
So on a button press I'm creating splitLat: [Double] from a throwing function called splitLatitude that takes currentLocation: CLLocationCoordinate2D?. I then want to use splitLat as a Label (its going to be used for other things as well but this serves the example)
#IBAction func ButtonPress() {
let splitLat = try self.splitLatitude(self.currentLocation)
LatSplitLabel.text = "\(splitLat)"
}
this gets a error "Errors thrown from here are not handled"
I resolve this by putting it in a do catch block
do{
let splitLat = try self.splitLatitude(self.currentLocation)
} catch {
print("error") //Example - Fix
}
but the when i try to set the label later on splitLat is an "unresolved identifier"
New to swift and programming in general, am i missing something basic/ do i have a mis understanding? is there a way i can use the constant from the do {} statement outside of the do statement. Tried return but that is reserved for functions.
Really appreciate any help
Thanks
You have two options (I'll assume that splitLat is String type)
do{
let splitLat = try self.splitLatitude(self.currentLocation)
//do rest of the code here
} catch {
print("error") //Example - Fix
}
second option, predeclare the variable
let splitLat : String? //you can late init let vars from swift 1.2
do{
splitLat = try self.splitLatitude(self.currentLocation)
} catch {
print("error") //Example - Fix
}
//Here splitLat is recognized
Now, some explanation of your problem.
in Swift (and many other languages) variables are only defined inside the scope they are defined
scope is defined between these brackets {/* scope code */ }
{
var x : Int
{
//Here x is defined, it is inside the parent scope
var y : Int
}
//Here Y is not defined, it is outside it's scope
}
//here X is outside the scope, undefined
A 3rd option is to use a closure:
let splitLat:String = {
do {
return try self.splitLatitude(self.currentLocation)
}
catch {
print("error") //Example - Fix
return ""
}
}()
LatSplitLabel.text = "\(splitLat)"
This is a scoping error if you want to succeed execution after the do/catch block. You must declare the variable outside of this do/catch scope in order to utilize it after the do/catch execution.
Try this:
var splitLat: <initialType> = <initialValue>
do {
let splitLat = try self.splitLatitude(self.currentLocation)
} catch {
print("error")
}
print(splitLat)
Here is a concocted example that runs in a Swift 2.2 playground:
enum Errors: ErrorType {
case SomeBadError
}
func getResult(param: String) throws -> Bool {
if param == "" {
throw Errors.SomeBadError
}
return true
}
var result = false
do {
result = try getResult("it")
} catch {
print("Some error")
}
print(result)
How to remove Optional("") text on optional value when displaying without forcing to !.
Update
// I have somthing like this declared outside class
// I put question mark wrapper since I don't know when this session might have a value
var url = "\(self.session?.apiURL)/api/products.json"
// private session
private var _session:Session?
class MyClass
{
.
.
.
// the value of apiURL depends on session, session has optional value and declared as
// custom lazy loaded var session
var session:Session?
{
get
{
if _session == nil
{
_session = // fetch from coredata store if there is an active session. Might return nil
// if no active session
if _session == nil
{
// I just print "No active session"
}
}
// return _session may or may not contain any value
return _session
}
}
}
When the session has a value the url has a value:
Optional("my_api_url_here")/api/products.json
You can use this pod http://cocoapods.org/pods/NoOptionalInterpolation.
Alternatively, add this code to your project to remove the Optional(...) and nil text in string interpolation:
public protocol Unwrappable {
func unwrap() -> Any?
}
extension Optional: Unwrappable {
public func unwrap() -> Any? {
switch self {
case .None:
return nil
case .Some(let unwrappable as Unwrappable):
return unwrappable.unwrap()
case .Some (let some):
return some
}
}
}
public extension String {
init(stringInterpolationSegment expr: Unwrappable) {
self = String(expr.unwrap() ?? "")
}
}
Please note that simply overriding the description function of Optional won't work for string interpolation, although it works for print.
you can use ?? (null coalescing operator) to unwrap it and provide a default value if it is nil
let sessionApiURL = self.session?.apiURL ?? ""
var url = "\(sessionApiURL)/api/products.json"
If you want to without optional value you have to unwrap the Optional. You can use "optional binding" to unwrap an Optional:
if let url = self.session?.apiURL{
//you can use url without optional
print(url)
}
You can check my example in online swift playground for better understand.