NSCoder crash on decodeBool forKey (Xcode 8, Swift 3) - xcode

I have this simple class
import UIKit
class SimpleModel: NSObject, NSCoding {
var name : String!
var done : Bool!
init(name:String) {
self.name = name
self.done = false
}
internal required init?(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.done = aDecoder.decodeBool(forKey: "done") // BUG HERE
}
func encode(with encoder: NSCoder) {
encoder.encode(self.name, forKey: "name")
encoder.encode(self.done, forKey: "done")
}
}
the save code:
let data = NSKeyedArchiver.archivedData(withRootObject: storageArray)
UserDefaults.standard.set(data, forKey: "storage")
UserDefaults.standard.synchronize()
the read code:
if let data = UserDefaults.standard.data(forKey: "storage") {
storageArray = NSKeyedUnarchiver.unarchiveObject(with: data) as! [SimpleModel]
}
the problem occurs when the NSKeyedUnarchiver does it's job. I can not understand where the problem comes from.
Thanks!

the trick is remove ! form the primitive types.
If you put ! you are saying "make an implicit-unwrapped optional" so the encoder will archive as NSNumber instead of Bool (or Int, Double).
If you remove ! the encoder will archive as Bool and things works as expected (I spent an "incident" and this solution is provided by Apple)

Bool and Int have new methods:
self.x = Int(decoder.decodeCInt(forKey: "Speed"))
self.y = decoder.decodeObject(forKey: "Serial") as! String
self.z = Bool(decoder.decodeBool(forKey: "Direction") )

I had the same Problem.
Try this:
self.done = aDecoder.decodeObject(forKey: "done") as? Bool ?? aDecoder.decodeBool(forKey: "done")

Related

self is immutable

I have a class, Task, with a 'count' property. The end goal is to increase the count by 1.
In a playground, this is working:
class Task: NSObject {
var name: String
var count: Int
init(name:String, count:Int) {
self.name = name
self.count = count
}
}
var test = Task(name: "test", count: 0)
test.count += 1
// => 1
But in my actual project, it's getting this error:
Left side of mutating operator isn't mutable: 'self' is immutable
In my ViewController, I have an #IBAction function that I'd like to trigger the count increase, so that's where the error is occurring. Here's a trimmed down version:
#IBAction func addTask(sender: AnyObject) {
currentTask = newTaskInput.stringValue
let x = Task(name: currentTask, count: 0)
currentTaskObj = x
// error occurs here
currentTaskObj!.count += 1
}
Any idea how to fix this? I must be missing something because it's working in a playground exactly how I'd like. Huge thanks in advance!
Edit: here's the actual class from my project (I changed sessions to count for the examples above):
class Task: NSObject, NSCoding {
var name: String
var sessions: Int
var time: String = ""
init(name:String, sessions:Int, time:String? = "") {
self.name = name
self.sessions = sessions
self.time = time!
}
required convenience init(coder aDecoder: NSCoder) {
var name = aDecoder.decodeObjectForKey("name") as! String
var sessions = aDecoder.decodeObjectForKey("sessions") as! Int
var time = aDecoder.decodeObjectForKey("time") as! String
self.init(name: name, sessions: sessions, time: time)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: "name")
aCoder.encodeObject(self.sessions, forKey: "sessions")
aCoder.encodeObject(self.time, forKey: "time")
}
}

using NSUserDefaults to save an array of objects

