Can't get NSKeyedArchiver to work: unrecognized selector [closed] - swift

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
In a UITableView when deleting a row and saving the data source I also want to save to persistent storage.
I'm using NSKeyedArchiver to save an array of my custom object type Participant and it fails straight away with:
2018-02-13 23:01:41.818945+0000 ProjectName[28680:5720571]
-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60800004d290
2018-02-13 23:01:41.826553+0000 ProjectName[28680:5720571] *** Terminating app due to uncaught
exception 'NSInvalidArgumentException', reason: '-[_SwiftValue
encodeWithCoder:]: unrecognized selector sent to instance
0x60800004d290'
I've searched everywhere and can't find anything that's applicable to me. So far as I can tell my data class for Participant object conforms to NSObject and NSCoding with the necessary methods implemented.
EDIT: Thank you all for your feedback so quickly. Code is below and as I said before, if I've neglected to post something necessary that likely speaks to my experience so any help gratefully received!
Data Class (excerpt)
class Participant: NSObject, NSCoding {
//MARK: Properties
var name: String
var jobTitle: String?
var picture: UIImage?
var rate: Float
var ratePeriod: ratePeriods
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("participants")
//MARK: Types
struct PropertyKey {
//THESE MUST NOT CHANGE ONCE BUILT
static let name = "name"
static let jobTitle = "jobTitle"
static let picture = "picture"
static let rate = "rate"
static let ratePeriod = "ratePeriod"
}
//MARK: Initialisation
init?(name: String, jobTitle: String?, picture: UIImage?, rate: Float?, ratePeriod: ratePeriods?) {
//The name must not be empty
guard !name.isEmpty else {
return nil
}
//Init stored properties
self.name = name
self.jobTitle = jobTitle ?? ""
self.picture = picture
self.rate = rate ?? 0.0
self.ratePeriod = ratePeriod ?? .annually
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(jobTitle, forKey: PropertyKey.jobTitle)
aCoder.encode(picture, forKey: PropertyKey.picture)
aCoder.encode(rate, forKey: PropertyKey.rate)
aCoder.encode(ratePeriod, forKey: PropertyKey.ratePeriod)
}
required convenience init?(coder aDecoder: NSCoder) {
//The name is required. If we cannot decode a name string, the init should fail.
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String else {
return nil
}
let jobTitle = aDecoder.decodeObject(forKey: PropertyKey.jobTitle) as? String
let picture = aDecoder.decodeObject(forKey: PropertyKey.picture) as? UIImage
let rate = aDecoder.decodeFloat(forKey: PropertyKey.rate)
let ratePeriod = aDecoder.decodeObject(forKey: PropertyKey.ratePeriod) as? ratePeriods
//Must call designated init
self.init(name: name, jobTitle: jobTitle, picture: picture, rate: rate, ratePeriod: ratePeriod)
}
}
From inside Private Func where I'm trying to make use of it and it breaks
let archivePath = Participant.ArchiveURL.path
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(participants, toFile: archivePath)
Thank you again for any help I can get on this.
EDIT 2: I've done a bit more debugging and I guess my inexperience with the Xcode IDE and debugging has hindered this but it looks to be the ratePeriod property of the object being stored that's throwing the error. This property is a Struct which I think somebody else leaned to as being an issue. Is this resolvable or do I need to look at a different method for persisting storage of Structs?
EDIT 3: I've solved the problem but don't know how to mark this as resolved. The issue wasn't the Struct (which I wasn't storing) but rather an Enum. When using NSKeyedArchiver and NSCoding to store Enums you need to store the .rawValue and reconstitute it as an Int. ie: -
Encoding
aCoder.encode(ratePeriod.rawValue, forKey: PropertyKey.ratePeriod)
and
Decoding
let ratePeriodIntValue = (aDecoder.decodeObject(forKey: PropertyKey.ratePeriod) as? Int) ?? ratePeriods.annually.rawValue
let ratePeriod = ratePeriods(rawValue: ratePeriodIntValue)

