How to use protocols for stucts to emulate classes inheritance - swift

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.

Related

Swift: Insert codable object into Core Data

I'm getting a response from an API and decoding the response like this:
struct MyStuff: Codable {
let name: String
let quantity: Int
let location: String
}
And I have instance an Entity to map MyStuff:
#objc(Stuff)
public class Stuff: NSManagedObject {
}
extension Stuff {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Stuff> {
return NSFetchRequest<Stuff>(entityName: "Stuff")
}
#NSManaged public var name: String?
#NSManaged public var quantity: Int64
#NSManaged public var location: String?
}
My question is, when I have the response of type MyStuff there is a way to loop thru the keys and map the values to core data?
for example:
let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")
let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")
for chidren in Mirror(reflecting: myStuff).children {
print(chidren.label)
print(chidren.value)
/*
insert values to core data
*/
}
I'll really appreciate your help
A smart solution is to adopt Decodable in Stuff
Write an extension of CodingUserInfoKey and JSONDecoder
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")!
}
extension JSONDecoder {
convenience init(context: NSManagedObjectContext) {
self.init()
self.userInfo[.context] = context
}
}
In Stuff adopt Decodable and implement init(from:), it must be implemented in the class, not in the extension
#objc(Stuff)
public class Stuff: NSManagedObject, Decodable {
private enum CodingKeys: String, CodingKey { case name, quantity, location }
public required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Error: context doesn't exist!") }
let entity = NSEntityDescription.entity(forEntityName: "Stuff", in: context)!
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
quantity = try values.decodeIfPresent(Int64.self, forKey: .quantity) ?? 0
location = try values.decodeIfPresent(String.self, forKey: .location)
}
}
To decode the JSON you have to initialize the decoder with the convenience initializer
let decoder = JSONDecoder(context: context)
where context is the current NSManagedObjectContext instance.
Now you can create Stuff instances directly from the JSON.
You can store entire object as JSONString if you don't support query for each field.
If you need query for some field then keep that field in entity object.
struct MyStuff: Codable {
let name: String
let quantity: Int
let location: String
}
extension Encodable {
func toString() -> String? {
if let config = try? JSONEncoder().encode(self) {
return String(data: config, encoding: .utf8)
}
return .none
}
}
extension Decodable {
static func map(JSONString: String) -> Self? {
try? JSONDecoder().decode(Self.self, from: JSONString.data(using: .utf8) ?? .init())
}
}
#objc(Stuff)
public class Stuff: NSManagedObject {
}
// Entity with single field (no field base query support)
extension Stuff {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Stuff> {
return NSFetchRequest<Stuff>(entityName: "Stuff")
}
#NSManaged public var myStuffRawJSON: String?
func mapToMyStuff() -> MyStuff? {
MyStuff.map(JSONString: myStuffRawJSON ?? "")
}
}
How to use:
let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")
let entity: Stuff //Create entity
entity.myStuffRawJSON = myStuff.toString()
// save your entity

Swift - Return type of struct parameters without default values

