Swift: How to determine object type based on the outcome of switch statement - swift

I have a function that returns whether an item exists in 1 of 3 CoreData entities, depending on the list: CoreDataList enum argument passed to that function:
static func listContains(word: String, list: CoreDataList) -> Bool {
var reply = false
switch list {
case .itemsSearched:
let request : NSFetchRequest<ItemsSearched> = ItemsSearched.fetchRequest()
case .itemsAdded:
let request : NSFetchRequest<ItemsAdded> = ItemsAdded.fetchRequest()
case .itemsFinished:
let request : NSFetchRequest<ItemsFinished> = ItemsFinished.fetchRequest()
}
do {
request.predicate = NSPredicate(format: "item == %#", word)
let fetchedResults = try context.fetch(request)
if fetchedResults.count == 1 {
reply = true
}
} catch {
print ("fetch task failed", error)
}
return reply
}
The problem here is that let request cannot be initialized before the switch statement because its type will depend on the outcome of the switch.
Therefore I cannot find a way to do this function without repeating the entire do catch block for each case, which will result in repeating the same code three times. The other option is to separate into three different function, but this presents the same code repetition problem.

You can create a generic function for this
static func checkCount<T: NSManagedObject>(word: String, context: NSManagedObjectContext, type: T.Type) -> Bool {
let request = T.fetchRequest()
request.predicate = NSPredicate(format: "item == %#", word)
do {
let fetchedResults = try context.fetch(request)
if fetchedResults.count == 1 {
return true
}
} catch {
print ("fetch task failed", error)
}
return false
}
and then call it from inside the switch
case .itemsSearched:
return checkCount(word: word, context: context, type: ItemsSearched.self)
Note that I added context as a parameter to the function since I didn't know how it is declared, perhaps that parameter isn't needed.

Related

Nested Cloudkit queries not printing in the right order

