Swift NSCoding - How to read an encoded array - arrays

I'm using Swift playground to write a parent-child relationship using NSCoding.
The relationship can be described as:
One Author can write many Books
However, it causes an error when I try to save the collection of books;
Playground execution aborted: error: Execution was interrupted,
reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). The
process has been left at the point where it was interrupted, use
"thread return -x" to return to the state before expression
evaluation.
My Swift playground code is as follows;
// Author model
class Author: NSObject, NSCoding
{
var name: String
var books:[Book] = [Book]()
init(name:String, books:[Book]?) {
self.name = name
guard let bookList = books else {
return
}
self.books = bookList
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey:"name") as! String
let books = aDecoder.decodeObject(forKey:"books") as! [Book]
self.init(name:name, books:books)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey:"name")
aCoder.encode(books, forKey:"books")
}
}
// Book model
class Book: NSObject, NSCoding {
var title: String
var author: Author?
init(title:String, author: Author?) {
self.title = title
self.author = author
}
public convenience required init?(coder aDecoder: NSCoder) {
let title = aDecoder.decodeObject(forKey: "title") as! String
let author = aDecoder.decodeObject(forKey: "author") as! Author
self.init(title:title, author:author)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(author, forKey: "author")
}
}
// Create the data
var author:Author = Author(name: "Tom Clancy", books: nil)
let book0 = Book(title: "The Hunt for Red October", author: author)
let book1 = Book(title: "Red Storm Rising", author: author)
author.books.append(contentsOf: [book0, book1])
// -----------
// Save data
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first! as URL
let writePath: URL = url.appendingPathComponent("archive.plist")
print("Attempting to save to: \(writePath)")
let saveData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: saveData)
archiver.encode(books, forKey:"books")
archiver.encode(author, forKey: "author")
//NSKeyedArchiver.archiveRootObject(books, toFile: writePath.path)
archiver.finishEncoding()
_ = saveData.write(to: writePath, atomically: true)
// -----------
// Load data
print("Attempting to load from: \(writePath)")
if FileManager.default.fileExists(atPath: writePath.path) {
if let saveData = try? Data(contentsOf: writePath) {
let unarchiver = NSKeyedUnarchiver(forReadingWith: saveData)
// Crash occurs here
var authorData = unarchiver.decodeObject(forKey: "author") as! Author
print (authorData.name)
}
} else {
print("No data archive exists")
}
So my question is: What is causing the error and how can I rectify the issue?
Many thanks

I was able to solve this problem by refactoring my code.
class Author: NSObject, NSCoding
{
var name: String
var books:[Book] = [Book]()
init(name:String, books:[Book]?) {
self.name = name
guard let bookList = books else {
return
}
self.books = bookList
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey:"name") as! String
let books = aDecoder.decodeObject(forKey:"books") as! [Book]
self.init(name:name, books:books)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey:"name")
aCoder.encode(books, forKey:"books")
}
}
class Book: NSObject, NSCoding {
var title: String
var author: Author
init(title:String, author: Author) {
self.title = title
self.author = author
}
public convenience required init?(coder aDecoder: NSCoder) {
let title = aDecoder.decodeObject(forKey: "title") as! String
let author = aDecoder.decodeObject(forKey: "author") as! Author
self.init(title:title, author:author)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(author, forKey: "author")
}
}
let author:Author = Author(name: "Tom Clancy", books: nil)
let books = [
Book(title: "The Hunt for Red October", author: author)
, Book(title: "Red Storm Rising", author: author)
]
//author.books.append(contentsOf: books)
// -----------
// Save data
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first! as URL
let writeFile: URL = url.appendingPathComponent("books.data")
print ("Attempting to write to: \(writeFile.path)")
NSKeyedArchiver.archiveRootObject(books, toFile: writeFile.path)
if let bookData = NSKeyedUnarchiver.unarchiveObject(withFile: writeFile.path) as? [Book] {
for book in bookData {
print ("\(book.title) - \(book.author.name)")
}
}
This seems to work.
Issue closed

Related

Saving an custom object array that is appended constantly

