Convenience initializer with non-optional property - swift

An object of mine has an integer ID. Since this is a required property I am not defining it as an optional and I am requiring it in the designated initializer:
class Thing {
var uniqueID: Int
var name: String?
init (uniqueID: Int) {
self.uniqueID = uniqueID
}
}
Since I am creating one of these from some JSON, the usage is along the lines of:
if let uniqueID = dictionary["id"] as? Int {
let thing = Thing(uniqueID: unique)
}
Next, I would like to be able to add a convenience initializer to the Thing class that accepts the dictionary object and sets the properties accordingly. This includes the required uniqueID and some other optional properties. My best effort so far is:
convenience init (dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
//set other values here?
}
//or here?
}
But of course this isn't sufficient since the designated initializer isn't called on all paths of the conditional.
How should I be handling this scenario? Is it even possible? Or should I accept that uniqueID must be an optional?

You have a couple of options with this one. One is a failable initialisers:
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: -1)
return nil
}
}
Technically this can be tweaked a bit (mainly depending on your preference/version of swift), but my person preference is something as follows:
class func fromDictionary(dictionary: [String: AnyObject]) -> Thing? {
if let uniqueID = dictionary["id"] as? Int {
return self.init(uniqueID: uniqueID)
}
return nil
}
All together, as a playground:
class Thing {
var uniqueID: Int
var name: String?
init(uniqueID: Int) {
self.uniqueID = uniqueID
}
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: -1)
return nil
}
}
class func fromDictionary(dictionary: [String: AnyObject]) -> Thing? {
if let uniqueID = dictionary["id"] as? Int {
return self.init(uniqueID: uniqueID)
}
return nil
}
}
let firstThing = Thing(uniqueID: 1)
let secondThing = Thing(dictionary: ["id": 2])
let thirdThing = Thing(dictionary: ["not_id": 3])
let forthThing = Thing.fromDictionary(["id": 4])
let fithThing = Thing.fromDictionary(["not_id": 4])

The best solution is probably to use a failable initializer, which will either return an instantiated object or nil.
Because Swift objects cannot be partially constructed and convenience initializers must call a non-convenience initializer, we must still do something in the failure case.
The result will look something like this:
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: 0)
return nil
}
}
Generally speaking, our non-convenience initializer(s) should be one that accepts all arguments, and convenience initializers should be methods which don't require some of the arguments.
For example, I might make my default initializer look like this:
init(uniqueID: Int, name: String? = nil) {
self.uniqueID = uniqueID
self.name = name
}
This allows us to call the constructor in several different ways:
let thing1 = Thing(1)
let thing2 = Thing(2, nil)
let thing3 = Thing(3, "foo")
let thing4 = Thing(4, myUnwrappedStringVar)
let thing5 = Thing(5, myWrappedStringOptional)
And that already covers a lot of use cases for us.
So, let's add another convenience initializer that accepts an optional Int.
convenience init?(uniqueID: Int? = nil, name: String? = nil) {
if let id = uniqueID {
self.init(uniqueID: id, name: name)
} else {
self.init(uniqueID: 0)
return nil
}
}
Now we can take an Int? for our uniqueID argument and just fail when it's nil.
So, one more to accept the dictionary.
convenience init?(dictionary: [String: AnyObject]) {
let uniqueID = dictionary["id"] as? Int
let name = dictionary["name"] as? String
self.init(uniqueID: uniqueID, name: name)
}
We still have the slightly weird initialize then return nil pattern in our first convenience constructor, but everything else we build on top of this can simply call that convenience initializer and doesn't require the weird pattern.
In the initializer that takes the dictionary, if there's no id key, or if it's something that's not an Int, then the let uniqueID will be nil, so when we call the other constructor, it will call the one that accepts an Int?, be passed nil, return nil, and therefore the one we called will return nil.

Related

Dynamic protocol conformance in Swift