My code is not printing in the right order. I think its a problem with the way I'm using dispatch to main. I want to pass in a boss ID. Find the User with that ID then get their SubscribedBosses Array. For every ID in that array query that user and return that users screenName. Append those screenName to the userArray. After that Complete the GetBossSubs function and return userArray (array of screenNames).
Right now result of the .onAppear is running before the function in .onAppear is actually completed. User Appended prints before Found TestUserName
static func getBossSubs(bossID: String, completion: #escaping (Result<UserNames, Error>) ->
()) {
let pred = NSPredicate(format: "uniqueID = %#", bossID)
let sort = NSSortDescriptor(key: "creationDate", ascending: false)
let query = CKQuery(recordType: RecordType.Users, predicate: pred)
query.sortDescriptors = [sort]
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["subscribedBosses"]
operation.resultsLimit = 50
operation.recordFetchedBlock = { record in
DispatchQueue.main.async {
guard let subs = record["subscribedBosses"] as? [String] else {
print("Error at screenName")
completion(.failure(CloudKitHelperError.castFailure))
return
}
let userArray = UserNames() //Error that it should be a LET is here.
for boss in subs{
CloudKitHelper.getBossScreenName(bossID: boss) { (result) in
switch result{
case .success(let name):
userArray.names.append(name) //works fine
print("Found \(userArray.names)") //Prints a name
case .failure(let er):
completion(.failure(er))
}
}
}
print("does this run?") // Only runs if No Name
completion(.success(userArray)) // contains no name or doesn't run?
}
}
operation.queryCompletionBlock = { (_, err) in
DispatchQueue.main.async {
if let err = err {
completion(.failure(err))
return
}
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
I call the code like this:
.onAppear {
// MARK: - fetch from CloudKit
self.userList.names = []
let myUserID = UserDefaults.standard.string(forKey: self.signInWithAppleManager.userIdentifierKey)!
// get my subs projects
CloudKitHelper.getBossSubs(bossID: myUserID) { (results) in
switch results{
case .success(let user):
print("Users appended")
self.userList.names = user.names
case .failure(let er):
print(er.localizedDescription)
}
}
}
This is because you run an asynchronous function inside another asynchronous function.
// this just *starts* a series of asynchronous functions, `result` is not yet available
for boss in subs {
CloudKitHelper.getBossScreenName(bossID: boss) { result in
switch result {
case .success(let name):
userArray.names.append(name) // works fine
print("Found \(userArray.names)") // Prints a name
case .failure(let er):
completion(.failure(er))
}
}
}
// this will run before any `CloudKitHelper.getBossScreenName` finishes
completion(.success(userArray))
A possible solution may be to check if all CloudKitHelper.getBossScreenName functions finish (eg. by checking the size of the userArray) and only then return the completion:
for boss in subs {
CloudKitHelper.getBossScreenName(bossID: boss) { result in
switch result {
case .success(let name):
userArray.names.append(name)
if userArray.names.count == subs.count {
completion(.success(userArray)) // complete only when all functions finish
}
case .failure(let er):
completion(.failure(er))
}
}
}
// do not call completion here, wait for all functions to finish
// completion(.success(userArray))

When updating UI with json response, "Thread 1: Fatal error: Index out of range." is received - Swift

Attempting to update a menu item to return all fixtures from api.
I've got a list of fixtures being returned.
How do I go about updating the fixtureMenuItem in the MenuController with all fixtures returned from the JSON? I thought I might be able to do something along the lines of fixtureMenuItem.title = fixtures.description
, but I'm getting "Thread 1: Fatal error: Index out of range."
Model
struct LiveScores: Codable {
let success: Bool
let fixturesData: FixturesData?
enum CodingKeys: String, CodingKey {
case fixturesData = "data"
case success
}
}
struct FixturesData: Codable {
let fixtures: [Fixture]
let nextPage, prevPage: Bool
enum CodingKeys: String, CodingKey {
case fixtures
case nextPage = "next_page"
case prevPage = "prev_page"
}
}
struct Fixture: Codable, CustomStringConvertible {
let id, date, time, round: String
let homeName, awayName, location, leagueID: String
let homeID, awayID: Int?
enum CodingKeys: String, CodingKey {
case id, date, time, round
case homeName = "home_name"
case awayName = "away_name"
case location
case leagueID = "league_id"
case homeID = "home_id"
case awayID = "away_id"
}
var description: String {
return "\(time): \(homeName) vs. \(awayName)"
}
}
// MARK: Convenience initializers
extension LiveScores {
init(data: Data) throws {
self = try JSONDecoder().decode(LiveScores.self, from: data)
}
}
Menu Controller - this is where I want to update the fixture menu item, to include the time, home and away team names. "Here is where all the fixtures will be populated!" - this is the hardcoded text I wish to replace with the fixture data.
var fixtures = [Fixture]()
func updateScores() {
liveScoreApi.fetchFixtures()
if let fixtureMenuItem = self.Menu.item(withTitle: "Fixtures") {
fixtureMenuItem.title = "Here is where all the fixtures will be populated!"
// TODO - populate the UI with fixtures returned from JSON response
}
}
Fetch Fixtures - here's where the fixtures are retrieved.
func fetchFixtures() {
let session = URLSession.shared
let url = URL(string: "\(baseUrl)fixtures/matches.json?key=\
(apiKey)&secret=\(apiSecret)&date=2018-06-02")
let task = session.dataTask(with: url!) { data, response, err in
// check for a hard error
if let error = err {
NSLog("Live Scores Api Error: \(error)")
}
// check the response code
if let httpResponse = response as? HTTPURLResponse {
switch httpResponse.statusCode {
case 200: // perfecto!
if let liveScores = try? LiveScores.init(data: data!),
let fixture = liveScores.fixturesData
{
NSLog("\(fixture)")
}
case 401: // unauthorised
NSLog("Live Score Api returned an 'unauthorised' response.")
default:
NSLog("Live Scores Api returned response: %d %#", httpResponse.statusCode, HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))
}
}
}
task.resume()
}
In this example fixture data there are 26 fixtures and I want to show all of these.
Variations of this question come up constantly on SO.
Async functions don't wait for their results to be available. You give them a callback, which is a closure (a block of code you provide) that gets executed once the operation is complete.
You should rewrite your fetchFixtures() function to take a completion handler, and then refactor your updateScores() function to pass the code that updates your menu item into the completion handler for FetchFixtures.
See my answer to the question in the thread below for a simple example of this approach:
Swift: Wait for Firebase to load before return a function
As Duncan said in his answer, the issue was that the results weren't actually available.
I've implemented a completion handler of handleCompletion: on the fetchFixtures() function, which takes a true/false value plus the fixtures data. This is then returned in each http response case as shown below:
func fetchFixtures(handleCompletion:#escaping (_ isOK:Bool,_ param:
FixturesData?)->()) {
let session = URLSession.shared
let url = URL(string: "\(baseUrl)fixtures/matches.json?key=\
(apiKey)&secret=\(apiSecret)&date=2018-06-04")
let task = session.dataTask(with: url!) { data, response, err in
// check for a hard error
if let error = err {
NSLog("Live Scores Api Error: \(error)")
}
// check the response code
if let httpResponse = response as? HTTPURLResponse {
switch httpResponse.statusCode {
case 200: // perfecto!
if let liveScores = try? LiveScores.init(data: data!),
let fixture = liveScores.fixturesData
{
//NSLog("\(fixture)")
handleCompletion(true, fixture)
}
case 401: // unauthorised
NSLog("Live Score Api returned an 'unauthorised' response.")
handleCompletion(false, nil)
default:
NSLog("Live Scores Api returned response: %d %#", httpResponse.statusCode, HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))
handleCompletion(false, nil)
}
}
}
task.resume()
}
After implementing the above, I refactored the updateScores() to use this completion handler.
func updateScores() {
liveScoreApi.fetchFixtures() { (
isOK, fixture) in
if isOK == true {
if let fixtureMenuItem = self.Menu.item(withTitle: "Fixtures") {
fixtureMenuItem.title = (fixture?.fixtures.description)!
}
}
else {
NSLog("error fetching!")
}
}
}
The fixtureMenuItem now successfully displays the data if available.