I'm relatively new to Swift and coding in general. I'm trying to hone my skills at the moment but putting together a simple reminder app. I'm trying to get the back end working before I put together the story board but I have the essential story board elements to test if my system will work.
Basically I'm trying to save a array that contains a custom object, but this array is appended to each reminder addition done by the user. This is so that every time the app opens, the array will contain the reminders from last time.
Here is the code I have so far to create and append the list;
func createReminder() {
let reminderAdd = Reminder(chosenReminderDescription: textRetrieve.text!, chosenReminderLength: 1)
reminderList.append(reminderAdd)
dump(reminderList)
}
Here is the object code;
class Reminder {
var reminderDescription = "Require initalisation."
var reminderLength = 1 // in days
init (chosenReminderDescription: String, chosenReminderLength: Int) {
reminderDescription = chosenReminderDescription
reminderLength = chosenReminderLength
}
}
How would I go about saving the array?
EDIT:
This is what i've added so far.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let reminderAdd = Reminder(chosenReminderDescription: "Placeholder test", chosenReminderLength: 1)
reminderList.append(reminderAdd)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = NSManagedObject(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
I'm getting crashes and I can't seem to debug it as of yet. I believe this line is causing the crash as when I remove it the app launches fine.
let reminderAdd = Reminder(chosenReminderDescription: "Placeholder test", chosenReminderLength: 1)
reminderList.append(reminderAdd)
Any ideas?
EDIT 2:
datamodel
That is the data model, I'm not entirely sure what you mean to make the object into a codable. Thanks again.
EDIT 3:
ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = Tasks(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
dump(reminderList)
}
you could create an instance using CoreData and store it like an internal database.
These are some good tutorial to start with that:
https://medium.com/xcblog/core-data-with-swift-4-for-beginners-1fc067cca707
https://www.raywenderlich.com/7569-getting-started-with-core-data-tutorial
EDIT 2
As you can see in this image,
https://ibb.co/f1axcA
my list in coreData is of type [Notifica], so is an array of object Notifica, to implement codable you should do something like this
public class Notifica: NSObject, NSCoding {
public required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeObject(forKey: "id") as? Double
self.type = aDecoder.decodeObject(forKey: "type") as? String
self.idEvent = aDecoder.decodeObject(forKey: "idEvent") as? Int
self.contactPerson = aDecoder.decodeObject(forKey: "contactPerson") as? People
self.title = aDecoder.decodeObject(forKey: "title") as? String
self.date = aDecoder.decodeObject(forKey: "date") as? String
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(type, forKey: "type")
aCoder.encode(idEvent, forKey: "idEvent")
aCoder.encode(contactPerson, forKey: "contactPerson")
aCoder.encode(title, forKey: "title")
aCoder.encode(date, forKey: "date")
}
ecc..
Another thing is to not call NSManagedObject and pass the entity, but you should name that Tasks as you called in dataModel, if you type Tasks on xcode it will fin for you the NSManagedObject created and then you can set the value for taskName
EDIT 3
"<Simple_Reminders.Reminder: 0x60400046da40>" means that a Reminder object exist! So you saved it! Reminder has two variable:
-reminderDescription and
-reminderLength, so change your code
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
with this
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
if let reminders = data.value(forKey: "taskName") as? [Reminder] {
for reminder in reminders {
// Now you have your single object Reminder and you can print his variables
print("Your reminder description is \(reminder. reminderDescription), and his length is \(reminder. reminderLength))"
}
}
}
} catch {
print("Failed")
}

extend an Array of Dictionary<String, Any> Swift 3

