In Swift, I have a custom NSError, I need to get the error userInfo dictionary and add things later, but it is nil in the assign line, but then error.userInfo have an object...
With error.userInfo as nil:
class MyError: NSError {
init(error: NSError) {
var newUserInfo = error.userInfo
...newUserInfo is nil...
super.init(...)
}
}
If I assign it 2 times it works ( I know there's something missing but what?)
init(error: NSError) {
var newUserInfo = error.userInfo
newUserInfo = error.userInfo
...newUserInfo now contains a dictionary...
}
Why?
This looks maybe compiler bug-ey to me, but it's hard to tell without seeing more of your code. At any rate, this sort of thing is easier to debug if you use conditional cast. userInfo in swift is a Dictionary<NSObject: AnyObject>?; if you're getting this from a Cocoa API you can do something like:
if let userInfo = error.userInfo as? [NSObject: NSObject] {
// modify and assign values as necessary
}
this will at least make it clearer where things are breaking.
Related
I am a fan of the guard statements using Swift.
One thing I haven't fully understand is how (or even if) to use it inside a function that expect return value.
Simple example:
func refreshAudioMix() -> AVPlayerItem? {
guard let originalAsset = rootNC.lastAssetLoaded else {
return nil
}
let asset = originalAsset.copy() as! AVAsset
..... return AVPlayerItem ....
}
The issue with this approach is that I need to check the returned value each time. I am trying to understand if am I approaching this correctly or maybe even guard not needed here at all.
Thank you!
I'd say the use of guard isn't wrong. When the objects you're manipulating have a probability of being nil, it seems fair that you return an optional value.
There's one other way (at least, but I don't see others right now) to handle that: write that your function can throw an error and throw it when you find nil in an optional value in a guard statement. You can even create errors so it's easily readable. You can read more about it here
sample :
enum CustomError: Error {
case errorOne
case errorTwo
case errorThree
}
func refreshAudioMix() throws -> AVPlayerItem {
guard let originalAsset = rootNC.lastAssetLoaded else {
throw CustomError.errorOne
}
let asset = originalAsset.copy() as! AVAsset
..... return AVPlayerItem ....
}
I am trying to create sort of a generic wrapper for simple core data fetches.
What I wanted to achieve, is, instead of writing multiple redundant methods that look like this:
func loadNSMOSubclass() -> [NSMOSubclass] {
let fetchRequest: NSFetchRequest<NSMOSubclass> = NSMOSubclass.fetchRequest()
do {
let result = try mainContext.fetch(fetchRequest)
return result
}
catch {
return []
}
}
I thought I could create a generic helper for that:
struct EntityLoader<T> where T: NSManagedObject {
func loadEntity() -> [T] {
let fetchRequest: NSFetchRequest<T> = T.fetchRequest()
do {
let mainContext = CoreDataState().mainContext
let result = try mainContext.fetch(fetchRequest)
return result
}
catch {
return []
}
}
}
However, at that point the compiler has a weird error:
Cannot convert value of type NSFetchRequest<NSFetchRequestResult> to specified type NSFetchRequest<T>
where the suggested solution is even stranger, since everything compiles when I do a casting:
let fetchRequest: NSFetchRequest<T> = T.fetchRequest() as! NSFetchRequest<T>
That might be ugly, but I could live with that. However when I run this code I am getting a fatal error:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'executeFetchRequest:error:
<null> is not a valid NSFetchRequest.'
Am I doing something wrong, or are these just some Swift's constraints that are currently impossible to surpass?
Problem here is fetchRequest() is a auto-generated helper function and it's only available for your NSManagedObject sub classes. It's not available for your generic type. Therefore, instead of using that function you have to use it's implementation with your generic type like this,
replace this line,
let fetchRequest: NSFetchRequest<T> = T.fetchRequest()
with this,
let fetchRequest: NSFetchRequest<T> = NSFetchRequest<T>(entityName: String(describing: T.self))
So I'm new to core data, starting doing some tutorials and got a very basic setup where a 10 scores are saved from the AppDelegate and that works fine (am able to fetch and print from the console).
Then when I want to use a fetchrequest in another VC to retrieve the data which has been saved successfully, I get the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
Here's the code to retrieve the data:
import Foundation
import CoreData
import UIKit
class testVC: UIViewController {
var managedObjectContext: NSManagedObjectContext!
var scores = [Scores]()
#IBOutlet weak var retrieveDataLabel: UIButton!
#IBOutlet weak var saveLabel: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func retrieveData(sender: AnyObject) {
let fetchRequest = NSFetchRequest(entityName: "Scores")
do {
if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [Scores]{
scores = results
for result in results {
if let gameScore = result.valueForKey("gameScore") as? Int{
print("Your score is \(gameScore)")
}
}
}
}catch{
print("Error fetching data")
}}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Since I'm using if let .. shouldn't I be getting "Error fetching data"? instead of getting the fatal error? How come I can retrieve the data when loading the app but not when I try to retrieve the data in a different VC using the same code?
I've looked at other questions relating to fatal error found nil while unwrapping an optional value:
What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?
fatal error: unexpectedly found nil while unwrapping an Optional value
and it seems that I'm basically trying to retrieve something which isn't there, but the data is saved indeed and I can retrieve it in the console when starting the app. Using the same fetch request I'd expect to get identical results when I load the app and retrieve the data but that's not the case.
What am I missing here? It feels like it should be very basic but for some reason just cannot get it to work after trying for 2 days.
app delegate code to save and retrieve data:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
//addTestData()
let fetchRequest = NSFetchRequest(entityName: "Scores")
do {
if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]{
for result in results {
if let gameScore = result.valueForKey("gameScore") as? Int{
print("Your score is \(gameScore)")
}
}
}
}catch{
print("Error fetching data")
}
return true
}
func addTestData(){
guard let entity = NSEntityDescription.entityForName("Scores", inManagedObjectContext: managedObjectContext) else{
fatalError("Could not find entity description")
}
for i in 1...10{
let score = Scores(entity: entity, insertIntoManagedObjectContext: managedObjectContext)
score.gameScore = i
}
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
Any help would be greatly appreciated!
When you use if let, you are checking to see if it is possible to make the result as an Int. You want to see if that result exists first. Try this:
for result in results {
if let gameScore = result.valueForKey("gameScore") {
print("Your score is \(gameScore as! Int)")
}
}
The issue is the nil NSManagedObjectContext (declared but no value)
By definition executeFetchRequest returns a non-optional array on success and since you are supposed to know from the Core Data model that the entity Scores returns always Scores objects you can safely write
do {
scores = try managedObjectContext.executeFetchRequest(fetchRequest) as! [Scores]
And obviously using a NSManagedObject subclass use the property directly to avoid the type casting. You need the optional binding only if the property gameScore is declared as optional.
for result in results {
if let gameScore = result.gameScore {
print("Your score is \(gameScore)")
}
However for a Int value like a score a non-optional is more reasonable, than you can reduce the code to
for result in results {
print("Your score is \(result.gameScore)")
}
So I did manage to get it working, using the following for managedObjectContext:
let managedContext = AppDelegate().managedObjectContext
I'm still puzzled why I didn't get the error which was specified in the catch but at least it's working now.
Thanks for your answers!
How should errors related to NSCoding be handled in Swift?
When an object is initialized using init?(coder:) it may fail to be initialized if the data is invalid. I'd like to catch these errors and appropriately handle them. Why is init?(coder:) not defined as a throwing function in Swift?
NSCoding defines it as Optional:
init?(coder aDecoder: NSCoder)
So it is certainly possible to detect errors.
90% of "why does Swift...?" questions can be answered with "because Cocoa." Cocoa does not define initWithCoder: as returning an error, so it does not translate to throws in Swift. There would be no way to cleanly bridge it to existing code. (NSCoding goes back NeXTSTEP. We've built a lot of software without returning an NSError there. Doesn't mean it might not be nice sometimes, but "couldn't init" has traditionally been enough.)
Check for nil. That means that something failed. That is all the information that is provided.
I've never in practice had to check too deeply that the entire object graph was correct. If it isn't, you're incredibly likely to get other errors anyway, and remember that NSKeyedUnarchiver will raise an ObjC exception (!!!) if it fails to decode. Unless you wrap this in an ObjC #catch, you're going to crash anyway. (And yes, that's pretty crazy, but still true.)
But if I wanted to be extremely careful and make sure that things I expected to be in the archive were really in the archive (even if they were nil), I might do it this way (untested; it compiles but I haven't made sure it really works):
import Foundation
enum DecodeError: ErrorType {
case MissingProperty(String)
case MalformedProperty(String)
}
extension NSCoder {
func encodeOptionalObject(obj: AnyObject?, forKey key: String) {
let data = obj.map{ NSKeyedArchiver.archivedDataWithRootObject($0) } ?? NSData()
self.encodeObject(data, forKey: key)
}
func decodeRequiredOptionalObjectForKey(key: String) throws -> AnyObject? {
guard let any = self.decodeObjectForKey(key) else {
throw DecodeError.MissingProperty(key)
}
guard let data = any as? NSData else {
throw DecodeError.MalformedProperty(key)
}
if data.length == 0 {
return nil // Found nil
}
// But remember, this will raise an ObjC exception if it's malformed data!
guard let prop = NSKeyedUnarchiver.unarchiveObjectWithData(data) else {
throw DecodeError.MalformedProperty(key)
}
return prop
}
}
class MyClass: NSObject, NSCoding {
static let propertyKey = "property"
let property: String?
init(property: String?) {
self.property = property
}
required init?(coder aDecoder: NSCoder) {
do {
property = try aDecoder.decodeRequiredOptionalObjectForKey(MyClass.propertyKey) as? String
} catch {
// do something with error if you want
property = nil
super.init()
return nil
}
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeOptionalObject(property, forKey: MyClass.propertyKey)
}
}
As I said, I've never actually done this in a Cocoa program. If anything were really corrupted in the archive, you're almost certainly going to wind up raising an ObjC exception, so all this error checking is likely overkill. But it does let you distinguish between "nil" and "not in the archive." I just encode the property individually as an NSData and then encode the NSData. If it's nil, I encode an empty NSData.
What's the difference between these two declarations? Which one is better? Why?
... error = some NSError ...
1.
var newUserInfo: [NSObject: NSObject] = [:]
if let tempUserInfo = error.userInfo as? [NSObject: NSObject] {
newUserInfo = tempUserInfo
}
2.
var newUserInfo: [NSObject: NSObject]
if let tempUserInfo = error.userInfo as? [NSObject: NSObject] {
newUserInfo = tempUserInfo
} else {
newUserInfo = [:]
}
As of Swift 1.2, you can now use let with deferred assignment so you can use your if/else version:
let newUserInfo: [NSObject: NSObject]
if let tempUserInfo = error.userInfo as? [NSObject: NSObject] {
newUserInfo = tempUserInfo
} else {
newUserInfo = [:]
}
However, option 1 will not work, since there is a path where newUserInfo may not be set.
(note, as of 1.2b1, this doesn't work with global variables, only member and local variables, in case you try this out in a playground)
Alternatively, you could use the nil-coalescing operator to do it in one go, like this:
let newUserInfo = (error.userInfo as? [NSObject:NSObject]) ?? [:]
edit: Swift 1.2 added deferred assignment of let, enabling option 2 to be used with let now, but also changed the precedence of as? vs ??, requiring parens.
Pre-1.2 answer in case you have similar code you need to migrate:
Neither are particularly appealing if you ask me. In both cases, you have to have to declare newUserInfo with var, because you're not declaring and assigning it in one go.
I'd suggest:
let newUserInfo = error.userInfo as? [NSObject:NSObject] ?? [:]
In 1. newUserInfo is assigned twice if the if branch is executed. 2. is better in terms of performance
In 1. it's clearly visible that newUserInfo is initialized as an empty array. 2. makes the code less readable because you have to browse the code to know if it has a default value
If newUserInfo can be set in several places (such as if it can be initialized it in several if statements), you should duplicate the else branch in 2., so 1. looks better
So: in solution no. 1 code is more readable, but solution no. 2 is slightly more performant.
Besides using #AirspeedVelocity solution (which is better than yours, no offence :)), I'd rather prefer to use an optional, setting newUserInfo to nil to indicate absence of value - after all, that's what optionals are for, isn't it? But of course that depends on your specific needs.
My preferred pattern for this is:
struct TryUnrap<T> {
typealias Tryee = () -> T?
typealias Catchee = () -> T
private var tryee: Tryee
init(tryee: Tryee) {
self.tryee = tryee
}
func catch(catchee: Catchee) -> T {
if let result = tryee() {
return result
}
return catchee()
}
}
let error: NSError? = NSError()
let newUserInfo = TryUnrap {
error?.userInfo as? [NSObject : NSObject]
}.catch {
[:]
}
println("newUserInfo = \(newUserInfo)")
I prefer the above because I find it reads better for me. Also see answer 4 of Error-Handling in Swift-Language for general error handling using the same pattern.