Using Do/Catch in Swift

I am working on an app and want to get data back from a function. However sometimes data is missing or is different from the kind of that I want to retrieve. I am new to Swift and I can't find a way to write a function that does a little bit of processing and returns this data. When this data is missing, the function should give back a string "Not Found". Like this:
func processData(data:String) {
do {
//processing
var result = processedData
} catch {
var result = "Not Found"
}
return result
}
It would be very nice if somebody could help me.
You should check if the result is nil.
func processData(data: String?) -> String {
guard let result = data else {
return "Not Found"
  }
return result
}
The most concise way of doing it would be using the guard-let construct:
func processData(data: String?) -> String {
// Assuming getProcessedData(:) returns your processed data
guard let result = getProcessedData(data) else {
return "Not found"
}
return result
}
Also, your function is missing a return type. You must specify the return type like -> TYPE in all functions that return some value.
Those answer were written till mine are right. There is one way: with handler check get result and use by your point.
enum Errors: Error {
case noData
case unknownError
}
func progress(data: String?, completionHandler: #escaping (_ result: String? , _ error: Error?) -> Void ) {
guard let data = data else {
// Data is missing
throw nil, Errors.noData
}
// Do other things, and throw if necessary
result = data
return result, nil
}
// example of calling this function
process(data: "A data to process"){(result, error) -> Void in
//do any stuff
/*if error == nil {
}*/
}
A good practice in swift would be to use correctly the throws errors
This is an example inspired from yours :
enum Errors: Error {
case noData
case unknownError
}
func progress(data: String?) throws -> String {
guard let data = data else {
// Data is missing
throw Errors.noData
}
// Do other things, and throw if necessary
result = data
return result
}
do {
try process(data: "A data to process")
} catch {
print("An error occurred: \(error)")
}
You can try this code as is in a Swift Playgound
Your function needs to be explicit about returning something with e.g. -> String Also do-catch is for methods that can throw an error. It seems like you need to take a look at how to use optionals. Optionals can have a value or they can have no value.
fun processData(data: String) -> String {
var result: String?
// Do some processing and assign the result to result variable
guard let result = result else { return "Not Found" }
return result
}

Adding a rule to a custom rule in Eureka