var dicts = [["key1": "value1", "key2": "value2"]]
dicts.values(of: "key1") // prints - value1
I am working on a project where I want to store the array of dictionary and then fetch the data from there on condition if array of dictionary contains the particular value.
Swift 3.0
You can try this way.
var dicts:[[String:Any]] = []
var check:Bool = false
dicts = [["search_date": "17/03/17", "search_title": ""],["search_date": "17/02/19", "search_title": "parth"],["search_date": "20/02/19", "search_title": "roy"]]
for item in dicts {
if let title = item["search_title"] as? String {
if title == "parth" {
check = true
break
}else {
check = false
}
}
else {
check = false
}
}
print(check)
We can Use Model to solve the Problem
class Person: NSObject, NSCoding {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
self.age = decoder.decodeInteger(forKey: "age")
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(age, forKey: "age")
}
}
Class
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// setting a value for a key
let newPerson = Person(name: "Joe", age: 10)
var people = [Person]()
people.append(newPerson)
let encodedData = NSKeyedArchiver.archivedData(withRootObject: people)
UserDefaults.standard.set(encodedData, forKey: "people")
// retrieving a value for a key
if let data = UserDefaults.standard.data(forKey: "people"),
let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Person] {
myPeopleList.forEach({print( $0.name, $0.age)}) // Joe 10
} else {
print("There is an issue")
}
}
}
All Thanks to Leo Dabus
[Link] (https://stackoverflow.com/a/37983027/3706845)
Your question is very vague. But what I understood is that you want to filter the array of dictionaries so it only contains dictionaries that have a certain value, and this can be done this way:
let filteredDicts = dicts.filter({ $0.values.contains("value2") })

How to Save complex Arrayobjects to device using NSKeyedArchiver swift

I want to save an array of any class (e.g let array = SymptomsModel) type into device using NSKeyedArchiver in swift .
I Know how to save an array if SymptomsModel class contains all variables with primitive data types , but don't know how to save it if also contains an array of any other class as its property
Below I have explained my problem with the help of example , please go through it and provide solution.
I have a class
class SymptomsModel: NSObject, NSCoding ,ResponseJSONObjectSerializable {
var slug:String?
var name:String?
var images:[Sym_images]?
var videos:[Sym_videos]?
struct Keys {
static let Name = "name"
static let Slug = "slug"
static let Images = "images"
static let Videos = "videos"
}
required init(json:SwiftyJSON.JSON) {
self.slug = json["slug"].string
self.name = json["name"].string
self.images = [Sym_images]()
if let imagesJSON = json["images"].array {
for(imagesJSON) in imagesJSON {
if let newImages = Sym_images(json: imagesJSON){
self.images?.append(newImages)
}
}
}
self.videos = [Sym_videos]()
if let videosJSONArray = json["videos"].array {
for(videosJSON) in videosJSONArray {
if let newVideos = Sym_videos(json: videosJSON){
self.videos?.append(newVideos)
}
}
}
}
init(dictionary: [String : AnyObject]) {
self.name = dictionary[Keys.Name] as? String
self.slug = dictionary[Keys.Slug] as? String
self.images = dictionary[Keys.Images] as? [Sym_acc_images_objects]
self.videos = dictionary[Keys.Videos] as? [Sym_acc_videos_objects]
}
func encodeWithCoder(archiver: NSCoder) {
archiver.encodeObject(name, forKey: Keys.Name)
archiver.encodeObject(slug, forKey: Keys.Slug)
archiver.encodeObject(images, forKey: Keys.Images)
archiver.encodeObject(videos, forKey: Keys.Videos)
}
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_images_objects]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_videos_objects]
}
and a PersistanceManager class to save the data with NskeyedArchiver as
class PersistenceManager {
class private func documentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentDirectory = paths[0] as String
return documentDirectory
}
class func saveNSArray(arrayToSave: NSArray, key: String) {
print(" saveNSArray key : \(key)")
let file = documentsDirectory().stringByAppendingPathComponent(key)
NSKeyedArchiver.archiveRootObject(arrayToSave, toFile: file)
}
class func loadNSArray(path: String) -> NSArray? {
print(" loadNSArray key : \(path)")
let file = documentsDirectory().stringByAppendingPathComponent(path)
let result = NSKeyedUnarchiver.unarchiveObjectWithFile(file)
return result as? NSArray
}
}
And Here is my implimentation of saving and then retreiving the array
class ViewController: UIViewController{
var ArraySymptom = [SymptomsModel]()
override func viewDidLoad() {
super.viewDidLoad()
ArraySymptom = loadArray()
//saving data in device
PersistenceManager.saveNSArray(ArraySymptom, key: "Symptom")
//loading data from device
if let value = PersistenceManager.loadNSArray("Symptom") as? [SymptomsModel] {
let images = value[0].images
print("images : \(images)")
let slug = value[0].slug
print("slug : \(slug)")
}
}
Here am able to get the value of slug but not able to fetch images value.
It might be happening because slug is of String type and Images is of Custom Class type .
Please suggest me the way i can get it done .
Is is possible to save these type of arrays with NSKeyedArchiver , so that i can access images value just by retreiving ArraySymptom from device.
Silly mistake it was
I was getting nil in Images because it was decoded with wrong key , it was copy paste mistake
The Error was in this function..
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_videos]
}
And the correct decoding must be
self.images = unarchiver.decodeObjectForKey(Keys.Images) as?
[Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Videos) as?
[Sym_videos]