Hi I am struggle to solve the problem dynamic protocol conformance in swift language. Please see code.
Protocol:
protocol Object {
init(by object: [String: Any])
}
Custom structs with protocol object conformance:
struct Tree: Object {
let treeName: String
init(by object: [String: Any]) {
self.treeName = object["tree"] as? String ?? "Notree"
}
}
struct Plant: Object {
let plantName: String
init(by object: [String : Any]) {
self.plantName = object["tree"] as? String ?? ""
}
}
The above code just fine until the object is [String: Any]. I can't use [[String: Any]] like below.
let coconut = ["tree":"Coconut"] // => This fine
let allTrees = [["tree":"Apple"],["tree":"Orange"],["tree":"Jakfruit"]] //=> Here is the problem
let aTree = Tree(by: coconut)
let bTree = Tree(by: ["data":allTrees])
let cTree = Plant(by: ["data":allTrees])
I can't use array of objects. So, I used to store objects in to key "data". Now I used extension: Array confirm protocol object.
extension Array: Object where Element == Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) -> Object in
// return Plant.init(by: object) // => Works, But I need dynamic confirmance
// return Tree.init(by: object) // => Works, But I need dynamic confirmance
return Object.init(by: object) //=> How can I do?
})
}else{
self = []
}
}
}
The return Object shows error Protocol type 'Object' cannot be instantiated. I tried lot to solve but not able.
Can someone suggest better idea or solution for this problem? Thank you in advance...
First, you should not use the constraint == Object. You want to say that not only [Object] is an Object, but also [Plant] and [Tree] are Objects too, right? For that, you should use the : Object constraint. Second, you can use Element.init to initialise a new Element of the array. Because of the constraint Element : Object, we know that a init(by:) initialiser exists:
extension Array: Object where Element: Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) in
return Element.init(by: object)
})
}else{
self = []
}
}
}
Usage:
let trees = [Tree](by: ["data": allTrees])
Here's what I think a more Swifty version of your code, making use of failable initialisers - initialisers that return nil when they fail to initialise the object:
protocol Object {
init?(by object: [String: Any])
}
struct Tree: Object {
let treeName: String
init?(by object: [String: Any]) {
if let treeName = object["tree"] as? String {
self.treeName = treeName
} else {
return nil
}
}
}
struct Plant: Object {
let plantName: String
init?(by object: [String : Any]) {
if let plantName = object["tree"] as? String {
self.plantName = plantName
} else {
return nil
}
}
}
extension Array: Object where Element: Object{
init?(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.compactMap(Element.init)
}else{
return nil
}
}
}

How to use protocols for stucts to emulate classes inheritance