I am new to swift, and I am trying to build a game which will include a top 5 players name and score, if i just add the players to an array and than restart the game it "deletes" the players, so i am trying to use NSUserDefaults(i need to store just 5 strings and 5 integers), it does not work no matter what,
the code is:
class scoreController: UITableViewController {
var playersArray:[Player] = [Player]()
var nameFromGame = "" //name from game vc
var timeFromGame = 0 //time from game vc
let tmpPlayer = Player(playerName: "", scoreTime: 0)
let playerDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let player1 = Player(playerName: "Bil", scoreTime: 50)
let player2 = Player(playerName: "Bob", scoreTime: 100)
playersArray.append(player1)
playersArray.append(player2)
tmpPlayer.playerName = nameFromGame
tmpPlayer.scoreTime = timeFromGame
playersArray.append(tmpPlayer)
playerDefaults.setObject(playersArray[11], forKey: "players")
print(playersArray )
}
}
I am just trying to save this for now and it crashes, does anyone know why? and also how can i store this in my app so it will save the data?
thank you!
Your player objects probably are the problem here. You must implement two methods in your player class (I am no swift master but it's probaby the same mistake):
- (void)encodeWithCoder:(NSCoder *)encoder;
- (id)initWithCoder:(NSCoder *)decoder;
That should work for you.
Hope it helps!!
PD: check this answer
How to store custom objects in NSUserDefaults
Your Player class needs to conform to NSCoding, and you'll need to store an archived data of your players array and unarchive it when extracting out the data.
Class:
class Player: NSObject, NSCoding {
var playerName: String
var scoreTime: Int
init(playerName: String, scoreTime: Int) {
self.playerName = playerName
self.scoreTime = scoreTime
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(scoreTime, forKey: "score_time")
aCoder.encodeObject(playerName, forKey: "player_name")
}
required convenience init?(coder decoder: NSCoder) {
guard let playerName = decoder.decodeObjectForKey("player_name") as? String else {
return nil
}
self.init(playerName: playerName, scoreTime: decoder.decodeIntegerForKey("score_time"))
}
}
NSUserdefaults & Archiving/Unarchiving :
let player1 = Player(playerName: "Bil", scoreTime: 50)
let player2 = Player(playerName: "Bob", scoreTime: 100)
let playersArray = [player1, player2]
let playersData = NSKeyedArchiver.archivedDataWithRootObject(playersArray)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(playersData, forKey: "players")
defaults.synchronize()
if let playersArrayData = defaults.objectForKey("players") as? NSData {
let unarchivedPlayers = NSKeyedUnarchiver.unarchiveObjectWithData(playersArrayData) as! [Player]
print(unarchivedPlayers)
}
Hope this helps, please remember to choose answer and up-vote if this solves your question.
You need to could call
playerDefaults.synchronize()
to save the data in the defaults.
EDIT
As facumenzella says, your other problem is storing custom class objects in the defaults. To solve that you need to implement methods in the custom Player class that tell it how to be converted to data. Then you should serialize the object before you store it in the defaults.

How to fix `Ambiguous use of 'subscript'` every time?

I'm using this class that was written in Swift 1.2 and now I want to use it with Swift 2.0.
I get an error: Ambiguous use of 'subscript' # let artist = result["name"] as! String
} else if let jsonArtists = jsonResult["artists"] as? NSDictionary {
if let results:NSArray = jsonArtists["items"] as? NSArray {
dispatch_async(dispatch_get_main_queue(), {
self.searching = false
var suggestionResults: [spotifySearchResult] = []
for result in results {
let artist = result["name"] as! String
var sugresult = spotifySearchResult()
sugresult.artist = artist
if !suggestionResults.contains(sugresult) {
suggestionResults.append(sugresult)
}
}
handler(suggestionResults)
})
}
}
}
I tried different fixes such as let artist = (result["name"] as! String) or let artist = (result["name"] as! String) as! String
But nothing worked.
I know that the question was already post 4 times but, I can't find anyone explaining how to fix it in every case, only case by case.
Can someone explain me how to investigate to fix it? Not just only a fix for my case. I would prefer fix it by myself with your hints!
BTW what does mean subscript? Is subscript the thing between quotation mark? IMHO the error message is a bit vague...
EDIT:
class spotifySearchResult : NSObject {
var artist=""
var track=""
var duration=0
var spotifyURL = NSURL()
var spotifyURI = NSURL()
override func isEqual(theObject: AnyObject?) -> Bool {
if let myObject = theObject as? spotifySearchResult {
return (myObject.artist.uppercaseString == self.artist.uppercaseString && myObject.track.uppercaseString == self.track.uppercaseString)
}
return false
}
}
Subscription means to use the shorter syntax item["key"] for item.objectForKey["key"]
results seems to be an array of dictionaries so I suggest to cast down to a more specific type
if let results = jsonArtists["items"] as? [[String:AnyObject]] {
or even, if all values are guaranteed to be strings
if let results = jsonArtists["items"] as? [[String:String]] {

transfer array of custom objects with WatchConnectivity / encoding in Swift

Am trying to transfer an array of custom objects from iOS to watchkitextension.
Understood that in order to do so, data needs to be encoded. Am though getting error when decoding.
Here we go:
The custom object:
final class Person: NSObject {
var PersonName:String = ""
var PersonAge:Int = 0
var joined:NSDate = NSDate()
init(PersonName: String, PersonAge:Int, joined:NSDate){
self.PersonName = PersonName
self.PersonAge = PersonAge
self.joined = joined
super.init()
}
}
extension Person: NSCoding {
private struct CodingKeys {
static let PersonName = "PersonName"
static let PersonAge = "PersonAge"
static let joined = "joined"
}
convenience init(coder aDecoder: NSCoder) {
let PersonName = aDecoder.decodeObjectForKey(CodingKeys.PersonName) as! String
let PersonAge = aDecoder.decodeIntForKey(CodingKeys.PersonAge) as! Int
let joined = aDecoder.decodeDoubleForKey(CodingKeys.joined) as! NSDate
self.init(PersonName: PersonName, PersonAge: PersonAge, joined: joined)
}
func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(PersonName, forKey: CodingKeys.PersonName)
encoder.encodeObject(PersonAge, forKey: CodingKeys.PersonAge)
encoder.encodeObject(joined, forKey: CodingKeys.joined)
}
}
The class with the array:
#objc(Group)
final class Group: NSObject {
static let sharedInstance = Group()
var Persons:[Person] = []
required override init() {
super.init()
}
init (Persons:[Person]){
self.Persons = Persons
super.init()
}
}
extension Group: NSCoding {
private struct CodingKeys {
static let Persons = "Persons"
}
convenience init(coder aDecoder: NSCoder) {
let Persons = aDecoder.decodeObjectForKey(CodingKeys.Persons) as! [Person]
self.init(Persons: Persons)
self.Persons = aDecoder.decodeObjectForKey(CodingKeys.Persons) as! [Person]
}
func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(Persons, forKey: CodingKeys.Persons)
}
}
Creating example object, append to array, then encode:
let aPerson:Person? = Person(PersonName: "Martin", PersonAge: 50, joined: NSDate())
Group.sharedInstance.Persons.append(aPerson!)
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(Group.sharedInstance)
And here I get the error "execution was interrupted - reason signal SIGABRT"
let decodedData = NSKeyedUnarchiver.unarchiveObjectWithData(encodedData) as? Group
Try changing these sections to:
convenience init(coder aDecoder: NSCoder) {
let PersonName = aDecoder.decodeObjectForKey(CodingKeys.PersonName) as! String
let PersonAge = aDecoder.decodeIntForKey(CodingKeys.PersonAge) as! Int
let joined = aDecoder.decodeObjectForKey(CodingKeys.joined) as! NSDate
self.init(PersonName: PersonName, PersonAge: PersonAge, joined: joined)
}
func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(PersonName, forKey: CodingKeys.PersonName)
encoder.encodeInt(PersonAge, forKey: CodingKeys.PersonAge)
encoder.encodeObject(joined, forKey: CodingKeys.joined)
}
So that the serialization matches the deserialization, and the types desired by the Person class. That said, the WWDC talk on WatchConnectivity specifically recommended not to use NSKeyedArchiver as it is not a very space efficient serialization method.

