0

Swift 4 / Xcode 9.3 / OS X 10.13.4 / iOS 11.3 & 11.2.6

I'm trying to build my app and I'm getting the above error message. I've checked the code over and over and over and I can't figure out why I'm getting this error. I'm not certain which part of the code you need to see, but here is the page I'm getting the error on. The error code is flagging the very last line of code.

import UIKit
import os.log

class Bonus: NSObject, NSCoding {

    //MARK: Archiving Paths
    static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("bonuses")

    //MARK: Properties
    var bonusCode: String
    var category: String
    var name: String
    var value: Int
    var city: String
    var state: String
    var photo: UIImage?

    //MARK: Initialization
    init?(bonusCode: String, category: String, name: String, value: Int, city: String, state: String, photo: UIImage?) {

        // The name must not be empty.
        guard !name.isEmpty else {
            return nil
        }

        // The value must not be negative.
        guard (value >= 0) else {
            return nil
        }

        // Initialize stored properties.
        self.bonusCode = bonusCode
        self.category = category
        self.name = name
        self.value = value
        self.city = city
        self.state = state
        self.photo = photo
    }

    //MARK: Types
    struct  PropertyKey {
        static let bonusCode = "bonusCode"
        static let category = "category"
        static let name = "name"
        static let value = "value"
        static let city = "city"
        static let state = "state"
        static let photo = "photo"
    }

    //MARK: NSCoding
    func encode(with aCoder: NSCoder) {
        aCoder.encode(bonusCode, forKey: PropertyKey.bonusCode)
        aCoder.encode(category, forKey: PropertyKey.category)
        aCoder.encode(name, forKey: PropertyKey.name)
        aCoder.encode(value, forKey: PropertyKey.value)
        aCoder.encode(city, forKey: PropertyKey.city)
        aCoder.encode(state, forKey: PropertyKey.state)
        aCoder.encode(photo, forKey: PropertyKey.photo)
    }

    required convenience init?(coder aDecoder: NSCoder) {
        // The name is required. If we cannot decode a name string, the initializer should fail.
        guard let bonusCode = aDecoder.decodeObject(forKey: PropertyKey.bonusCode) as? String else {
            os_log("Unable to decode the Code for a Bonus object.", log: OSLog.default, type: .debug)
            return nil
        }

        // Because photo is an optional property of Meal, just use conditional cast
        let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage

        let category = aDecoder.decodeObject(forKey: PropertyKey.category)
        let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
        let city = aDecoder.decodeObject(forKey: PropertyKey.city)
        let state = aDecoder.decodeObject(forKey: PropertyKey.state)

        // Must call designated initializer.
        self.init(bonusCode: String, category: String, name: String, value: Int, city: String, state: String, photo: UIImage?)
    }
}

The error is flagging on the bonusCode: String, specifically on the S in String.

I'm pretty new to programming, but I only found one other search result for this specific question, and the other similar ones seemed to be very specific to the code being used.

1
  • I should add that the code had no errors until I added the bonusCode, so I suspect I missed something somewhere but can't figure out where. Commented Apr 10, 2018 at 8:58

2 Answers 2

1

You have to pass the decoded values rather than the types in the last line and the line to decode the name is missing and you have to cast the other string objects. The force unwrapping is safe because all non-optional values are encoded properly.

let name = aDecoder.decodeObject(forKey: PropertyKey.name) as! String

let category = aDecoder.decodeObject(forKey: PropertyKey.category) as! String
let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
let city = aDecoder.decodeObject(forKey: PropertyKey.city) as! String
let state = aDecoder.decodeObject(forKey: PropertyKey.state) as! String

...

self.init(bonusCode: bonusCode, category: category, name: name, value: value, city: city, state: state, photo: photo)
Sign up to request clarification or add additional context in comments.

5 Comments

Changing the last line like you suggested seems to have resolved it, though I had to add the forces in the self.init. Not sure why it made me do that, but it builds and compiles now.
@vadian Regarding the force-unwrap: What if his bonuses file misses some key or is of a different type? Well... it is encoded here properly so if it goes through this class, it will save in the proper format but what if it had to start with a file, like a save state kind of thing and that file is a little messed up. Just saying, it's too early to decide on the force-unwrap.
All properties except photo are declared as non-optional so in the designated init method they must have a value. It's the responsibility of the developer to eliminate these kind of design-errors like a little messed up file
Right now I'm using sample data, so I'm forcing the defaults. I plan on expanding it to being a JSON based file for easier management. Knowing I plan on leaving the sample data in there, does that change how I should set the optionals? I'd rather learn it right than easy.
Don't use optionals as a don't care alibi. The strong type system of Swift encourages you to use non-optional types as much as possible, a non-optional can never cause a crash.
1
self.init(bonusCode: String,
          category: String,
          name: String,
          value: Int,
          city: String,
          state: String,
          photo: UIImage?)

This is a function call, not a function declaration.
You are passing types instead of values into a function call.

You should be doing this instead:

self.init(bonusCode: bonusCode,
          category: category,
          name: name,
          value: value,
          city: city,
          state: state,
          photo: photo)

So finally, your init should look like (with a little improvement):

required convenience init?(coder aDecoder: NSCoder) {
    //NOTE: `decodeObject(forKey:)` returns optional `Any` and hence all those `as? String`

    //name was missing
    let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String

    let bonusCode = aDecoder.decodeObject(forKey: PropertyKey.bonusCode) as? String
    let category = aDecoder.decodeObject(forKey: PropertyKey.category) as? String
    let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
    let city = aDecoder.decodeObject(forKey: PropertyKey.city) as? String
    let state = aDecoder.decodeObject(forKey: PropertyKey.state) as? String
    let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage

    /*
     Only photo is optional in order to init but the rest are required
     Hence the optional binding for the rest below
    */
    if let name = name,
        let bonusCode = bonusCode,
        let category = category,
        let city = city,
        let state = state {
        // Must call designated initializer.
        self.init(bonusCode: bonusCode,
                  category: category,
                  name: name,
                  value: value,
                  city: city,
                  state: state,
                  photo: photo)
    }
    else {
        /*
         Some required object/s were missing so we can't call the
         designated initializer unless we want to give default values.

         Hence return nil
         */
        return nil
    }
}

2 Comments

I changed it similar to how you stated, and it returned a bunch of errors. I changed the self.init back to using types and it seemed to work. I then just add to force the unwrap (I think that is the correct term) like vadian suggested above. It now compiles and runs.
@DJFriar Just curious, what errors did you get? Compile-time errors or Run-time? For me, I don't get any compile-time errors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.