2

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

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

Comments

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.