So I've created a custom row which is just a row with a simply UITextView now I want to create a rule such that if the UITextView has under 100 characters the variable row.isValid will evaluate to false.
<<< TextViewRow("About Me") {
let cell = $0.baseCell as! TextViewCell
cell.textView.text = currentUser.aboutMe
$0.disabled = Condition.function([]) {
form in
print("in disabled")
let section = form.sectionBy(tag: "About Me")
let view = section!.header?.viewForSection(section!, type: .header) as! TitleHeaderView
if view.isLocked {
return true
} else {
return false
}
}
$0.cellUpdate({ (cell, row) in
if row.isDisabled {
cell.textView.isUserInteractionEnabled = false
cell.textView.textColor = UIColor.gray
} else {
cell.textView.isUserInteractionEnabled = true
cell.textView.textColor = UIColor.black
}
})
let ruleRequiredViaClosure = RuleClosure<String> { rowValue in
guard let rowValue = rowValue else {
return(ValidationError(msg : "Please write more!"))
}
let numberOfCharacters = rowValue.characters.count
return (numberOfCharacters < 250 ? ValidationError(msg: "Please write more!") : nil)
}
$0.add(rule: ruleRequiredViaClosure)
$0.validationOptions = .validatesOnDemand
}
With a normal TextRow this would compile and XCode won't throw any error, although since TextViewRow is a custom row I believe this is why it is throwing an error.
The error it gives me is that I need to use:
$0.add(ruleSet: ...)
but I can't find any documentation on it.
Also I'm not sure whether I am able to define my rule as I have since the rowValue in the closure probably isn't referencing anything. How would I fix this problem?
Welcome to the world of Eureka, %&*# all documentation on anything. I recently took a liking to Eureka and began to convert my project to it, along the way I hit many obstacles that could easily have been avoided if the documentation was decent. Fortunately for you, custom validation rules was one such obstacle; my implementation is as follows:
struct RulePassword<T: Equatable>:RuleType {
public init() {}
public var id: String?
public var validationError: ValidationError = ValidationError(msg: "Invalid password.")
public func isValid(value: T?) -> ValidationError? {
if let str = value as? String {
let errorMsg = RGOValidationHelper.passwordValidation(str)
return errorMsg != nil ? ValidationError(msg: errorMsg!) : nil
}
return validationError
}
}
Note the call to RGOValidationHelper simply returns an error string in the event that the validation fails, nil otherwise.
Before you go and create a custom rule however, it must be noted that Eureka actually includes a facility to set a minimum length on a field. RuleMinLength is the name of the rule and it can be initialised by passing in a value for min length, in your case 100. Here is Eureka's implementation for the rule:
public struct RuleMinLength: RuleType {
let min: UInt
public var id: String?
public var validationError: ValidationError
public init(minLength: UInt, msg: String? = nil){
let ruleMsg = msg ?? "Field value must have at least \(minLength) characters"
min = minLength
validationError = ValidationError(msg: ruleMsg)
}
public func isValid(value: String?) -> ValidationError? {
guard let value = value else { return nil }
return value.characters.count < Int(min) ? validationError : nil
}
}
I hope this helps somewhat, if you have any further problems with Eureka chances are I've come across and solved them before, so feel free to send me a message.

How do I tell which guard statement failed?

