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

Related

Why is this subclass failing to be archived by NSKeyedArchived?

I have this class that have to be the subclass of OIDAuthState (this class is an NSObject and conforms to NSSecureCoding),
The code to encode the class OIDAuthState was working fine before
self.data = try NSKeyedArchiver.archivedData(withRootObject: authState, requiringSecureCoding: true)
Once I created this subclass that includes adds a simple Boolean to the class isExpiredTokenEnforced, I now get the error when using NSKeyedArchiver,
The error says:
NSDebugDescription=Caught exception during archival: *** -decodeBoolForKey: only defined for abstract class. Define -[NSKeyedArchiver decodeBoolForKey:]!
Here is the subclass I am trying to archive and down below the override method to encode and decode this class, I am simply encoding and decoding this 1 extra property and then passing down the coder to the parent class,
class AuthenticationStateManager: OIDAuthState, AuthenticationState {
var isExpiredTokenEnforced = false
var lastTokenResponseInterface: TokenResponse? {
if isExpiredTokenEnforced {
return EnforcedExpiredTokenResponse(idToken: super.lastTokenResponse?.idToken,
accessToken: super.lastTokenResponse?.accessToken)
} else {
return super.lastTokenResponse
}
}
override static var supportsSecureCoding: Bool { true }
required override init(authorizationResponse: OIDAuthorizationResponse?, tokenResponse: OIDTokenResponse?, registrationResponse: OIDRegistrationResponse?) {
super.init(authorizationResponse: authorizationResponse, tokenResponse: tokenResponse, registrationResponse: registrationResponse)
}
required init?(coder: NSCoder) {
coder.encode(isExpiredTokenEnforced, forKey: "isExpiredTokenEnforced")
super.init(coder: coder)
}
override func encode(with coder: NSCoder) {
self.isExpiredTokenEnforced = coder.decodeBool(forKey: "isExpiredTokenEnforced")
super.encode(with: coder)
}
}
I can't figure out why this is failing I couldn't find relevant information to fix this,
does anyone have a clue what could be wrong here?
Thank you in advance for your time.
You are mixing up your encoder and decoder functions. Your func encode() is trying to call decodeBool. It should be calling encode().
Similarly your init?(coder:) function should be calling decode (or rather decodeBool(forKey:).)

How can I store a type that conforms to a protocol to be used later in an object?

I'd like to make a controller that can store a type and later create an object of that type. However, I'm getting a few compiler errors as noted below:
class Test:Codable {
let str : String
let x : Int
let y : Int
}
class TestController: UIViewController {
let type : Decodable
init <T>(type: T) where T:Decodable {
self.type = type
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func doSomething() {
let x = type.init() // Static member 'init' cannot be used on instance of type 'Decodable'
}
}
let controller = TestController(type: Test.self) // Initializer 'init(type:)' requires that 'Test.Type' conform to 'Decodable'
Is it only possible to do this by writing doSomething as a closure that is passed to init instead of the type?

Deserialize subclass of GKGraphNode using NSKeyedUnarchiver

I want to serialize and deserialize an object of my GKGraphNode subclass using NSKeyedArchiver and NSKeyedUnarchiver. So I try the following:
//: Playground - noun: a place where people can play
import GameplayKit
class MyGraphNode: GKGraphNode {
static let textCodingKey = "TextCodingKey"
let text: String
override convenience init() {
self.init(text: "Default Text")
}
init(text: String) {
self.text = text
super.init()
}
required init?(coder aDecoder: NSCoder) {
text = aDecoder.decodeObject(forKey: MyGraphNode.textCodingKey) as! String
super.init(coder: aDecoder)
}
override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(text, forKey: MyGraphNode.textCodingKey)
}
}
let text = "Test Text"
let graphNode = MyGraphNode(text: text)
let data = NSKeyedArchiver.archivedData(withRootObject: graphNode)
if let unarchivedGraphNode = NSKeyedUnarchiver.unarchiveObject(with: data) as? MyGraphNode {
print("Text: \(unarchivedGraphNode.text)")
}
Unfortunately the example prints only the default text and not the expected test text:
Text: Default Text
First I omitted the convenience initializer. But in this case it crashed with this error:
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.
GKGraphNodeSubclass.playground: 5: 7: Fatal error: Use of
unimplemented initializer 'init()' for class
'__lldb_expr_58.MyGraphNode'
Can anyone explain why the test text is ignored during the deserialization?
Or why I have to add the convenience initializer at all?
I got help in the Apple Developer Forum:
The "text" property is ending up being reset by "super.init(coder: aDecoder)", presumably because that calls "init()" internally, and that ends up at your convenience "init ()". This would be illegal in Swift, but it's legal in Obj-C, which doesn't have the same strict initialization rules.
The solution is to initialize "text" after the super.init(coder:), rather than before. This means you can't use a "let" property
To fix my example I changed the variable declaration and the NSCoding initializer like this:
var text: String!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
text = aDecoder.decodeObject(forKey: MyGraphNode.textCodingKey) as! String
}
I agree with Ruben, with a small addition
It might also be necessary to override the init() to ensure it exist. Due to the strict initialization rules of Swift
override init() {
super.init()
}

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.

How to implement a failable initializer for a class conforming to NSCoding protocol in Swift?

How to implement a failable initializer for a class conforming to NSCoding protocol?
I'm getting the following errors:
1. Line override init() {}: Property 'self.videoURL' not initialized at implicitly generated super.init call
2. Line return nil: All stored properties of a class instance must be initialized before returning nil from an initializer
I've seen Best practice to implement a failable initializer and All stored properties of a class instance must be initialized before returning nil which helped me a lot, but since my class also conforms to NSCoding protocol I don't know how to implement a failable initializer in my case.
Any suggestions on how to implement a failable initializer?
class CustomMedia : NSObject, NSCoding {
var videoTitle: String?
let videoURL: NSURL!
override init() {}
init?(title: String?, urlString: String) {
// super.init()
if let url = NSURL(string: urlString) {
self.videoURL = url
self.videoTitle = title
} else {
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.videoTitle, forKey: PropertyKey.videoTitle)
aCoder.encodeObject(self.videoURL, forKey: PropertyKey.videoURL)
}
required init(coder aDecoder: NSCoder) {
videoTitle = aDecoder.decodeObjectForKey(PropertyKey.videoTitle) as? String
videoURL = aDecoder.decodeObjectForKey(PropertyKey.videoURL) as! NSURL
}
}
UPDATE: This was addressed in the Swift 2.2 update, and you no longer have to assign a nil value and call super prior to failing an initializer.
For version of Swift prior to 2.2:
You actually have to initialize your values before returning nil, unfortunately.
Here's the working solution:
class CustomMedia : NSObject, NSCoding {
var videoTitle: String?
var videoURL: NSURL!
init?(title: String?, urlString: String) {
super.init()
if let url = NSURL(string: urlString) {
self.videoURL = url
self.videoTitle = title
} else {
self.videoURL = nil
self.videoTitle = nil
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.videoTitle, forKey: PropertyKey.videoTitle)
aCoder.encodeObject(self.videoURL, forKey: PropertyKey.videoURL)
}
required init(coder aDecoder: NSCoder) {
videoTitle = aDecoder.decodeObjectForKey(PropertyKey.videoTitle) as? String
videoURL = aDecoder.decodeObjectForKey(PropertyKey.videoURL) as! NSURL
}
}