.prepare vs. .select - swift

I have a working connection to a database in an iOS10 app, using SQLite.swift.
I want to select details for a specific university where I have an IHE_ID passed in from another view controller.
I would like to just select the row for that specific university, but I can't get a query to work. I can however loop through all the data with a prepare and then choose the one I need from that, which of course is more resource intensive than I need since I already have the specific IHE_ID passed in as anIHE Int from the sending view controller.
connection is working so omitting that code...
do {
let db = try Connection(destinationDBPath, readonly: true)
let IHEs = Table("IHE")
let IHE_ID = Expression<Int>("IHE_ID")
let IHE_Name = Expression<String>("IHE_Name")
let IHE_COE_Url = Expression<String>("IHE_COE_URL")
let IHE_Sector = Expression<Int>("IHE_Sector")
let IHE_COE_Name = Expression<String>("IHE_COE_Name")
for ihe in try db.prepare(IHEs){
if (ihe[IHE_ID] == anIHE){
// build this string, otherwise ignore rest of dataset (doing this because query isn't working)
buildIHE = "Name: \(ihe[IHE_Name])\n"
buildIHE.append("URL: \(ihe[IHE_COE_Url])\n")
// buildIHE.append("Sector: \(ihe[IHE_Sector])\n")
if (ihe[IHE_Sector] == 0) {
buildIHE.append("Sector: Public\n")
} else {
buildIHE.append("Sector: Private\n")
}
buildIHE.append("College of Education Name: \(ihe[IHE_COE_Name])\n")
}
}
print ("Got through building IHE portion of view")
What I'd like to do is use this instead of the for loop.
if let query = IHEs.select(IHE_ID,IHE_Name,IHE_COE_Url,IHE_Sector,IHE_COE_Name).filter(IHE_ID == anIHE){
print("Query successful for \(anIHE) with name \(query[IHE_Name])")
// more actions to build the string I need would then occur
} else {
print("Query has failed or returned nil")
}
Finally, I'll use the selected elements if I can get the query to work.
I think I probably just have something wrong with my syntax on the query, but any help is appreciated.
The line with the "if let query" has this error in Xcode:
Initializer for conditional binding must have Optional type, not 'Table'.
This leads me to think it's something with my use of the .select statement and just new to using SQLite.swift and swift in general.
Last thing is that anIHE comes into this function as an Int, and IHE_ID is Expression as shown in this code. I'm thinking this may be part of the problem.

The Initializer for conditional binding must have Optional type error means that the expression on the right of the if let v = expr statement is not an Optional: there is no point using if let, and the Swift compiler says that you should just write let v = expr.
And indeed, IHEs.select(...).filter(...) returns a non-optional value of type Table.
It is not the database row you expect, because the query has been defined, but not executed yet. After all, you weren't using db: where would the rows be loaded from?
The solution is to bring back the database connection, and load a single row. This is done with the pluck method.
if let ihe = try db.pluck(IHEs.select(...).filter(...)) {
print("Name \(ihe[IHE_Name])")
}

Related

How to do unwrappin inside If-let statement?

I am using if let for getting the object if its not nil. But I also need to check other condition as well i.e., if "treatmentContext.patientTreatment.canWritePermissions.contains(treatmentContext.pathPatientTreatment.owner". That I am able to do by putting comma after the first statement but here the issue is, I need to unwrap the value of treatmentContext.pathPatientTreatment.owner and here I don't know where exactly I need to unwrap that so that my if condition gets pass when it meets all the criteria.
Below is the code for reference.
if let treatmentContext = IoC.resolve(Treatment.self, from: .treatment), treatmentContext.patientTreatment.canWritePermissions.contains(treatmentContext.pathPatientTreatment.owner)
{
self.presentNavigation(isNew: isNew)
}
You already know you can separate the conditions with ,, so just do that again, but this time it's another optional binding clause:
if let treatmentContext = IoC.resolve(Treatment.self, from: .treatment),
let owner = treatmentContext.pathPatientTreatment.owner,
treatmentContext.patientTreatment.canWritePermissions.contains(owner) {
self.presentNavigation(isNew: isNew)
}
You can separate any number of optional binding clauses, Bool conditions, or case ... patterns with , in an if.

How to get value of a NSSingleObjectArrayI

func responseDataHandler(data: NSDictionary) {
let temperature_c = data.value(forKeyPath: "data.current_condition.temp_C")
DispatchQueue.main.async{
self.Temperature.text = temperature_c as? String
}
}
I have the above code where I am accessing a weather API which returns data in the form of an NSDictionary to this function. I need to access the value in temperature_c which when I try to print it, it says that it is: Optional(<__NSSingleObjectArrayI 0x600002147fd0>(
25
)
). Temperature is the outlet for label on my storyboard which I want to take on the value of 25 however as written now, it doesn't work and I have tried everything to try and access the value in the Single Object Array but nothing is working. I found this stack overflow question that was similar but it doesn't work for my situation because I keep getting the error that temperature_c is of type any and doesn't have subscripts.
The issue is that you can't cast to String an array, you should try to convert it to [String]. So could change your code to:
self.Temperature.text = (temperature_c as? [String])?.first ?? "Not available"
Let's go step by step:
temperature_c as? [String] tries to convert the NSDictionary to a String array which is the expectable type.
Since the previous step may return nil we have to use optional chaining ?. If we got a valid array using first return the the arrays first element.
Since both previous steps can return nil we can use nil coalescing operator to return a default value. In this case I use "Not available" but you can set any value.
You could write it in a more verbose way like this:
var text2Display2 = "Not available"
if let theArray = temperature_c as? [String] {
if let element = theArray.first {
text2Display2 = element
}
}
self.Temperature.text = text2Display2

Swift 3: switching on UILabel.text and getting type error but type matches

I have a function with a parameter of type [String]. I can call the function and it executes successfully. However, I recently encountered an error when adding a new data source, and I'm trying to debug. I'm switching on the array parameter, using the same values I'm passing in to call the function. Swift throws an error:
Swift Expression pattern of type [String] cannot match values of type [String]
Here's the section of code I'm working with:
func calcRelevance(array: [String]) {
/* block of code */
if relevanceArr.count >= 1 {
//do something
} else {
switch array {
case self.someArray:
self.label.text = "No results returned from some data source"
case self.someOtherArray:
self.label.text = "No results returned from some other data source"
default:
self.label.text = "Your search yielded no results. Please refine your search by tapping back and using more relevant search terms"
}
}
}
As I typed this out I realized that I may not be able to use a switch block inside an if statement. I'm not sure if control flow allows for such, which seems odd to get a type error from it if that's the case.
Also, I've looked at Switching on UIButton title: Expression pattern of type 'String' cannot match values of type 'String?!' and it has to do with unwrapping optionals which I don't believe applies here.
That's not how a switch is meant to work. The switch is not expecting case values to be arrays. If you want to compare arrays, use an if statement instead.
if array == someArray {
/* do something */
} else if array == someOtherArray {
/* do something else */
} else {
/* do another thing */
}

memory usage increases dramatically after each FMDB query

Below is my source code, every time I execute the function, the memory usage increases dramatically. Please help to point out what is the problem.
func loadfontsFromDatabase(code:String)->[String] {
let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask,true)[0] as AnyObject
let databasePath = documentsPath.appending("/bsmcoding.sqlite")
let contactDB = FMDatabase(path: databasePath as String)
var c:[String]=[]
let querySQL = "SELECT FONT FROM BSMCODE WHERE BSMCODE.CODE = '\(code)' ORDER BY NO DESC"
NSLog("query:\(querySQL)")
let results:FMResultSet? = Constants.contactDB?.executeQuery(querySQL, withArgumentsIn: nil)
while (results?.next())! {
c.append((results?.string(forColumn: "FONT"))!)
}
results?.close()
return c
}
There's nothing here that would account for any substantial memory loss. I would suggest using the "Debug Memory Graph" feature in Xcode 8 to identify what objects are being created and not being released, but I suspect the problem rests elsewhere in your code. Or use Instruments to track it down what's leaking and debug from there. See https://stackoverflow.com/a/30993476/1271826.
There are unrelated issues here, though:
You are creating local contactDB, but you never open it and you never use it. It will be released when the routine exits, but it's completely unnecessary if you're going to use Constants.contactDB, anyway.
I'd advise against using string interpolation when building your SQL. Use ? placeholder and pass the code in as a parameter. This is much safer, in case the code ever contained something that couldn't be represented in SQL statement. (This is especially true if the code was supplied by the user, in which case you'd be susceptible to SQL injection attacks or innocent input errors that could lead to crashes.)
For example, you could do something like:
func loadfontsFromDatabase(code: String) -> [String] {
var c = [String]()
let querySQL = "SELECT FONT FROM BSMCODE WHERE BSMCODE.CODE = ? ORDER BY NO DESC"
let results = try! Constants.contactDB!.executeQuery(querySQL, values: [code])
while results.next() {
c.append((results.string(forColumn: "FONT"))!)
}
return c
}
If you don't like the forced unwrapping, you can do optional unwrapping if you want, but personally I'd rather know immediately when debugging during the development phase if there's some logic mistake (e.g. the contactDB wasn't open, the SQL is incorrect, etc.). But you can do optional binding and add the necessary guard statements if you want. But don't just do optional binding and silently return a value suggesting that everything is copacetic, leaving you with a debugging challenge of tracking down the problem if you don't get what you expected.
But the key point is to avoid inserting values into your SQL directly. Use ? placeholders.

Swift: Can not use array filter in if let statement condition

Suppose I have an array of user's names
let users = ["Hello", "1212", "12", "Bob", "Rob"]
I want to get the first user whose name length is 2, so I filtered the array and got the first user
if let selected = users.filter{$0.characters.count == 2}.first {
print(selected)
}
This code is throwing a compilation error under swift 2.2
Consecutive statements on a line must be separated by ';'
However, this is working fine though
let selected = users.filter{$0.characters.count == 2}.first
if let selected = selected {
print(selected)
}
Can anyone explain why do I need to store filter result in a separate variable first? Any help would be really appreciated.
You can make this work by putting parentheses around the closure that you're passing to filter:
if let selected = users.filter({$0.characters.count == 2}).first {
print(selected)
}
That is the right way to do it. The trailing closure syntax doesn't work very well sometimes on lines with extra elements. You could also put parentheses around the whole statement:
if let selected = (users.filter {$0.characters.count == 2}.first) {
print(selected)
}
Swift is just having trouble parsing your statement. The parentheses give it help in how to parse the line. You should prefer the first way since the closure is indeed a parameter of filter, so enclosing it in parentheses makes it clear to Swift that you are passing it to filter.