Serialize Custom subclass of CLCircularRegion with extra an property in the class - swift

I am using this class to create an object but when I archive and unarchive from and to UserDefaults it only gives me the value of trialUrl but not it's superclass properties.Since I don't know if CLCircularRegion supports Codable protocol I'm stuck with this approach.
class TrialRegion: CLCircularRegion{
var trailUrl:URL?
init(center: CLLocationCoordinate2D, radius: CLLocationDistance, identifier: String,trailUrl:URL) {
super.init(center: center, radius: radius, identifier: identifier)
self.trailUrl = trailUrl
}
override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(URL.self, forKey: "trialUrl")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let url = aDecoder.decodeObject(of: NSURL.self, forKey: "trialUrl") as URL? else {return}
self.trailUrl = url
}
}

Related

Xcode 12.5 NSCoding issue with SpriteKit/GameplayKit

I'm working on application using SpriteKit and I'm having trouble with NSCoding on a particular object (a GKComponent object). I'm using Swift 4.0 and iOS 11 in Build Settings. The application always crashes on the line (marked with //***) where I'm trying to decode the object. I've been using the same structure for encoding/decoding other objects and they seem to work fine. Not sure why it won't work with this particular GKComponent object.I've spent hours trying to resolve this issue but couldn't figure out why.
Any help will be appreciated.
Thanks in advance!
class ItemEntity: GKEntity {
var name: String!
var embeddedSpell: KnockbackComponent?
//Other variables
init(itemName: String) {
super.init()
name = itemName
embeddedSpell = KnockbackComponent(anItem: self)
addComponent(embeddedSpell!)
//Other initializations
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "Item.name")
aCoder.encode(embeddedSpell, forKey: "Item.embeddedSpell")
super.encode(with: aCoder)
}
required convenience init?(coder aDecoder: NSCoder) {
let itemName = aDecoder.decodeObject(forKey: "Item.name") as! String
self.init(itemName: itemName)
embeddedSpell = aDecoder.decodeObject(forKey: "Item.embeddedSpell") as? KnockbackComponent
if embeddedSpell != nil {
addComponent(self.embeddedSpell!)
}
}
//Other codes
}
Other class
class KnockbackComponent: GKComponent {
weak var item: ItemEntity!
init(anItem: ItemEntity) {
item = anItem
super.init()
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(item, forKey: "KnockbackComponent.item")
super.encode(with: aCoder)
}
required convenience init?(coder aDecoder: NSCoder) {
//***This line causes crash with error msg Thread 1: EXC_BAD_INSTRUCTION***//
let item = aDecoder.decodeObject(forKey: "KnockbackComponent.item") as? ItemEntity //<--error msg Thread 1: EXC_BAD_INSTRUCTION
self.init(anItem: item!)
}
//Other codes
}
I've resolved the issue by getting my GKComponent class to conform to Codable with the codes below:
override func encode(to encoder: Encoder) {
var container = encoder.container(keyedBy: CodingKeys.self)
try? (container.encode(itemEnt, forKey: .itemEnt))
}
required convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let itemEntity = try container.decode(ItemEntity.self, forKey: .itemEnt)
self.init(entity: itemEntity)
}

The required encoding function doesn't shown in a working code