I'm implementing a model:
It has structs ClientSummary and ClientDetails
ClientDetails struct has all properties of ClientSummary struct + some extra properties
Both structs have main initializer init(jsonDictionary: [String: Any])
inits of ClientSummary and ClientDetails share big part of the code
There is an extension which will work with shared functionality of those structs.
The most straightforward solution which came to my mind is just classic inheritance, but it doesn't work for value types.
I'm trying to solve that with protocols, but I can't implement those "shared inits". I was trying to move shared part of the init to the protocol extension but can't really make it. There are various errors.
Here is the test code.
protocol Client {
var name: String { get }
var age: Int { get }
var dateOfBirth: Date { get }
init?(jsonDictionary: [String: Any])
}
struct ClientSummary: Client {
let name: String
let age: Int
let dateOfBirth: Date
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
}
}
struct ClientDetails: Client {
let name: String
let age: Int
let dateOfBirth: Date
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
visitHistory = [Date(): "Test"]
}
}
extension Client {
// A lot of helper methods here
var stringDOB: String {
return formatter.string(from: dateOfBirth)
}
}
Inheritance is the wrong tool here. It doesn't make sense to say "details IS-A summary." Details are not a kind of summary. Step away from the structural question of whether they share a lot of methods, and focus on the essential question of whether one is a kind of the other. (Sometimes renaming things can make that true, but as long as they're "summary" and "detail" it doesn't make sense to inherit.)
What can make sense is to say that details HAS-A summary. Composition, not inheritance. So you wind up with something like:
struct ClientDetails {
let summary: ClientSummary
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let summary = ClientSummary(jsonDictionary: jsonDictionary) else {
return nil
}
self.summary = summary
visitHistory = [Date(): "Test"]
}
// You can add these if you need them, or to conform to Client if that's still useful.
var name: String { return summary.name }
var age: Int { return summary.age }
var dateOfBirth: Date { return summary.dateOfBirth }
}
I often wish that Swift had a built-in way to separate out parts of init methods. However, it can be done, admittedly somewhat awkwardly, with tuples, as below:
struct S {
let foo: String
let bar: Int
let baz: Bool
init() {
(self.foo, self.bar, self.baz) = S.sharedSetup()
}
static func sharedSetup() -> (String, Int, Bool) {
...
}
}
In your case, the sharedSetup() method can be moved to the protocol extension, or wherever it's convenient to have it.
For structs you can use composition instead of relying on inheritance. Let's suppose you already have ClientSummary struct defined with the Client protocol:
protocol Client {
var name: String { get }
var age: Int { get }
var dateOfBirth: Date { get }
init?(jsonDictionary: [String: Any])
}
struct ClientSummary: Client {
let name: String
let age: Int
let dateOfBirth: Date
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
}
}
Now to create ClientDetails sharing ClientSummary logic you can just create a ClientSummary property in ClientDetails. This way have the same initializer as ClientSummary with your additional type specific logic and with use of dynamicMemberLookup you can access ClientSummary properties on ClientDetails type:
#dynamicMemberLookup
struct ClientDetails {
var summary: ClientSummary
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let summary = ClientSummary(jsonDictionary: jsonDictionary) else {
return nil
}
self.summary = summary
visitHistory = [Date(): "Test"]
}
subscript<T>(dynamicMember path: KeyPath<ClientSummary, T>) -> T {
return summary[keyPath: path]
}
subscript<T>(dynamicMember path: WritableKeyPath<ClientSummary, T>) -> T {
get {
return summary[keyPath: path]
}
set {
summary[keyPath: path] = newValue
}
}
subscript<T>(dynamicMember path: ReferenceWritableKeyPath<ClientSummary, T>) -> T {
get {
return summary[keyPath: path]
}
set {
summary[keyPath: path] = newValue
}
}
}
There is an extension which will work with shared functionality of those structs.
Now sharing code between ClientSummary and ClientDetails is tricky. By using dynamicMemberLookup you will be able to access all the properties in ClientSummary from ClientDetails but methods from ClientSummary can't be invoked this way. There is proposal to fulfill protocol requirements with dynamicMemberLookup which should allow you to share methods between ClientSummary and ClientDetails for now you have to invoke ClientSummary methods on ClientDetails using the summary property.

enum method returning a dynamic type

I have an enum and I'd like to create a method to return a different type for every case.
For example, I have a dictionary [String: Any]. To process the values I'm using the enum to create an array of keys:
enum Foo {
case option1
case option2
func createKey() -> [String] {
switch self {
case .option1: return ["scenario1"]
case .option2: return ["scenario2"]
}
}
}
Once I have the values, I need to cast them to a the proper type to be able to use them. Right now I'm doing it manually using if-statements but it would reduce a lot of code if I can somehow create a method in the enum to return the proper type. My current code:
let origin: [String: Any] = ["scenario2": "someText"]
let option: Foo = .option2
option.createKey().forEach {
guard let rawValue = origin[$0] else { return }
switch option {
case .option1:
guard let value = rawValue as? Int else { return }
print("Value is an Int:", value)
case .option2:
guard let value = rawValue as? String else { return }
print("Value is a String:", value)
}
}
What I would like to achieve is something like:
option.createKey().forEach {
guard let rawValue = origin[$0] as? option.getType() else { return }
}
Is this possible?
I think the core of the problem here is that Swift has strict typing. That means types must be known at compile time. This, obviously, is legal:
let s : Any = "howdy"
if let ss = s as? String {
print(ss)
}
But this is not legal:
let s : Any = "howdy"
let someType = String.self
if let ss = s as? someType { // *
print(ss)
}
someType must be a type; it cannot be a variable hiding a type inside itself. But that is precisely what, in effect, you are asking to do.