How to unarchive custom array with NSUserDefaults?

I'm saving an array of type ClassA to NSUserDefaults. ClassA look like this:
class ClassA :NSObject, NSCoding{
init (descriptionParam: String) {
self.description = descriptionParam
}
var description: String?
required init(coder aDecoder: NSCoder) {
if let description = aDecoder.decodeObjectForKey("description") as? String {
self.description = description
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let description = self.description {
aCoder.encodeObject(description, forKey: "description")
}
}
}
This is how I'm saving the array to NSUserDefaults:
let myData = NSKeyedArchiver.archivedDataWithRootObject(ClassAManager.classa_array)
userDefaults.setObject(myData, forKey: "classAarray");
I'm doing the following in my initial viewDidLoad():
var classA: AnyObject? = NSUserDefaultsManager.userDefaults.objectForKey("classAarray") as AnyObject?
let classAunpacked = NSKeyedUnarchiver.unarchiveObjectWithData(classA) as [ClassA]
I get the following compile-time error on the second line above (the one with let):
Cannot invoke 'unarchiveObjectWithData' with an argument list of type '(AnyObject?)'
However, if I try to retrieve the array with anything other than AnyObject?, I get other compile time errors. It also seems I can't cast from AnyObject? to [ClassA]. Any ideas how this should be done?
unarchiveObjectWithData takes an NSData as it's sole argument, not an optional AnyObject. Since the result of unarchive... is also an optional, I'd suggest using:
if let classA = NSUserDefaultsManager.userDefaults.dataForKey("classAarray") {
if let classAunpacked = NSKeyedUnarchiver.unarchiveObjectWithData(classA) as? [ClassA] {
// Use classAunpacked here
}
}

Resources