Eugleo
Eugleo

Reputation: 428

NSCoding in Swift - decoding an array of class objects throws error

Here is my code for a Game object. It has plenty of different properties, one of which is an array of Cards. When I try to use NSCoding to save my game objects, XCode throws this error

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Hearthstone_Tracker.Card encodeWithCoder:]: unrecognized selector sent to instance 0x7fbca9db9750'

From what I've read, these errors happen oftentimes when an UIButton calls a function and passes itself as an argument when the function doesn't expect it or vice versa. I don't see how it's applicable to my case though.

I've read NSCoding has some problem with structs, but as Cards is actually a class, I don't think the problem lies there either.

Here is my Game class:

import Foundation
import UIKit

class Game: NSObject, NSCoding {

// MARK: Properties

let playerHero: String
let playerDeck: String
let playerImage: UIImage?

let opponentHero: String
let opponentDeck: String
let opponentImage: UIImage?

let result: String
let coin: Bool
var date: String?
let mode: String
let rank: String?
let duration: Int?
let durationSec: Int?
let id: Int

let cardsPlayed: [Card]

var manaEfficiency: (Double, Double)?
var turnCount: Int?
var estimatedTurnLength: Int?
var finisher: String?

// MARK: Keys for the data

struct PropertyKey {
    static let playerHero = "playerHero"
    static let playerDeck = "playerDeck"
    static let playerImage = "playerImage"

    static let opponentHero = "opponentHero"
    static let opponentDeck = "opponentDeck"
    static let opponentImage = "opponntImage"

    static let result = "result"
    static let coin = "coin"
    static let date = "date"
    static let mode = "mode"
    static let rank = "rank"
    static let duration = "duration"
    static let durationSec = "durationSec"
    static let id = "id"

    static let cardsPlayed = "cardsPlayed"
}

// MARK: Archiving Paths

static let DocumentsDirectory: AnyObject = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("games")

// MARK: Initializer

init(playerHero: String, playerDeck: String, opponentHero: String, opponentDeck: String, result: String, coin: Bool, date: String, mode: String, rank: String, duration: Int?, durationSec: Int?, id: Int, cardsPlayed: [Card]) {
    // ...
    // Some initializer code
    // ...  

    self.cardsPlayed = cardsPlayed

    super.init()
}

// MARK: NSCoding

func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(playerHero, forKey: PropertyKey.playerHero)
    aCoder.encodeObject(playerDeck, forKey: PropertyKey.playerDeck)

    aCoder.encodeObject(opponentHero, forKey: PropertyKey.opponentHero)
    aCoder.encodeObject(opponentDeck, forKey: PropertyKey.opponentDeck)

    aCoder.encodeObject(result, forKey: PropertyKey.result)
    aCoder.encodeBool(coin, forKey: PropertyKey.coin)
    aCoder.encodeObject(date, forKey: PropertyKey.date)
    aCoder.encodeObject(mode, forKey: PropertyKey.mode)
    aCoder.encodeObject(rank, forKey: PropertyKey.rank)
    aCoder.encodeObject(duration, forKey: PropertyKey.duration)
    aCoder.encodeObject(durationSec, forKey: PropertyKey.durationSec)
    aCoder.encodeObject(id, forKey: PropertyKey.id)

    aCoder.encodeObject(cardsPlayed, forKey: PropertyKey.cardsPlayed)
}

required convenience init?(coder aDecoder: NSCoder) {
    let playerHero = aDecoder.decodeObjectForKey(PropertyKey.playerHero) as! String
    let playerDeck = aDecoder.decodeObjectForKey(PropertyKey.playerDeck) as! String

    let opponentHero = aDecoder.decodeObjectForKey(PropertyKey.opponentHero) as! String
    let opponentDeck = aDecoder.decodeObjectForKey(PropertyKey.opponentDeck) as! String

    let result = aDecoder.decodeObjectForKey(PropertyKey.result) as! String
    let coin = aDecoder.decodeBoolForKey(PropertyKey.coin)
    let date = aDecoder.decodeObjectForKey(PropertyKey.date) as! String
    let mode = aDecoder.decodeObjectForKey(PropertyKey.mode) as! String
    let rank = aDecoder.decodeObjectForKey(PropertyKey.rank) as! String
    let duration = aDecoder.decodeObjectForKey(PropertyKey.duration) as? Int
    let durationSec = aDecoder.decodeObjectForKey(PropertyKey.durationSec) as? Int
    let id = aDecoder.decodeObjectForKey(PropertyKey.id) as! Int

    let cardsPlayed = aDecoder.decodeObjectForKey(PropertyKey.cardsPlayed) as! [Card]

    self.init(playerHero: playerHero, playerDeck: playerDeck, opponentHero: opponentHero, opponentDeck: opponentDeck, result: result, coin: coin, date: date, mode: mode, rank: rank, duration: duration, durationSec: durationSec, id: id, cardsPlayed: cardsPlayed)
}

And here is Card class:

import Foundation
import UIKit

class Card: NSObject {

// MARK: Properties

let name: String
let manacost: Int
let turn: Int
let player: String

// MARK: Initialzer

init(name: String, manacost: Int, turn: Int, player: String) {
    self.name = name
    self.manacost = manacost
    self.turn = turn
    self.player = player

    super.init()
}

}

Upvotes: 0

Views: 471

Answers (1)

Hugo Alonso
Hugo Alonso

Reputation: 6824

Every object (and its internal hierarchy of subobjects) to be able to be stored in NSDefaults must be able to serialise itself ...that is.. must conform to NSCoding.

Upvotes: 2

Related Questions