serialization and deserialization is done by the two method defined in the NSCoding protocol as follow
encodeWithCoder(_ aCoder: NSCoder) {
// Serialize your object here
}
init(coder aDecoder: NSCoder) {
// Deserialize your object here
}
In my own ViewController (inherited from UITableViewController), I have a Array which contain my own object named Item and I do implemented the init(coder aDecoder: NSCoder) initial function. The Item is defined as follow
class Item: NSObject, NSCoding {
var text = ""
var checked = false
func toggleChecked() {
checked = !checked
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.text, forKey: "SLAText")
aCoder.encode(self.checked, forKey: "SLAChecked")
}
required init?(coder aDecoder: NSCoder) {
self.text = aDecoder.decodeObject(forKey: "SLAText") as! String
self.checked = aDecoder.decodeBool(forKey: "SLAChecked")
super.init()
}
override init() {
super.init()
}
}
Instead implement the function encode(with: NSCoder) I defined my own serialization function named saveItems()
func saveItems() {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(items, forKey: "ItemLists") //items is a array of type [Item]
archiver.finishEncoding()
data.write(to: dataFilePath(), atomically: true)
}
Question
Why the code is working with out implement the required NSCoding function? The code is simplified from a example of the book I'm studying, I didn't find the encodeWithCoder(_ aCoder: NSCoder) function at all. Isn't the required means you have to implemented it?
Thanks for your time and help
Why the code is working with out implement the required NSCoding function?
This is not true.
NSCoding is a protocol
First of all NSCoding is a protocol, not a function. And in order to conform a type to NSCoding you need to implement a method and an initializer
public func encode(with aCoder: NSCoder)
public init?(coder aDecoder: NSCoder)
Why does your code work?
Let's look at what you are doing
archiver.encode(items, forKey: "ItemLists")
Here items is defined as [Item] which is and array of Item(s).
And Array does conform to NSCoding. This can be easily tested by writing
let nums = [1, 2, 3]
if nums is NSCoding {
print("Array does conform to NSCoding")
} else {
print("Array does NOT conform to NSCoding")
}
The result is
Array does conform to NSCoding
Conclusion
Of course to work properly the element inside of the array must be conform to NSCodable too. And since the generic type of your Array is Item (which you made conform to NSCodable) the mechanism does work properly.

Archive and unarchive a custom class(have a property as object array),always failed

I am searching for a long time on net. But no use. Please help or try to give some ideas how to achieve this.
when unarchive app crashes and i get this message:
Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason:
'*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_TtCC11ArchiveTest8dogHouse3dog)
for key (NS.objects); the class may be defined in source code or a library that is not linked'
What does it means?
Anyone helps?Thanks in advance.
Now,I recognize the Terminating because other codes,like this:
enter image description here
if I try:
enter image description here
it's well done,buy why?code write in the viewDidLoad do anything else?which method linked the class?
I have a class:
let ModelPath = "Model.plist".cacheDir()
class dogHouse: NSObject , NSCoding{
var dogs:[Dog]?
required init(coder aDecoder: NSCoder)
{
dogs = aDecoder.decodeObject(forKey: "dogs") as? [Dog]
super.init()
}
func encode(with aCoder: NSCoder) {
if dogs != nil{
aCoder.encode(dogs, forKey: "dogs")
}
}
class Dog: NSObject , NSCoding {
var name : String?
required init(coder aDecoder: NSCoder)
{
name = aDecoder.decodeObject(forKey: "name") as! String?
super.init()
}
func encode(with aCoder: NSCoder) {
if name != nil{
aCoder.encode(name, forKey: "name")
}
}
//ArchiveModels
func saveModel() -> Bool{
return NSKeyedArchiver.archiveRootObject(self, toFile: ICModelPath)
}
//UnArchiveModels
class func loadArchiver() -> [Dog]?{
let obj = NSKeyedUnarchiver.unarchiveObject(withFile: ICModelPath)
if obj != nil {
print(obj.debugDescription)
let model = obj as? dogHouse
return model?.dogs
}
return nil
}
}
Maybe you missed a parameter in the init function?
required init(coder aDecoder: NSCoder)
{
dogs = aDecoder.decodeObject(forKey: "dogs") as? [Dog]
super.init(aDecoder) //HERE
}

Encoding/Decoding an array of objects which implements a protocol in Swift 2

I have got a class that inherits from NSObject and I want it to be NSCoding compliant. But I ran into trouble while encoding an array of objects which should implement a protocol.
protocol MyProtocol {
var myDescription: String { get }
}
class DummyClass: NSObject, NSCopying, MyProtocol {
var myDescription: String {
return "Some description"
}
func encodeWithCoder(aCoder: NSCoder) {
// does not need to do anything since myDescription is a computed property
}
override init() { super.init() }
required init?(coder aDecoder: NSCoder) { super.init() }
}
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol]
self.init(myCollection: collection)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myCollection, forKey: "collection")
}
}
For aCoder.encodeObject(myCollection, forKey: "collection") I get the error:
Cannot convert value of type '[MyProtocol]' to expected argument type 'AnyObject?'
OK, a protocol obviously is not an instance of a class and so it isn't AnyObject? but I've no idea how to fix that. Probably there is a trick that I'm not aware? Or do you do archiving/serialization differently in Swift as in Objective-C?
There's probably a problem with let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol], too but the compiler doesn't complain yet…
I've just found the solution myself: The key is to map myCollection into [AnyObject] and vice-versa, like so:
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection1 = aDecoder.decodeObjectForKey("collection") as! [AnyObject]
let collection2: [MyProtocol] = collection1.map { $0 as! MyProtocol }
self.init(myCollection: collection2)
}
func encodeWithCoder(aCoder: NSCoder) {
let aCollection: [AnyObject] = myCollection.map { $0 as! AnyObject }
aCoder.encodeObject(aCollection, forKey: "collection")
}
}
I know your title specifies Swift 2, but just for reference, for a similar problem I was working on, I found that in Swift 3, you don't need to convert anymore to AnyObject.
The following works for me in Swift 3 (using your example):
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection = aDecoder.decodeObject(forKey: "collection") as! [MyProtocol]
self.init(myCollection: collection)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(aCollection, forKey: "collection")
}
}