Swift when appending custom class to array all previous values overwritten

I have a custom class called Subject, which I am trying to initialize versions of and append to a array, however when I append one to a array it overwrites all the Subjects all ready in there. For example this code
var things:[Subject] = []
things.append(Subject(initName: "1", initTeacher: "1", initClassroom: "1"))
things.append(Subject(initName: "2", initTeacher: "2", initClassroom: "2"))
print(things[0].name)
print(things[1].name)
is printing Optional("2") Optional("2") when it should be printing 'Optional("1") Optional("2")'
This is the code for my custom class
class Subject: NSManagedObject{
var name: String?
var teacher: String?
var classroom: String?
init(initName: String, initTeacher: String, initClassroom: String){
name = initName
teacher = initTeacher
classroom = initClassroom
}
func save() -> Bool{
if(name == "" || teacher == "" || classroom == ""){
return false
}else{
let appDelgate = UIApplication.sharedApplication().delegate as? AppDelegate
let managedContext = appDelgate?.managedObjectContext
let entity = NSEntityDescription.entityForName("Subject", inManagedObjectContext: managedContext!)
let subject = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
subject.setValue(name, forKey: "name")
subject.setValue(teacher, forKey: "teacher")
subject.setValue(classroom, forKey: "classroom")
do{
try managedContext?.save()
}catch let error as NSError{
print("Failed because of \(error)")
}
return true
}
}
func edit(newName: String, newTeacher: String, newClassroom: String) -> Bool{
let appDelgate = UIApplication.sharedApplication().delegate as? AppDelegate
let managedContext = appDelgate?.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Subject")
fetchRequest.predicate = NSPredicate(format: "name = %#", name!)
do{
let fetchResults = try managedContext?.executeFetchRequest(fetchRequest)
let editingSubject = fetchResults![0] as! NSManagedObject
editingSubject.setValue(newName, forKey: "name")
editingSubject.setValue(newTeacher, forKey: "teacher")
editingSubject.setValue(newClassroom, forKey: "classroom")
do{
try managedContext?.save()
return true
}catch{
return false
}
}catch{
return false
}
}}
Thanks for any help

How to save and retrieve class object from NSUserDefaults swift?

I'm trying to save my array to NSUserDefaults. But my Array exist with struct.
struct MyData {
var Text = String()
var Number = Int()
}
var Data = [MyData]()
I found this and I tried to do this;
let Data2 = NSKeyedArchiver.archivedDataWithRootObject(Data)
But this gives me this: Cannot invoke 'archivedDataWithRootObject' with an argument list of type '([(TableViewController.MyData)])'
Any advice?
Swift structs are not classes, therefore they don't conform to AnyObject protocol.
And Syntax for archivedDataWithRootObject is:
class func archivedDataWithRootObject(rootObject: AnyObject) -> NSData
Which means it only accept AnyObject type object and struct doesn't conform AnyObject protocol so you can not use struct here.
So just change struct to class and it will work fine.
UPDATE:
This way you can store it into NSUserDefaults.
Tested with playGround
import UIKit
class Person: NSObject, NSCoding {
var name: String!
var age: Int!
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String
self.age = decoder.decodeObjectForKey("age") as! Int
}
convenience init(name: String, age: Int) {
self.init()
self.name = name
self.age = age
}
func encodeWithCoder(coder: NSCoder) {
if let name = name { coder.encodeObject(name, forKey: "name") }
if let age = age { coder.encodeObject(age, forKey: "age") }
}
}
var newPerson = [Person]()
newPerson.append(Person(name: "Leo", age: 45))
newPerson.append(Person(name: "Dharmesh", age: 25))
let personData = NSKeyedArchiver.archivedDataWithRootObject(newPerson)
NSUserDefaults().setObject(personData, forKey: "personData")
if let loadedData = NSUserDefaults().dataForKey("personData") {
loadedData
if let loadedPerson = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? [Person] {
loadedPerson[0].name //"Leo"
loadedPerson[0].age //45
}
}

Resources