Cannot downcast object of type Any to Int when accessing from dictionary

I have a Gfycat struct that represents the data I want to store after making a network call to the Gfycat API.
typealias JSONDictionary = [String: Any]
struct Gfycat {
let id: String
let number: Int
}
In an extension to the Gfycat struct, I wrote a failable initializer that takes a dictionary of type [String: Any] as its argument. This dictionary is then used to assign values to the struct's properties. This is the original init method I wrote:
extension Gfycat {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["gfyId"] as? String,
let number = dictionary["gfyNumber"] as? Int { return nil }
self.id = id
self.number = number
}
}
The problem is that when accessing a value from the dictionary, I cannot downcast the value from Any to Int. I must first downcast Any to String, then convert that string to Int. Is this a bug or rather a feature of Swift that I don't understand?
This was my solution:
extension Gfycat {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["gfyId"] as? String,
let uncastedNumber = dictionary["gfyNumber"] as? String,
let number = Int(uncastedNumber) else { return nil }
self.id = id
self.number = number
}
}
I must first downcast Any to String, then convert that string to Int. Is this a bug or rather a feature of Swift that I don't understand?
It's neither a bug nor a feature of Swift. It's a fact about the dictionary you're working with. This thing is a String, not an Int. So you cannot cast it to an Int.

cannot return String in function

I am having trouble casting an option AnyObject into a string. Whenever I try to call the fuction my program crashes with (lldb). This is the function.
func name() -> String {
print(attributes["name"])
print(attributes["name"]! as! String)
let name = attributes["name"]! as! String
return name
}
The output from the prints is:
Optional(Optional(Josh))
Josh
(lldb)
Thanks in advance for your help!
Lets say attributes is defined as follow
var attributes: NSMutableDictionary? = NSMutableDictionary()
and can be populated like follow
attributes?.setValue("Walter White", forKey: "name")
Optionals
You should design the name() function to return a String or nil (aka String? which is an Optional type)
func name() -> String? {
guard let
attributes = attributes,
name = attributes["name"] as? String else { return nil }
return name
}
The same logic can also be written this way
func name() -> String? {
return attributes?["name"] as? String
}
Now if a valid String value is found inside attributes with key name then it is returned. Otherwise the function does return nil.
Invoking the function
When using the function you should unwrap the result like this
if let name = name() {
print(name) // prints "Walter White"
}
In all these examples, attributes is defined as:
var attributes: AnyObject? = ["name": "Josh"]
Looks like the crash occurs due to type-safety issues. Try:
func name() -> String? {
if let name = attributes!["name"] as? String {
return name
}
return nil
}
Another option, which is slightly swiftier:
func name() -> String? {
guard let name = attributes!["name"] as? String else { return nil }
return name
}
Yet another option that would be using a block for the function, so that it doesn't return anything if attributes doesn't contain a key "name":
func name(block: ((text: String?) -> Void)) {
guard let name = attributes!["name"] as? String else { return }
return block(text: name)
}
// Usage:
name { text in
print(text!)
}
Prints:
Josh
if let _string = attributes["name"] as? String {
return _string
}
// fallback to something else, or make the method signature String?
return ""
When working with optionals, you don't want to just wrap things with exclamation points. If the value ever ended up not being a string, or not being there at all in the map, you're code would fail hard and potentially crash your application.
If you need a non-optional String, consider returning an empty string as a fallback method and using the if let pattern to return the optional string if it is available.
-- EDIT --
Not sure about the downvote... Here it is in a playground.
var attributes = [String:AnyObject]()
attributes["name"] = "test"
func name() -> String {
print(attributes["name"])
print(attributes["name"]! as! String)
let name = attributes["name"]! as! String
return name
}
// does not compile
//print(name())
func name2() -> String {
if let _string = attributes["name"] as? String {
return _string
}
// fallback to something else, or make the method signature String?
return ""
}
// prints test
print(name2())