If I’ve got a bunch of chained guard let statements, how can I diagnose which condition failed, short of breaking apart my guard let into multiple statements?
Given this example:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
return nil
}
How can I tell which of the 4 let statements was the one that failed and invoked the else block?
The simplest thing I can think of is to break out the statements into 4 sequential guard else statements, but that feels wrong.
guard let keypath = dictionary["field"] as? String
else
{
print("Keypath failed to load.")
self.init()
return nil
}
guard let rule = dictionary["rule"] as? String else
{
print("Rule failed to load.")
self.init()
return nil
}
guard let comparator = FormFieldDisplayRuleComparator(rawValue: rule) else
{
print("Comparator failed to load for rawValue: \(rule)")
self.init()
return nil
}
guard let value = dictionary["value"] else
{
print("Value failed to load.")
self.init()
return nil
}
If I wanted to keep them all in one guard statement, I can think of another option. Checking for nils inside the guard statement might work:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
if let keypath = keypath {} else {
print("Keypath failed to load.")
}
// ... Repeat for each let...
return nil
}
I don't even know if that will compile, but then I might as well have used a bunch of if let statements or guards to begin with.
What's the idiomatic Swift way?
Erica Sadun just wrote a good blog post on this exact topic.
Her solution was to hi-jack the where clause and use it to keep track of which guard statements pass. Each successful guard condition using the diagnose method will print the file name and the line number to the console. The guard condition following the last diagnose print statement is the one that failed. The solution looked like this:
func diagnose(file: String = #file, line: Int = #line) -> Bool {
print("Testing \(file):\(line)")
return true
}
// ...
let dictionary: [String : AnyObject] = [
"one" : "one"
"two" : "two"
"three" : 3
]
guard
// This line will print the file and line number
let one = dictionary["one"] as? String where diagnose(),
// This line will print the file and line number
let two = dictionary["two"] as? String where diagnose(),
// This line will NOT be printed. So it is the one that failed.
let three = dictionary["three"] as? String where diagnose()
else {
// ...
}
Erica's write-up on this topic can be found here
Normally, a guard statement doesn't let you distinguish which of its conditions wasn't satisfied. Its purpose is that when the program executes past the guard statement, you know all the variables are non-nil. But it doesn't provide any values inside the guard/else body (you just know that the conditions weren't all satisfied).
That said, if all you want to do is print something when one of the steps returns nil, you could make use of the coalescing operator ?? to perform an extra action.
Make a generic function that prints a message and returns nil:
/// Prints a message and returns `nil`. Use this with `??`, e.g.:
///
/// guard let x = optionalValue ?? printAndFail("missing x") else {
/// // ...
/// }
func printAndFail<T>(message: String) -> T? {
print(message)
return nil
}
Then use this function as a "fallback" for each case. Since the ?? operator employs short-circuit evaluation, the right-hand side won't be executed unless the left-hand side has already returned nil.
guard
let keypath = dictionary["field"] as? String ?? printAndFail("missing keypath"),
let rule = dictionary["rule"] as? String ?? printAndFail("missing rule"),
let comparator = FormFieldDisplayRuleComparator(rawValue: rule) ?? printAndFail("missing comparator"),
let value = dictionary["value"] ?? printAndFail("missing value")
else
{
// ...
return
}
Very good question
I wish I had a good answer for that but I have not.
Let's begin
However let's take a look at the problem together. This is a simplified version of your function
func foo(dictionary:[String:AnyObject]) -> AnyObject? {
guard let
a = dictionary["a"] as? String,
b = dictionary[a] as? String,
c = dictionary[b] else {
return nil // I want to know more ☹️ !!
}
return c
}
Inside the else we don't know what did go wrong
First of all inside the else block we do NOT have access to the constants defined in the guard statement. This because the compiler doesn't know which one of the clauses did fail. So it does assume the worst case scenario where the first clause did fail.
Conclusion: we cannot write a "simple" check inside the else statement to understand what did not work.
Writing a complex check inside the else
Of course we could replicate inside the else the logic we put insito the guard statement to find out the clause which did fail but this boilerplate code is very ugly and not easy to maintain.
Beyond nil: throwing errors
So yes, we need to split the guard statement. However if we want a more detailed information about what did go wrong our foo function should no longer return a nil value to signal an error, it should throw an error instead.
So
enum AppError: ErrorType {
case MissingValueForKey(String)
}
func foo(dictionary:[String:AnyObject]) throws -> AnyObject {
guard let a = dictionary["a"] as? String else { throw AppError.MissingValueForKey("a") }
guard let b = dictionary[a] as? String else { throw AppError.MissingValueForKey(a) }
guard let c = dictionary[b] else { throw AppError.MissingValueForKey(b) }
return c
}
I am curious about what the community thinks about this.
One possible (non-idiomatic) workaround: make use of the where clause to track the success of each subsequent optional binding in the guard block
I see nothing wrong with splitting up your guard statements in separate guard blocks, in case you're interested in which guard statement that fails.
Out of a technical perspective, however, one alternative to separate guard blocks is to make use of a where clause (to each optional binding) to increment a counter each time an optional binding is successful. In case a binding fails, the value of the counter can be used to track for which binding this was. E.g.:
func foo(a: Int?, _ b: Int?) {
var i: Int = 1
guard let a = a where (i+=1) is (),
let b = b where (i+=1) is () else {
print("Failed at condition #\(i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
Above we make use of the fact that the result of an assignment is the empty tuple (), whereas the side effect is the assignment to the lhs of the expression.
If you'd like to avoid introducing the mutable counter i prior the scope of guard clause, you could place the counter and the incrementing of it as a static class member, e.g.
class Foo {
static var i: Int = 1
static func reset() -> Bool { i = 1; return true }
static func success() -> Bool { i += 1; return true }
}
func foo(a: Int?, _ b: Int?) {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
print("Failed at condition #\(Foo.i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
Possibly a more natural approach is to propagate the value of the counter by letting the function throw an error:
class Foo { /* as above */ }
enum Bar: ErrorType {
case Baz(Int)
}
func foo(a: Int?, _ b: Int?) throws {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
throw Bar.Baz(Foo.i)
}
// ...
}
do {
try foo(nil,1) // Baz error: failed at condition #1
// try foo(1,nil) // Baz error: failed at condition #2
} catch Bar.Baz(let num) {
print("Baz error: failed at condition #\(num)")
}
I should probably point out, however, that the above is probably closer to be categorized as a "hacky" construct, rather than an idiomatic one.
The simplest thing I can think of is to break out the statements into 4 sequential guard else statements, but that feels wrong.
In my personal opinion, the Swift way shouldn't require you to check whether the values are nil or not.
However, you could extend Optional to suit your needs:
extension Optional
{
public func testingForNil<T>(#noescape f: (Void -> T)) -> Optional
{
if self == nil
{
f()
}
return self
}
}
Allowing for:
guard let keypath = (dictionary["field"] as? String).testingForNil({ /* or else */ }),
let rule = (dictionary["rule"] as? String).testingForNil({ /* or else */ }),
let comparator = FormFieldDisplayRuleComparator(rawValue: rule).testingForNil({ /* or else */ }),
let value = dictionary["value"].testingForNil({ /* or else */ })
else
{
return nil
}
My two cents:
Since Swift doesn't let me add the where in the guard let, I came up with this solution instead:
func validate<T>(_ input: T?, file: String = #file, line: Int = #line) -> T? {
guard let input = input else {
print("Nil argument at \(file), line: \(line)")
return nil
}
return input
}
class Model {
let id: Int
let name: String
init?(id: Int?, name: String?) {
guard let id = validate(id),
let name = validate(name) else {
return nil
}
self.id = id
self.name = name
}
}
let t = Model(id: 0, name: "ok") // Not nil
let t2 = Model(id: 0, name: nil) // Nil
let t3 = Model(id: nil, name: "ok") // Nil
I think other answers here are better, but another approach is to define functions like this:
func checkAll<T1, T2, T3>(clauses: (T1?, T2?, T3?)) -> (T1, T2, T3)? {
guard let one = clauses.0 else {
print("1st clause is nil")
return nil
}
guard let two = clauses.1 else {
print("2nd clause is nil")
return nil
}
guard let three = clauses.2 else {
print("3rd clause is nil")
return nil
}
return (one, two, three)
}
And then use it like this
let a: Int? = 0
let b: Int? = nil
let c: Int? = 3
guard let (d, e, f) = checkAll((a, b, c)) else {
fatalError()
}
print("a: \(d)")
print("b: \(e)")
print("c: \(f)")
You could extend it to print the file & line number of the guard statement like other answers.
On the plus side, there isn't too much clutter at the call site, and you only get output for the failing cases. But since it uses tuples and you can't write a function that operates on arbitrary tuples, you would have to define a similar method for one parameter, two parameters etc up to some arity. It also breaks the visual relation between the clause and the variable it's being bound to, especially if the unwrapped clauses are long.
This code can be used for all guard and if logic tests like optional, bool and case tests. It prints a line of a logic test which failed.
class GuardLogger {
var lastGoodLine: Int
var lineWithError: Int { lastGoodLine + 1 }
var file: String
var function: String
init(file: String = #file, function: String = #function, line: Int = #line) {
self.lastGoodLine = line
self.file = file
self.function = function
}
func log(line: Int = #line) -> Bool {
lastGoodLine = line
return true
}
func print() {
Swift.print([file, function, String(lineWithError)].joined(separator: " "))
}
}
let testBoolTrue = true
let testBoolFalse = false
let guardLogger = GuardLogger()
guard
testBoolTrue, guardLogger.log(),
let testOptionalBoolTrue = Optional(testBoolTrue), guardLogger.log(),
let selfIsViewController = self as? UIViewController, guardLogger.log(),
testBoolTrue == false, guardLogger.log() // this fails
else {
print(guardLogger.lastGoodLine)
fatalError()
}