How to test required init(coder:)?

In my custom class WLNetworkClient I had to implement such method:
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I do not need to use that, but I would like to test this to make 100% code coverage. Do you know how to achieve this?
I tried following way with no success:
let nc = WLNetworkClient(coder: NSCoder())
XCTAssertNotNil(nc)
Production code:
required init?(coder: NSCoder) {
return nil
}
Test:
func testInitWithCoder() {
let archiverData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWithMutableData: archiverData)
let someView = SomeView(coder: archiver)
XCTAssertNil(someView)
}
Since the required initializer returns nil and does not use the coder, the above code can be simplified to:
func testInitWithCoder() {
let someView = SomeView(coder: NSCoder())
XCTAssertNil(someView)
}
Here is answer which should help you:
let cd = NSKeyedUnarchiver(forReadingWithData: NSMutableData())
let c = CustomTextField(coder:cd)
Answer from Rudolf Adamkovič ist still working with Swift 4:
required init?(coder aDecoder: NSCoder) {
return nil
}
func testInitWithCoder() {
// 1. Arrange
let archiver = NSKeyedArchiver(forWritingWith: NSMutableData())
// 2. Action
let viewController = ViewController(coder: archiver)
// 3. Assert
XCTAssertNil(viewController)
}
I combined Rudolf's answer with Liz's suggestion and ended with the following solution:
let viewController = SomeTableViewController(
presenter: SomePresenterMock(),
coder: NSKeyedUnarchiver(forReadingWith: Data()))
XCTAssert(viewController?.tableView.numberOfSections == 1)
The key here is to use NSKeyedUnarchiver(forReadingWith: Data()) as mock coder, otherwise the test crashes with NSInvalidArgumentException.
I use improved Rudolf's Adamkovič answer:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func test_initWithCoder() {
let object = SomeView()
let data = NSKeyedArchiver.archivedData(withRootObject: object)
let coder = NSKeyedUnarchiver(forReadingWith: data)
let sut = SomeView(coder: coder)
XCTAssertNotNil(sut)
}
It seems that all the previous answers stopped working after Swift 5.
I managed to modify #ChikabuZ answer and make it work like this:
func testInitWithCoder() {
// Given
let object = SomeView()
let data = try! NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false)
let coder = try! NSKeyedUnarchiver(forReadingFrom: data)
// When
let sut = SomeView(coder: coder)
// Then
XCTAssertNotNil(sut)
}