I'm very new in Swift so i might be missing some basics.
I have struct:
struct MyStruct {
var a: Int
var b: String
var c: Bool
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
}
and function, that should iterate through given struct properties and return their types in json:
func structProps(){
let elm = MyStruct()
let mirror = Mirror(reflecting: elm)
var exampleDict: [String: String] = [:]
for child in mirror.children {
exampleDict[child.label!] = String(describing:type(of: child.value)) as String
}
if let theJSONData = try? JSONSerialization.data(
withJSONObject: exampleDict,
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
}
}
it kinda return what i need:
JSON string = {"a":"Int","b":"String","c":"Bool"}
Because i'm having a lot of structs and i want to export json from all of them, i'm wondering if there is a way to have generic initializer. Without passing default values.
It means without
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
If I understand correctly , you can create a base protocol and add as an extension to your structures like
protocol BaseFunction {
func getElements() -> [String : String]
func getDict() -> String
}
extension BaseFunction {
func getElements() -> [String : String] {
let mirror = Mirror(reflecting: self)
let propertiesRemoveNil = mirror.children.filter({!(($0.value as AnyObject) is NSNull)})
let properties = propertiesRemoveNil.compactMap({$0.label})
var types = [String]()
_ = mirror.children.forEach({
types.append(String(describing:type(of: $0.value)))
})
return Dictionary(uniqueKeysWithValues: zip(properties, types))
}
func getDict() -> String{
if let theJSONData = try? JSONSerialization.data(
withJSONObject: getElements(),
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
return theJSONText ?? ""
}
return ""
}
}
And usage :
func structProps(){
let elm = MyStruct()
print(elm.getDict())
}
OUTPUT :
{"a":"Int","b":"String","c":"Bool"}
I was going to write a comment about using a protocol but I thought it would be easier to understand as an answer with some code.
To make the usage more generic so you don't need specific code for each type of struct you should use a protocol. Instead of having an init that might clash with already existing init in the struct I prefer a static method that returns a new object, also known as a factory method
protocol PropertyExtract {
static func createEmpty() -> PropertyExtract
}
Then we can make use of the default init for the struct or any supplied to create an object with some initial values by letting the struct conform to the protocol in an extension
extension MyStruct: PropertyExtract {
static func createEmpty() -> PropertyExtract {
MyStruct(a: 0, b: "", c: false)
}
}
And instead of hardcoding or passing a specific type of object to the encoding function we pass the type of it
func structProps(for structType: PropertyExtract.Type)
and use the protocol method to get an instance of the type
let object = structType.createEmpty()
The whole function (with some additional changes)
func structProps(for structType: PropertyExtract.Type) -> String? {
let object = structType.createEmpty()
let mirror = Mirror(reflecting: object)
let exampleDict = mirror.children.reduce(into: [String:String]()) {
guard let label = $1.label else { return }
$0[label] = String(describing:type(of: $1.value))
}
if let data = try? JSONEncoder().encode(exampleDict) {
return String(data: data, encoding: .utf8)
}
return nil
}
And this is then called with the type of the struct
let jsonString = structProps(for: MyStruct.self)
According to Creating a Swift Runtime Library, there is a way to access meta data types without initializer.
Solution for what i was asking for is possible with Runtime library.
Mirror(reflecting:) expects an instance of a type and not a type itself, ref.
One idea is to have a generic function that works with all types conforming to a protocol that provides an empty init. Something like:
protocol EmptyInitializable {
init()
}
struct StructOne {
let a: Bool
let b: String
}
struct StructTwo {
let c: Int
let d: Float
}
extension StructOne: EmptyInitializable {
init() {
a = false
b = ""
}
}
extension StructTwo: EmptyInitializable {
init() {
c = 1
d = 1.0
}
}
func print(subject: EmptyInitializable) -> String? {
let dictionary = Dictionary(uniqueKeysWithValues:
Mirror(reflecting: subject).children.map {
($0.label!, String(describing: type(of: $0.value)))
}
)
return (try? JSONSerialization.data(withJSONObject: dictionary)).flatMap {
String(data: $0, encoding: .utf8)
}
}
print(subject: StructOne()) // "{"a":"Bool","b":"String"}"
print(subject: StructTwo()) // "{"d":"Float","c":"Int"}"
You still have to implement the empty init and assign some values and I'm afraid there's no way currently to avoid that.

Swift Encoding: possible to have variable keys?

Say I have a struct:
struct Animal {
var age: Int
var type: String
}
I know I can define my custom coding keys but what I want is, when I encode a struct into a JSON, instead of:
{
"age": 12
"type": "dog"
}
I want it be:
{
"12": "dog"
}
But I'm not sure if this is possible with the built-in encoding mechanism?
If you want to use the Encodable protocol to do this, you're going to need to define a custom key type that will allow you to represent the value variably, and write encode(to:) yourself:
struct Animal : Encodable {
var age: Int
var type: String
private struct CodingKeys : CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: CodingKeys(intValue: age)!)
}
}
I have to reiterate what #Rob said, which is that this is a terrible idea, but if this is the format you're required to encode to, it's still possible.
You can Custom function for Json:
struct Animal {
var age: Int
var type: String
var jsonString: String {
return "{\"\(age)\": \"\(type)\"}"
}
var jsonObjc: Any? {
if let data = jsonString.data(using: String.Encoding.utf8) {
return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
}
return nil
}
}
let animal = Animal(age: 1, type: "dog")
print(animal.jsonString)
print(animal.jsonObjc ?? "nil")
print content:
{"1": "dog"}
{
1 = dog;
}
hope help for you.

