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

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.

Related

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

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
}
}

Correctly init NSCoder in sub class when init NSCoder is convenience method in base class in Swift

Here's my code:
import Foundation
class Person: NSObject, NSCoding {
var name: String
init(name: String) {
self.name = name
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: "name")
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey("name") as! String
self.init(name: name)
}
}
class Martin: Person {
init() {
self.init(name: "Martin")
}
required convenience init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
let p = Martin()
print(p.name)
For some reason I always end-up in a catch-22 situation, the only way i see making this work is to explicitly initialize all properties in required convenience init?(coder aDecoder: NSCoder) to able to remove the convenience and do super.init(coder: aDecoder) in Martin
I read about the init rules in Swift, still don't understand why Martin can't inherit the convenience init from Person in this case.
Because the rules state that
A designated initializer must call a designated initializer from its immediate superclass.
A convenience initializer must call another initializer from the same class.
A convenience initializer must ultimately call a designated initializer.

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 code initwithcoder in Swift?

i'm newbie in swift adn i have problem with initwithcoder in swift.
I have class UserItem, i need it to save user login.
in objective c is like this
- (id)initWithCoder:(NSCoder *)decoder{
if (self = [super init]){
self.username = [decoder decodeObjectForKey:#"username"];
}
return self;
}
and in swift i'm trying like this
override init() {
super.init()
}
required init(coder decoder: NSCoder!) {
self.username = (decoder.decodeObjectForKey("username")?.stringValue)!
super.init(coder: decoder)
}
but if like above, i get error on code
super.init(coder: decoder)
error message is "extra argument 'coder' in call
i can't figure out anymore, so i'm try this code,
convenience init(decoder: NSCoder) {
self.init()
self.username = (decoder.decodeObjectForKey("username")?.stringValue)!
}
but, get error
.UserItem initWithCoder:]: unrecognized selector sent to instance 0x7fd4714ce010
what should i do? thanks before for your help.
I struggled with NSCoding (the protocol that you use to archive and unarchive objects) in the past and I'm seeing that you are going through the same pains. Hope this lessen it a bit:
class UserItem: NSObject, NSCoding {
var username: String
var anInt: Int
init(username: String, anInt: Int) {
self.username = username
self.anInt = anInt
}
required init?(coder aDecoder: NSCoder) {
// super.init(coder:) is optional, see notes below
self.username = aDecoder.decodeObjectForKey("username") as! String
self.anInt = aDecoder.decodeIntegerForKey("anInt")
}
func encodeWithCoder(aCoder: NSCoder) {
// super.encodeWithCoder(aCoder) is optional, see notes below
aCoder.encodeObject(self.username, forKey: "username")
aCoder.encodeInteger(self.anInt, forKey: "anInt")
}
// Provide some debug info
override var description: String {
get {
return ("\(self.username), \(self.anInt)")
}
}
}
// Original object
let a = UserItem(username: "michael", anInt: 42)
// Serialized data
let data = NSKeyedArchiver.archivedDataWithRootObject(a)
// Unarchived from data
let b = NSKeyedUnarchiver.unarchiveObjectWithData(data)!
print(a)
print(b)
The important thing is to match up the keys and the data types in encodeWithCoder(aCoder:) (the archive function) and init(coder:) (the unarchive function).
Where is confusing for beginners is what to do with the superclass. You should only include the superclass in the two functions above if the superclass itself conforms to NSCoding. NSObject does not provide that by itself. The idea is each class knows about its own properties, some of which are private. If the superclass cannot archive/unarchive, there's no need to call them.

Annotating Swift function declarations that support conformance to a protocol?

Is there a standard mechanism for annotating function declarations in Swift to indicate that they are present because a class conforms to some protocol?
For instance, this declaration might be present because a class conforms to NSCoding. (Marking it with override would result in a syntax error, so it's not the kind of annotation I am looking for.) Ideally I am looking for a code-level annotation (e.g. override instead of /*! ... */).
// ... annotation such as "conform to NSCoding", if possible
func encodeWithCoder(encoder: NSCoder) {
// ...
}
You can use extension. for example:
protocol SomeProtocol {
func doIt() -> Int
}
class ConcreteClass {
....
}
extension ConcreteClass: SomeProtocol {
func doIt() -> Int {
// ...
return 1
}
}
But you cannot define required initializer in extension, for example:
// THIS DOES NOT WORK!!!
class Foo: NSObject {
}
extension Foo: NSCoding {
required convenience init(coder aDecoder: NSCoder) {
self.init()
}
func encodeWithCoder(aCoder: NSCoder) {
// ...
}
}
emits an error:
error: 'required' initializer must be declared directly in class 'Foo' (not in an extension)
required convenience init(coder aDecoder: NSCoder) {
~~~~~~~~ ^
In this case, you should use // MARK: comments:
class Foo: NSObject {
// ...
// MARK: NSCoding
required init(coder aDecoder: NSCoder) {
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
// ...
}
}