Your custom object type, Participant, needs to be a class (not a struct), and in particular it must be a subclass of NSObject. Now you can adopt NSCoding successfully.

There is a very simple article on NSHipster about how to make your custom classes NSCoding/NSKeyedArchiver compliant. I recommend giving that a look.
As a basic answer, in order to make your object work with NSKeyedArchiver you need to tell the coder/decoder how to encode/decode your object. For example, if this is your class:
class Participant {
let name: String
let id: String
let age: Int
init(name: String, id: String, age: Int) {
self.name = name
self.id = id
self.age = age
}
}
You would need to make the following modifications to make it work with NSKeyedArchiver. First, declare that you are conforming to NSObject and NSCoding protocols. Then implement the encode methods and the convenience init?(coder:_) initializer:
class Participant: NSObject, NSCoding {
let name: String
let id: String
let age: Int
init(name: String, id: String, age: Int) {
self.name = name
self.id = id
self.age = age
}
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String,
let id = aDecoder.decodeObject(forKey: "id") as? String,
let age = aDecoder.decodeObject(forKey: "age") as? Int else { return nil }
self.init(name: name, id: id, age: age)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.id, forKey: "id")
aCoder.encode(self.age, forKey: "age")
}
}

Try adding #objc to your swift equivalent encodeWithCoder: method. Ditto for the decode init method. I doubt it's the solution but might be.
Also, please show how you wrote the NSCoding methods in Swift. Maybe they don't match up, given the inherent conversion between Swift and ObjC for how they are to be written?
func encode(with aCoder: NSCoder) {

Related

Can't save struct in custom UserDefaults - Error encode

After I fill out my form I input everything in a NSDictionary.
All of my other items can save no problem if I comment this out.
One of them being category. I encode it but keep getting the error
encodeWithCoder:]: unrecognized selector sent to instance 0x1c80bbe40'
Category Items is a subclass of Codeable.
Saving.
let playerDict : NSDictionary = [
"category" : CategoryItems(name: categoryData[0].name, image: categoryData[0].image, type: categoryData[0].type),
]
class PlayerModel: NSObject, NSCoding {
var category: CategoryItems?
init(json: NSDictionary) {
if let category = json["category"] as? CategoryItems {
self.category = category
}
}
}
required init?(coder aDecoder: NSCoder) {
if let category = aDecoder.decodeObject(forKey: "category") as? CategoryItems {
self.category = category
}
}
func encode(with aCoder: NSCoder) {
if let category = self.category {
aCoder.encode(category, forKey: "category")
}
}
Model:
struct CategoryItems: Codable {
var name : String
var image : String
var type : playerType
}
The main issue is that you can't use NSCoding with structs. Objects adopting NSCoding must be a subclass of NSObject.
I recommend to drop NSCoding completely and use Codable also for the class. Maybe you can resign the NSObject inheritance or even being a class.

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.

Saving Array with NSCoding