Swift 2.2 singleton

I am new in Swift. I am trying to parse some JSON data from web service and want a singleton class of user.But I got stuck to create the singleton. Here is my code:
import Foundation
class User {
private var success: String
private var userId: String
private var name: String
private var gender: String
private var email: String
private var userObject = [User]()
class var sharedInstane:User {
struct Singleton {
static var onceToken: dispatch_once_t = 0
static var instance:User? = nil
}
dispatch_once(&Singleton.onceToken){
Singleton.instance = User()
}
return Singleton.instance!
}
private init(success: String, userId: String, name: String, gender: String, email: String)
{
self.success = success
self.userId = userId
self.name = name
self.gender = gender
self.email = email
}
convenience init(dictionary: [String:AnyObject]) {
let success = dictionary["success"] as? String
let userId = dictionary["userId"] as? String
let name = dictionary["name"] as? String
let gender = dictionary["gender"] as? String
let email = dictionary["email"] as? String
self.init(success: success!, userId: userId!, name: name!, gender: gender!, email: email!, )
}
func callWebserviceToLoadUserInfo (url:String, param:[String:AnyObject],completeHandler:(Bool?,String) -> ())
{
let connection = ServerConnection()
connection.getJSONDataForWebService(url, params: param) { (response, error) in
// code goes here
var responseDict = response as! [String : AnyObject]
responseDict = responseDict["responseDict"] as! [String : AnyObject]
if responseDict["success"] as! String == "1" {
for dict in responseDict {
let user = User(dictionary: (dict as! [String:AnyObject]))
self.userObject.append(user)
}
print("user : \(self.userObject[0].name)")
}else{
// error goes here
}
}
}
}
Can any one please help me how should I do this code?
The singleton in the single line sample code.
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
For more details.
Using Krakendev's single-line singleton code, cited by Maheshwar, and turning your convenience init into an instance function to be called with User.sharedInstance.initialize(dictionary):
import Foundation
class User {
// Here you declare all your properties
// "private var" and all that jazz
static let sharedInstance = User()
private init() {
// If you have something to do at the initialization stage
// you can add it here, as long as it does not involve
// arbitrary values that you would pass as parameters.
}
func initialize(dictionary: [String:AnyObject]) {
// Transfer the values of the dictionary to each `self.property`.
// Be careful while using `as?` as you may have to deal with
// optionals. No need to call `self.init` at the end, because
// this is now a regular `func`.
}
// Add the rest of your stuff here
}
One note about how you were working inside of that convenience initializer: if you do property = SomeClass.someMethod().someProperty as? SomeType, then property will be of type SomeType?, or Optional(SomeType). According to The Swift Programming Language,
The conditional form, as?, returns an optional value of the type you are trying to downcast to.
While User was not instantiated at least one time sharedInstance will return nil. After the first successful instantiation of the User, sharedInstance starts return it and that's became impossible to instantiate another one User as singleton pattern requires it. Consider this:
class User {
private static var sharedUser: User?
class var sharedInstance: User? {
return sharedUser
}
private init(success: String, userId: String, name: String, gender: String, email: String)
{
//User initialization code here
User.sharedUser = self
}
convenience init?(dictionary: [String:AnyObject]) {
guard User.sharedUser == nil else {
return nil
}
//dictionary parsing code is here
self.init(success: success!, userId: userId!, name: name!, gender: gender!, email: email!)
}
}
Client's code:
User.sharedUser
//return nil
let dict: [String:AnyObject] = ["success": "success", "userId":"userId", "name":"name", "gender":"gender","email":"email"]
User(dictionary: dict)
//creates User
User.sharedUser
//returns just created user
User(dictionary: dict)
//return nil
You should think about making this two classes, so that User is your model class and then create a manager to handle all the users (which seems to be your goal).
So in User remove the sharedInstane part and create a second singleton class, e.g. called UserManager, with the standard way to create a singleton in Swift. Then you can keep the way you're creating your user and in the end just assign it to the singleton:
class UserManager {
static let sharedInstance = UserManager()
var users = [User]()
}
// in your code:
...
for dict in responseDict {
let user = User(dictionary: (dict as! [String:AnyObject]))
UserManager.sharedInstance.users.append(user)
}
...

Convenience initializer with non-optional property

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.