Pitfall in Swift Type Casting - swift

I am not totally sure if this the right place to post this, as it is more a pitfall I have found than a question I would ask. (Although I would be very interested if someone could explain the reason why this happens.)
So in my Swift iOS-app I had to use Objective-C Arrays for sorting. I knew for a fact that the NSMutableArray called results would contain only MyObjects. Thus, after sorting I cast it to [MyObject] like this to save it in the variable myArrayVar : [MyObject].
myArrayVar = (results as NSArray) as [MyObject]
This worked fine until I tested it on a release build. There it crashed. What I had to do to fix the crash was this:
if let results = results as Any as? NSArray {
if let results = results as? [MyObject] {
myArrayVar = results
} else { NSLog("the impossible happened.") }
} else { NSLog("the impossible happened.") }
Now we can see that this version cannot crash when the casting goes wrong whereas the first version would. However, the cast does not go wrong as I could verify by never seeing the log message.
So what might be the difference at runtime between these two versions of type casting? Whatever it is I have the feeling it might be a pitfall for others as well.

Related

Get all attribute names of Core Data entity; Swift

Is there a more efficient way to retrieve all the names/titles of attributes of a NSManagedObject than this:
func getAllAttributeTitles(_ myStatSheet:StatSheet) -> Array<String> {
let dictAttributes = myStatSheet.entity.attributesByName
var arrAttributeTitles:Array<String> = []
for (key, _) in dictAttributes {
arrAttributeTitles.append(key)
}
return arrAttributeTitles
}
As I mentioned, what you've got is the right way to do it. There are other ways but I wasn't at a Mac earlier and couldn't try them out.
A more "Swift-y" way to get the array would be something like
let arrAttributeTitles = myStatSheet.entity.attributesByName.enumerated().map { $0.element.key }
This won't be any more efficient, since it's really doing the same things, but it might be more what you were thinking of when you asked. It's still getting attributesByName and iterating over the result to get strings naming the attributes.
It might be worth noting that the argument type on your method could be NSManagedObject instead of StatSheet, since the code will work for any managed object of any entity type.

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: Dictionary, Remove key and value that are nil

I have a dictionary that a fetch request is returning. This dictionary is then made in to an array of dates from [String: NSDate]. This dictionary has a value that is [:]. I cannot do anything to remove it. Can anyone help because I have spent two nights trying everything.
let results = try managedObjectContext.executeFetchRequest(fetchRequest) as! [[String:NSDate]]
print("results \(results)")
dates = results.map { $0["savedTime"]! as NSDate }
This failed due to the savedTime Key being nil
print result is
[["savedTime": 2016-07-19 23:00:00 +0000], [:]]
This construct $0["savedTime"]! is wrong. Putting the ! on the object means you know that the object will always be there, but it isn't always there. You are lying to the compiler so it is crashing. Try removing the !.
Also, putting the as NSDate is unnecessary because you already told the compiler that the values are NSDates in the line above. Lastly, since not all the dictionaries have the correct key, you need to remove any that don't. There are a couple of ways to do that, one would be to filter out the nils. Another is to use flatMap which converts and filters out nils at the same time.
Then you end up with the below.
let results = try managedObjectContext.executeFetchRequest(fetchRequest) as! [[String:NSDate]]
print("results \(results)")
dates = results.flatMap { $0["savedTime"] }
I'm worried though that even the above, although it compiles and runs, might not be what you really need. The array of dictionaries is a rather odd thing to be pulling out of a managedObjectContext...

Cannot convert return expression of type 'Bool' to return type 'Bool'

Here's a special kind of nonsense that's left me curious. I figured a screenshot would be the easiest way to show what's happening.
This error seems to be total nonsense. I DO see the distinction "return expression" and "return type" in the error but I don't know if that's meaningful or just bad language choices by the developers.
Can anyone tell me what's going on here?
EDIT:
As requested, I created a small test case in an isolated Swift file as such:
func TestFunc(item: AnyObject) -> Bool {
if !(item[1] as! Bool){
return (item[2] as! Array).count > 0
}
return false
}
Which gave the same error. Following ColGraff's suggestion, of providing a type to the array, I changed the code to:
func TestFunc(item: AnyObject) -> Bool {
if !(item[1] as! Bool){
return (item[2] as! Array<AnyObject>).count > 0
}
return false
}
Which did remove the error message but none of the responses so far have answered the original question, as to what the error message actually means.
Your problem stems from the code
item[2] as! Array
You need to specify the type of element in the array. I'm assuming it's AnyObject so you should do:
return (item[2] as! Array<AnyObject>).count > 0
However, there are many errors in this code and also a lot of anti-patterns. One major one is you are using the forced unwrapping operator, !. Avoid using it at all, if you can. You should be testing each Optional and handling if it's set or unset.
guard let item2 = item[2] as? [AnyObject] else { return false }
return !item2.isEmpty
This question was more difficult to answer than it should have been because you didn't post the code in a Minimal, Complete, and Verifiable manner. Screenshots are not very useful because the answerer has to re-type your code, taking up time and possibly introducing errors. Not to mention that much of the code surrounding your error has to be reproduced before any debugging can occur. You'll get much more useful information out of this site if you make it easier for people to answer your questions.

Weird Memory Behavior in Swift

OK... so I have no idea why this happens but:
Compare the following two lines:
let pointCurve: [AnyObject] = self.curve.map{NSValue(point:$0)}
and
let pointCurve: [NSPoint] = self.curve.map{$0}
In either case, the variable is local and not used at all after assignment. The line resides in a method that is called repeatedly and very quickly. The first case results in terrible and ever faster growing of memory usage. But when I change it to the second, the memory stats are flat as a disc.
You may say, "oh, you're not doing anything in the second line". So I tried the following:
var pointCurve: [AnyObject] = []
for c in self.curve {
pointCurve.append(NSValue(point:NSPoint(x:1, y:1))
}
vs
var pointCurve: [NSPoint] = []
for c in self.curve {
pointCurve.append(NSPoint(x: 1, y: 1))
}
Now I see the exact same results. The culprit seems to be NSValue. I checked with Instruments that a whole bunch of NSConcreteValues are allocated, and I read online these are related to NSValue. But I didn't find anything about them causing memory leaks.
The question is what can I do about this. I'm supposed to send an array of points to some ObjC code, and until I figure out how to fix this, I can't do it without huge performance issues.
Try:
func pointCurvy() {
autoreleasepool {
let pointCurve: [AnyObject] = self.curve.map{NSValue(point:$0)}
// Do something with pointCurve.
}
}