I have the following function with throws:
func doSomething(_ myArray: [Int]) throws -> Int {
if myArray.count == 0 {
throw arrayErrors.empty
}
return myArray[0]
}
But when the array is equal to 0 it will throw the error but it will continue executing the function returning empty array.
How can I exit the function when goes to the if statement ?
The function does return when you throw an error. Pop this in a playground and look at the output.
//: Playground - noun: a place where people can play
enum MyError: Error {
case emptyArray
}
func doSomething<T>(_ array: [T]) throws -> T {
guard !array.isEmpty else {
throw MyError.emptyArray
}
return array[0]
}
do {
let emptyArray: [Int] = []
let x = try doSomething(emptyArray)
print("x from emptyArray is \(x)")
} catch {
print("error calling doSeomthing on emptyArray: \(error)")
}
do {
let populatedArray = [1]
let x = try doSomething(populatedArray)
print("x from populatedArray is \(x)")
} catch {
print("error calling doSeomthing on emptyArray: \(error)")
}
You'll see the output is
error calling doSeomthing on emptyArray: emptyArray
x from populatedArray is 1
Note that you don't see the output from print("x from emptyArray is \(x)") because it was never called, because throw ends the execution of that function. You can also confirm this because guard statements require the function to exit.
Also, just to note, if you want the first thing from an array you can just use myArray.first which will return T?, and you can handle the nil case instead of having to deal with an error. For example:
//: Playground - noun: a place where people can play
let myArray = [1, 2, 3]
if let firstItem = myArray.first {
print("the first item in myArray is \(firstItem)")
} else {
print("myArray is empty")
}
let myEmptyArray: [Int] = []
if let firstItem = myEmptyArray.first {
print("the first item in myEmptyArray is \(firstItem)")
} else {
print("myEmptyArray is empty")
}
which outputs:
the first item in myArray is 1
myEmptyArray is empty
You need to understand error handling. If you insert a throws keyword, anybody calling it will need to use a do-catch statement, try?, try! or continue to propagate them. So what will happen if the error is thrown is up to the caller.
Here's an example:
do {
try doSomething(foo)
} catch arrayErrors.empty {
fatalError("Array is empty!")
}
Apple's documentation for error handling
If you want to exit once you reach the if, simply don't use error handling and call fatalError inside the if.
if myArray.count = 0 {
fatalError("Error happened")
}
Related
I am trying to Apollo framework and a graphql api to obtain the data then return it. Once I have the data in another swift file, I want to call on certain parts of the data and assign it to a variable. The errors I get is variable used before it is initialized. and if try to return the variable from within the closure I get "Unexpected Non-Void Return Value In Void Function ". I heard of ways to get around that error but I don't completely understand it and how it works with my code. If you need more code or context you can message me and I can share my GitHub repo. Sorry if the code is bad, please don't roast me. I am still a beginner.
import Foundation
import Apollo
struct AniListAPI {
let aniListUrl = "https://graphql.anilist.co"
func ObtainData(AnimeID: Int)-> QueryQuery.Data{
var theData: QueryQuery.Data
let theInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: theInfo) { result in
switch result {
case .failure(let error):
print("A big No no happened \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
theData = Info
}
}
return theData
}
}
Unexpected Non-Void Return Value In Void Function.
The reason you're getting this warning is because you can't return value from inside the closure. Use closure instead of returning value.
func ObtainData(AnimeID: Int, completion: #escaping (Data) -> Void) {
var TheData: QueryQuery.Data
let TheInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: TheInfo) { result in
switch result {
case .failure(let error):
print("A big no no happened retard \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
TheData = Info
completion(TheData)
}
}
}
and call it like..
ObtainData(AnimeID: 123) { (anyData) in
print (anyData)
// continue your logic
}
I have the following function and it's working great but now I need the value out of the function to use it for another purpose.
But my problem is that I always have to execute the function twice to get a value in the outer part of the function (var valueOutOfFunction).
var valueOutOfFunction = [(String)]()
func loadQuery(com:#escaping( [(Int, String)] -> ())){
var arrayOfTuples = [(Int, String)]()
db.collection("Data").whereField("age", isGreaterThanOrEqualTo: 1).whereField("age", isLessThanOrEqualTo: 50).whereField("gender", isEqualTo: "F").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for (index, document) in querySnapshot!.documents.enumerated() {
arrayOfTuples += [(index, document.documentID)]
}
}
com(arrayOfTuples)
}
}
Then I am calling it here:
loadQuery { arr in
self.valueOutOfFunction = arr // Has a value in the first execution
}
print (valueOutOfFunction) //Executing the first time there is no value in the variable, the second time it have a value.
Why is it just on the second attempt available and what could be a solution for this problem ?
Thanks!
That is because the valueOutOfFunction is happening inside of that closure which is being called asynchronously to the print statement. Additionally, you are executing the completion outside of the else statement which may be firing before your for loop completes. What you want to do is control the flow from within the closure itself like so:
// move com(arrayOfTuples) up into the else block but outside of the for loop
func handleQuery() {
loadQuery { doStuffWithResult($0) }
// This line will run before the first line of doStuffWithResult
// It would be best to end the function logic at loadQuery
}
func doStuffWithResult(_ arrayOfTuples: [(Int, String)]) {
print(arrayOfTuples)
// Do other work here
}
What you're looking into is control flow. You want to execute X if Y has occurred and Z if !Y right?
I would recommend looking into swift's result type. This will help you manage control flow with closures.
An example:
typealias QueryResult = [(Int, String)]
enum QueryErrors: Error {
case couldNotFind
}
func loadQuery(_ result: #escaping (Result<QueryResult, Error>) -> Void) {
db.collection("Data").whereField("age", isGreaterThanOrEqualTo: 1).whereField("age", isLessThanOrEqualTo: 50).whereField("gender", isEqualTo: "F").getDocuments() { (querySnapshot, err) in
guard let documents = querySnapshot?.documents.enumerated() else {
// I think enumerated will give you an array of tuples... if not add this to the end of that line. After enumerated but before else
// enumerated().compactMap { ($0, $1) }
result(.failure(err ?? QueryErrors.couldNotFind))// passes back Firebase error if it exists or default error if it doesn't
return
}
result(.success(documents))
}
}
// how it works
loadQuery() { result in
switch(result) {
case .success(let arr): print(arr)
case .failure(let error): print(error)
}
}
I got confused error handling in swift3. I try to do like "if XX function got error then try YY function"
Let me show you what I try:
class MyClass {
enum error: Error
{
case nilString
}
func findURL() {
do {
let opt = try HTTP.GET(url_adr!)
opt.start { response in
if let err = response.error {
print("error: \(err.localizedDescription)")
return //also notify app of failure as needed
}
do
{
/* This is func1. and got error. I want to if this function has error then go next function. */
try self.stringOperation(data: response.description)
}
catch{
print("doesn't work on func1. trying 2nd func")
self.stringOperation2(data:response.descritption)
}
}
} catch let error {
print("got an error creating the request: \(error)")
}
}
func stringOperation(data:String)throws -> Bool{
do{
/** 1 **/
if let _:String = try! data.substring(from: data.index(of: "var sources2")!){
print("its done")
}else{
throw error.nilString
}
IN 1: I got this fatal error on this line:
"fatal error: unexpectedly found nil while unwrapping an Optional value" and program crashed.
I googled error handling try to understand and apply to in my code. However not succeed yet. Can someone explain where did I wrong?
Additional info: I got String extension for .substring(from:...) , and .index(of:"str"). So these lines doesn't got you confused.
As a general rule, try avoiding using force unwrapping (!), where you have
if let _: String= try! data.substring...
Instead use
if let index = data.index(of: "var sources2"),
let _: String = try? data.substring(from: index) { ... } else { ... }
That way you remove the two force unwraps that may be causing your crash. You already have the if let protection for catching the nil value, so you can make the most of it by using the conditional unwrapping.
So my goal here is to basically perform a query in a while loop and append results of the query to my array. When I run the code my "level" variable starts from zero and increments infinitely. I'm highly convinced that my problem is caused by fact that my code is running on 2 async queues but just can't figure out the exact cause.
func displayPathOf(argument: Argument, threadTableView: UITableView) {
array.removeAll()
threadTableView.reloadData()
var level = argument.level!-1
array.insert(argument, at: 0)
var stop = false
DispatchQueue.global(qos: .userInteractive).async {
repeat {
level += 1
print(level)
let query = Argument.query()?.whereKey("level", equalTo: level).addDescendingOrder("reach")
query?.getFirstObjectInBackground(block: { (object, error) in
if object != nil {
DispatchQueue.main.async {
array.append(object as! Argument)
print(array)
threadTableView.reloadData()}
} else {
stop = true
print(error)
}
})
} while stop == false
}
}
Your code boils down to:
do-in-background {
repeat {
level += 1
do-in-background { ... }
} while stop == false
}
do-in-background (i.e. both async and getFirstObjectInBackground) returns immediately, so from the point of view of this loop, it doesn't matter what's in the block. This is equivalent to a tight loop incrementing level as fast as it can.
It looks like you're trying to serialize your calls to getFirstObjectInBackground. You can do that one of two ways:
Have your completion block kick off the next search itself and remove the repeat loop.
Use a DispatchGroup to wait until the completion block is done.
In your case, I'd probably recommend the first. Get rid of stop and make a function something (vaguely) like:
func fetchObject(at level: Int) {
let query = Argument.query()?.whereKey("level", equalTo: level).addDescendingOrder("reach")
query?.getFirstObjectInBackground(block: { (object, error) in
if let object = object {
DispatchQueue.main.async {
array.append(object as! Argument)
print(array)
threadTableView.reloadData()}
// Schedule the next loop
DispatchQueue.global(qos: .userInteractive).async { fetchObject(level + 1) }
} else {
print(error)
}
})
}
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)