I have a small app that has a few saving functionalities. I have a data model class called: Closet:
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var Task: [Assignment]? // <----- assignment class is in example 2
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(deptName, forKey: "deptName")
aCoder.encodeObject(managerName, forKey: "mngName")
// aCoder.encodeObject(Task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
course = aDecoder.decodeObjectForKey("deptName") as! String
instructor = aDecoder.decodeObjectForKey("mngName") as! String
// Task = aDecoder.decodeObjectForKey("tasKArray") as? [Assignment]
}
override init() {
super.init()
}
}
So this is the main controller data model which in the first View Controller, a user is able to tap the "+" button to add a department name and manager name. The problem is not with saving this as i save it successfully using NSKeyedArchive and loads it back when the app starts.
The Problem:
I want to add an array of assignments on this data model Department called Assignment which would have a title and a notes variable. This is the Data model for Assignment:
Assignment.swift
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encodeWithCoder(aCoder: NSCoder) {
// Methods
aCoder.encodeObject(title, forKey: "Title")
aCoder.encodeObject(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObjectForKey("Title") as! String
notes = aDecoder.decodeObjectForKey("notepad") as! String
super.init()
}
override init() {
super.init()
}
}
So what i am essentially trying to achieve is an app where a user enters different departments with different manager names which work now in my app, but within a department, the user can click the "+" button to add an assignment title and notes section that can be editable when clicked which i can handle afterwards. These assignments are different from department to department.
My big problem is achieving this functionality. I can't seem to get this working.
I want this array assigment property to be part of the Department Class so each cell can have their own sort of To-Do list. any help would definitely help me out a lot. Thanks :)
You are using NSCoder correctly, but there are two errors in capitalization. The first error affects the functionality of the application, and the second error is a stylistic mistake. You encoded Task with the key "taskArray", but you tried to decode it with the key "tasKArray". If you fix the capital K in the latter, then your code will work.
The second capitalization error is a stylistic mistake: Task, like all properties in Swift, should be written in lowerCamelCase (llamaCase).
Be sure to pay close attention to indentation. In programming, there are special indentation rules we follow that help make code clear. Here is the corrected code with proper capitalization and indentation:
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var task: [Assignment]?
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(deptName, forKey: "deptName")
aCoder.encodeObject(managerName, forKey: "mngName")
aCoder.encodeObject(task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
course = aDecoder.decodeObjectForKey("deptName") as! String
instructor = aDecoder.decodeObjectForKey("mngName") as! String
task = aDecoder.decodeObjectForKey("taskArray") as? [Assignment]
}
override init() {
super.init()
}
}
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encodeWithCoder(aCoder: NSCoder) {
// Methods
aCoder.encodeObject(title, forKey: "Title")
aCoder.encodeObject(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObjectForKey("Title") as! String
notes = aDecoder.decodeObjectForKey("notepad") as! String
super.init()
}
override init() {
super.init()
}
}
Updated for Swift 5 / Xcode Version 12.4 (12D4e)
Thanks for the example above Tone416 -- I've reworked it for Swift 5 as the protocols and methods have changed. I've also included a simple test to prove it out so you should be able to just cut and paste this into a playground a run it.
import Foundation
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var task: [Assignment]?
func encode(with coder: NSCoder) {
coder.encode(deptName, forKey: "deptName")
coder.encode(managerName, forKey: "mngName")
coder.encode(task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
deptName = aDecoder.decodeObject(forKey: "deptName") as! String
managerName = aDecoder.decodeObject(forKey: "mngName") as! String
task = aDecoder.decodeObject(forKey: "taskArray") as? [Assignment]
}
override init() {
super.init()
}
convenience init(deptName: String, managerName: String, task: [Assignment]?) {
self.init()
self.deptName = deptName
self.managerName = managerName
self.task = task
}
}
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encode(with coder: NSCoder) {
// Methods
coder.encode(title, forKey: "Title")
coder.encode(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObject(forKey: "Title") as! String
notes = aDecoder.decodeObject(forKey: "notepad") as! String
super.init()
}
override init() {
super.init()
}
convenience init(title: String, notes: String) {
self.init()
self.title = title
self.notes = notes
}
}
// Create some data for testing
let assignment1 = Assignment(title: "title 1", notes: "notes 1")
let assignment2 = Assignment(title: "title 2", notes: "notes 2")
let myDepartment = Department(deptName: "My Dept", managerName: "My Manager", task: [assignment1, assignment2])
// Try archive and unarchive
do {
// Archive
let data = try NSKeyedArchiver.archivedData(withRootObject: myDepartment, requiringSecureCoding: false)
print ("Bytes in archive: \(data.count)")
// Unarchive
let obj = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! Department
// Print the contents of the unarchived object
print("Department: \(obj.deptName) Manager: \(obj.managerName)")
if let task = obj.task {
for i in 0...task.count-1 {
print("Task: \(task[i].title) \(task[i].notes)")
}
}
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
Enjoy

Passing Multiple Objects To WatchKit In A NSUserDefaults

With the help of some great tutorials and users here, I've had success implementing SwiftyJSON in my app and getting a basic WatchKit app built alongside. My last hurdle to pass is getting my whole set of parsed JSON data to be passed to WatchKit, as to allow me to choose from a cell in a TableView and pull up more specific detail on a piece of criteria.
I'm parsing JSON data in my Minion.swift file, like so;
import UIKit
class Minion {
var name: String?
var age: String?
var height: String?
var weight: String?
class func fetchMinionData() -> [Minion] {
let dataURL = NSURL(string: "http://myurl/json/")
var dataError: NSError?
let data = NSData(contentsOfURL: dataURL!, options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &dataError)
let minionJSON = JSONValue(data)
var minions = [Minion]()
for minionDictionary in minionJSON {
minions.append(Minion(minionDetails: minionDictionary))
}
return minions
}
init(minionDetails: JSONValue) {
name = minionDetails["san"].string
age = minionDetails["age"].string
height = minionDetails["height"].string
weight = minionDetails["free"].string
}
}
For my iOS app, this is working well to populate my UITableView and subsequent Detail View. I have my ViewController.Swift like so;
import UIKit
class ViewController: UITableViewController {
let minions: [Minion]
required init(coder aDecoder: NSCoder!) {
minions = Minion.fetchMinionData()
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let defaults = NSUserDefaults(suiteName: "group.com.mygroup.data")
let key = "dashboardData"
defaults?.setObject(minions, forKey: key)
defaults?.synchronize()
}
// MARK: Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
I've truncated much of the code as I don't believe it's relevant to WatchKit. In the WatchKit extension, I have my InterfaceController.swift like so;
import WatchKit
class InterfaceController: WKInterfaceController {
#IBOutlet weak var minionTable: WKInterfaceTable!
let defaults = NSUserDefaults(suiteName: "group.com.mygroup.data")
var dashboardData: String? {
defaults?.synchronize()
return defaults?.stringForKey("dashboardData")
}
let minions = ???
When I run the iOS app, it throws me the error "Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')" because I am passing the whole set of JSON data as "minions." If I set my NSUserDefaults key to "minions[0].name" it will pass the single string, but passing the whole set of data so the WatchKit table can allow me to choose a row seems to be evading me.
In advance, as always, I am most grateful.
Your Minion class need to implement the NSCoding. Then in your view controller you need to transfer your Minion object to NSData object.
class Minion: NSObject, NSCoding {
.....
init(coder aDecoder: NSCoder!) {
aCoder.encodeObject(name, forKey: "name")
aCoder.encodeObject(age, forKey: "age")
aCoder.encodeObject(height, forKey: "height")
aCoder.encodeObject(weight, forKey: "weight")
}
func encodeWithCoder(aCoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as String
age = aDecoder.decodeObjectForKey("age") as String
height = aDecoder.decodeObjectForKey("height") as String
weight = aDecoder.decodeObjectForKey("weight") as String
}
}
In your ViewController class:
NSKeyedArchiver.setClassName("Minion", forClass: Minion.self)
defaults?.setObject(NSKeyedArchiver.archivedDataWithRootObject(minions), forKey: "minions")
If you want to retrieve the data from NSUserDefaults:
if let data = defaults?.objectForKey("minions") as? NSData {
NSKeyedUnarchiver.setClass(Minion.self, forClassName: "Minion")
let minions = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [Minion]
}

How to construct convenience init? in Swift [duplicate]

With the following code I try to define a simple model class and it's failable initializer, which takes a (json-) dictionary as parameter. The initializer should return nil if the user name is not defined in the original json.
1.
Why doesn't the code compile? The error message says:
All stored properties of a class instance must be initialized before returning nil from an initializer.
That doesn't make sense. Why should I initialize those properties when I plan to return nil?
2.
Is my approach the right one or would there be other ideas or common patterns to achieve my goal?
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
if let value: String = dictionary["user_name"] as? String {
userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
}
}
That doesn't make sense. Why should I initialize those properties when
I plan to return nil?
According to Chris Lattner this is a bug. Here is what he says:
This is an implementation limitation in the swift 1.1 compiler,
documented in the release notes. The compiler is currently unable to
destroy partially initialized classes in all cases, so it disallows
formation of a situation where it would have to. We consider this a
bug to be fixed in future releases, not a feature.
Source
EDIT:
So swift is now open source and according to this changelog it is fixed now in snapshots of swift 2.2
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
Update: From the Swift 2.2 Change Log (released March 21, 2016):
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
For Swift 2.1 and earlier:
According to Apple's documentation (and your compiler error), a class must initialize all its stored properties before returning nil from a failable initializer:
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Note: It actually works fine for structures and enumerations, just not classes.
The suggested way to handle stored properties that can't be initialized before the initializer fails is to declare them as implicitly unwrapped optionals.
Example from the docs:
class Product {
let name: String!
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
In the example above, the name property of the Product class is
defined as having an implicitly unwrapped optional string type
(String!). Because it is of an optional type, this means that the name
property has a default value of nil before it is assigned a specific
value during initialization. This default value of nil in turn means
that all of the properties introduced by the Product class have a
valid initial value. As a result, the failable initializer for Product
can trigger an initialization failure at the start of the initializer
if it is passed an empty string, before assigning a specific value to
the name property within the initializer.
In your case, however, simply defining userName as a String! does not fix the compile error because you still need to worry about initializing the properties on your base class, NSObject. Luckily, with userName defined as a String!, you can actually call super.init() before you return nil which will init your NSObject base class and fix the compile error.
class User: NSObject {
let userName: String!
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
super.init()
if let value = dictionary["user_name"] as? String {
self.userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
self.isSuperUser = value
}
self.someDetails = dictionary["some_details"] as? Array
}
}
I accept that Mike S's answer is Apple's recommendation, but I don't think it's best practice. The whole point of a strong type system is to move runtime errors to compile time. This "solution" defeats that purpose. IMHO, better would be to go ahead and initialize the username to "" and then check it after the super.init(). If blank userNames are allowed, then set a flag.
class User: NSObject {
let userName: String = ""
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: [String: AnyObject]) {
if let user_name = dictionary["user_name"] as? String {
userName = user_name
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
if userName.isEmpty {
return nil
}
}
}
Another way to circumvent the limitation is to work with a class-functions to do the initialisation.
You might even want to move that function to an extension:
class User: NSObject {
let username: String
let isSuperUser: Bool
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
super.init()
}
}
extension User {
class func fromDictionary(dictionary: NSDictionary) -> User? {
if let username: String = dictionary["user_name"] as? String {
let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
let someDetails = dictionary["some_details"] as? [String]
return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
}
return nil
}
}
Using it would become:
if let user = User.fromDictionary(someDict) {
// Party hard
}
Although Swift 2.2 has been released and you no longer have to fully initialize the object before failing the initializer, you need to hold your horses until https://bugs.swift.org/browse/SR-704 is fixed.
I found out this can be done in Swift 1.2
There are some conditions:
Required properties should be declared as implicitly unwrapped optionals
Assign a value to your required properties exactly once. This value may be nil.
Then call super.init() if your class is inheriting from another class.
After all your required properties have been assigned a value, check if their value is as expected. If not, return nil.
Example:
class ClassName: NSObject {
let property: String!
init?(propertyValue: String?) {
self.property = propertyValue
super.init()
if self.property == nil {
return nil
}
}
}
A failable initializer for a value type (that is, a structure or
enumeration) can trigger an initialization failure at any point within
its initializer implementation
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/sg/jEUH0.l
You can use convenience init:
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
}
convenience init? (dict: NSDictionary) {
guard let userName = dictionary["user_name"] as? String else { return nil }
guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
guard let someDetails = dictionary["some_details"] as? [String] else { return nil }
self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
}
}