Code Monkey home page Code Monkey logo

dekoter's Introduction

Dekoter

Build Status Version License Platform

Why You Might Be Interested

Fills a gap left by the missing NSCoding's support for Swift structs. If you've ever implemented NSCoding, Koting will be familiar to you as well.

How Much Familiar It Feels

A quick reminder how to implement NSCoding:

class Cat: NSObject, NSCoding {

    let name: String

    init(name: String) {
        self.name = name
    }

    // MARK: - NSCoding

    private struct Key {
        static let name = "name"
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: Key.name)
    }

    required convenience init?(coder aDecoder: NSCoder) {
        guard let name = aDecoder.decodeObject(forKey: Key.name) as? String else {
            return nil
        }
        self.init(name: name)
    }
}

Let's compare it to Koting:

struct Cat: Koting {

    let name: String

    // MARK: - Koting

    private struct Key {
        static let name = "name"
    }

    init?(koter: Koter) {
        guard let name: String = koter.dekotObject(forKey: Key.name) else {
            return nil
        }
        self.init(name: name)
    }

    func enkot(with koter: Koter) {
        koter.enkotObject(name, forKey: Key.name)
    }
}

Thus, not much different besides naming.

To summarize:

  • Add the Koting protocol to the class declaration.
  • Implement init?(koter:) and func enkot(with:).
  • Done!

Once it's done, the compiler is happy, and you can convert objects to Data and back.

let puss = Cat(name: "Puss")
let data = NSKeyedArchiver.de_archivedData(withRootObject: puss)
guard let againPuss: Cat = NSKeyedUnarchiver.de_unarchiveObject(with: data) else { return }

One More Example

This one is going to depict most of the Dekoter's features.

struct Cat {

    enum Sex: Int {
        case male
        case female
    }

    let name: String
    let surname: String?
    let sex: Sex
    let nationality: String
    let birthPlace: Place?

    // MARK: - Koting

    private struct Key {
        static let name = "name"
        static let surname = "surname"
        static let sex = "sex"
        static let nationality = "nationality"
        static let birthPlace = "birthPlace"
    }

    init?(koter: Koter) {
        guard let name: String = koter.dekotObject(forKey: Key.name),
            let nationality: String = koter.dekotObject(forKey: Key.nationality),
            let sexValue: Int = koter.dekotObject(forKey: Key.sex),
            let sex = Sex(rawValue: sexValue) else {

            return nil
        }
        let surname: String? = koter.dekotObject(forKey: Key.surname)
        let birthPlace: Place? = koter.dekotObject(forKey: Key.birthPlace)
        self.init(name: name, surname: surname, sex: sex, nationality: nationality, birthPlace: birthPlace)
    }

    func enkot(with koter: Koter) {
        koter.enkotObject(name, forKey: Key.name)
        koter.enkotObject(surname, forKey: Key.surname)
        koter.enkotObject(sex.rawValue, forKey: Key.sex)
        koter.enkotObject(nationality, forKey: Key.nationality)
        koter.enkotObject(birthPlace, forKey: Key.birthPlace)
    }
}

What We've Learned from It

  • It's okay to have optional properties.

As you can see, there're two optional properties. To encode them you don't do anything special, enkotObject(_, forKey:) takes optional as the first argument. For decoding you use dekotObject(forKey:) which also returns optional and it's up to you how whether you unwrap it or not.

  • Koter supports the same parameter types as NSCoding and additionally types which implement Koting.

In the example above Cat has an optional birthPlace property of a type Place.

  • There's only one method for encoding and one โ€“ for decoding.

Regardless the type, you use the same methods: enkotObject(_, forKey:) for encoding and dekotObject(forKey:) for decoding. These methods are generic, they derive a type based on the expected return value, that's why you should always explicitly specify it.

Features

Save an Object to UserDefaults

There are two methods implemented in a UserDefaults extension: de_set(_, forKey:) and de_object(forKey:)

let murzik = Cat(name: "Murzik", surname: nil, sex: .male, nationality: "GER", birthPlace: nil)
userDefaults.de_set(murzik, forKey: "cat")
let againMurzik: Cat? = userDefaults.de_object(forKey: "cat")

and

let sonya = Cat(name: "Sonya", surname: "Kryvonis", sex: .female, nationality: "UA", birthPlace: Place(country: "Ukraine", city: "Lviv"))
let puff: Cat = Cat(name: "Puff", surname: nil, sex: .female, nationality: "US", birthPlace: nil)
let cats = [ sonya, puff ]
userDefaults.de_set(cats, forKey: Key.cat)
guard let againCats: [Cat] = userDefaults.de_object(forKey: Key.cat) else { return }

Archive and Unarchive an Object

The library contains two extensions for NSKeyedArchiver and NSKeyedUnarchiver with methods for objects which implement the Koting protocol.

let emma = Cat(name: "Emma", surname: "Lambert", sex: .female, nationality: "FR", birthPlace: Place(country: "France", city: "Marseille"))
let data = NSKeyedArchiver.de_archivedData(withRootObject: emma)        
guard let againEmma: Cat = NSKeyedUnarchiver.de_unarchiveObject(with: data) else { return }

and

let sonya = Cat(name: "Sonya", surname: "Kryvonis", sex: .female, nationality: "UA", birthPlace: Place(country: "Ukraine", city: "Lviv"))
let puff: Cat = Cat(name: "Puff", surname: nil, sex: .female, nationality: "US", birthPlace: nil)
let cats = [ sonya, puff ]
let data = NSKeyedArchiver.de_archivedData(withRootObject: cats)
guard let againCats: [Cat] = NSKeyedUnarchiver.de_unarchiveObject(with: data) else { return }

JSON

A JSONSerialization extension makes deserialization from JSON very easy.

let oneCat: Cat? = JSONSerialization.de_jsonObject(with: oneCatData)
let cats: [Cat]? = JSONSerialization.de_jsonObject(with: catsData)

For structs which make use only of this feature there's no need to implement the Koting protocol (contains 2 methods), instead implement a Dekoting protocol (only 1 method).

Micromission

The library is small but proud of its mission, though the latter is also not that big. It's willing to serve developers as good as NSCoding does. Developers shouldn't feel lost and disappointed without a convenient tool to convert their Swift structs to Data and back.

Why Dekoter

You might have noticed a few cats here and there. There's a reason. "Kot" in some slavic languages means "cat", and it sounds similar to "code".

"enkot" -> "encode"

"dekot" -> "decode"

"koter" -> "coder"

"koting" -> "coding"

"dekoter" -> "decoder"

Installation

CocoaPods

Add pod 'Dekoter' similar to the following to your Podfile:

target 'MyApp' do
  pod 'Dekoter'
end

Then run a pod install inside your terminal, or from CocoaPods.app.

Collaboration

Dear friends, your help is more than welcome! There're multiple ways to support the project.

if you find a problem, or you know how to improve, or you have a question.

if you develop something important (previously filed as an issue).

if you want to share your either positive or negative experience using the library and have a hard time expressing it in a form of issue. Or, maybe, you don't want to make it publicly available.

I'm always happy to read an email from you.

License

It's available under the MIT license. See the LICENSE file for more info.

dekoter's People

Contributors

isuru-nanayakkara avatar letko-dmitry avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

dekoter's Issues

Release new version

Hi,
Could you release new version of the library? At least it'll fix the issue (#12) with collections and help to keep podfile where it is mentioned clean. Not sure if I can do it by myself.
Thanks a lot in advance.

Enkoting an array of structs causes a crash

Hi,

I have two simple structs like this.

import Foundation
import Dekoter

struct Shop {
    let id: Int
    let employees: [String]
    let fruits: [Fruit]
}

extension Shop: Koting {
    private struct Key {
        static let id = "Id"
        static let employees = "Employees"
        static let fruits = "Fruits"
    }
    
    public init?(koter: Koter) {
        guard let id: Int = koter.dekotObject(forKey: Key.id) else { return nil }
        guard let employees: [String] = koter.dekotObject(forKey: Key.employees) else { return nil }
        guard let fruits: [Fruit] = koter.dekotObject(forKey: Key.fruits) else { return nil }
        
        self.init(id: id, employees: employees, fruits: fruits)
    }
    
    public func enkot(with koter: Koter) {
        koter.enkotObject(id, forKey: Key.id)
        koter.enkotObject(employees, forKey: Key.employees)
        koter.enkotObject(fruits, forKey: Key.fruits)
    }
}

import Foundation
import Dekoter

struct Fruit {
    let id: Int
    let name: String
}

extension Fruit: Koting {
    private struct Key {
        static let id = "Id"
        static let name = "Name"
    }
    
    public init?(koter: Koter) {
        guard let id: Int = koter.dekotObject(forKey: Key.id) else { return nil }
        guard let name: String = koter.dekotObject(forKey: Key.name) else { return nil }
        
        self.init(id: id, name: name)
    }
    
    public func enkot(with koter: Koter) {
        koter.enkotObject(id, forKey: Key.id)
        koter.enkotObject(name, forKey: Key.name)
    }
}

I save one Shop object with 3 employees and 2 Fruit objects.

let fruit1 = Fruit(id: 1, name: "Apple")
let fruit2 = Fruit(id: 2, name: "Orange")
let shop = Shop(id: 1, employees: ["Max", "Susan", "John"], fruits: [fruit1, fruit2])
        
UserDefaults.standard.de_set(shop, forKey: "MyShop")
let myShop: Shop? = UserDefaults.standard.de_object(forKey: "MyShop")
print(myShop)

But when I try to save the shop object, it crashes with the following error.

-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x14688be0
2017-11-18 14:13:46.676966+0530 DekoteDemo[4171:1181357] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x14688be0'

It seems that the error is due to the let fruits: [Fruit] property in the Shop object. I can save the employees property which is a string array with no problem. But the fruits array causing the crash.

Not sure why it's happening. The Fruit object is also conforming to the Koting protocol. I uploaded a demo project here.

CocoaPods installing an old version

Hi,

I installed the library via CocoaPods like so pod 'Dekoter'. But it installed an older version (0.2.2).

I had to manually point to the repo like this pod 'Dekoter', :git => 'https://github.com/artemstepanenko/Dekoter.git' to get the latest 0.3.0 version.

Saving a class with Dekoter

Hi again,

I'm not sure if this is supported by this library. Or I'm just doing something wrong here. Anyway, I'll post my question.

I have two model classes. One is called Product which is a struct and the other one is a class called ListItem (This is a nested class by the way).

Product

public struct Product {
    public let id: Int
    public let categoryId: Int
    public let name: String
    public let imageUrl: String
}

extension Product: Koting {
    private struct Key {
        static let id = "id"
        static let categoryId = "categoryId"
        static let name = "name"
        static let imageUrl = "imageUrl"
    }
    
    public init?(koter: Koter) {
        guard let id: Int = koter.dekotObject(forKey: Key.id) else { return nil }
        guard let categoryId: Int = koter.dekotObject(forKey: Key.categoryId) else { return nil }
        guard let name: String = koter.dekotObject(forKey: Key.name) else { return nil }
        guard let imageUrl: String = koter.dekotObject(forKey: Key.imageUrl) else { return nil }
        
        self.init(id: id, categoryId: categoryId, name: name, imageUrl: imageUrl)
    }
    
    public func enkot(with koter: Koter) {
        koter.enkotObject(id, forKey: Key.id)
        koter.enkotObject(categoryId, forKey: Key.categoryId)
        koter.enkotObject(name, forKey: Key.name)
        koter.enkotObject(imageUrl, forKey: Key.imageUrl)
    }
}

ListItem

extension ShoppingListViewController {
    
    class ListItem: Koting {
        let id: Int
        let name: String?
        let product: Product?
        var isCheckedOff: Bool
        
        init(id: Int, name: String?, product: Product?, isCheckedOff: Bool) {
            self.id = id
            self.name = name
            self.product = product
            self.isCheckedOff = isCheckedOff
        }
        
        
        // MARK: - Koting
        private struct Key {
            static let id = "id"
            static let name = "name"
            static let product = "product"
            static let isCheckedOff = "isCheckedOff"
        }
        
        convenience required init?(koter: Koter) {
            guard let id: Int = koter.dekotObject(forKey: Key.id) else { return nil }
            guard let name: String = koter.dekotObject(forKey: Key.name) else { return nil }
            guard let product: Product = koter.dekotObject(forKey: Key.product) else { return nil }
            guard let isCheckedOff: Bool = koter.dekotObject(forKey: Key.isCheckedOff) else { return nil }
            
            self.init(id: id, name: name, product: product, isCheckedOff: isCheckedOff)
        }
        
        func enkot(with koter: Koter) {
            koter.enkotObject(id, forKey: Key.id)
            koter.enkotObject(name, forKey: Key.name)
            koter.enkotObject(product, forKey: Key.product)
            koter.enkotObject(isCheckedOff, forKey: Key.isCheckedOff)
        }
    }
    
}

The ListItem class has a property for a Product. I'm trying to save ListItems using Dekoter.

No errors are thrown when I'm saving ListItem objects. But when I try to retrieve the saved objects, I get an empty array back.

Demo project uploaded here.

'unarchiveTopLevelObjectWithData' is unavailable

Hi,

I installed the library on a Xcode 9 (beta 4) project via CocoaPods.

platform :ios, '9.0'

target 'DekoterDemo' do
  use_frameworks!

  pod 'Dekoter'

end

It installed the v0.3.0 of Dekoter. Then I built the project and I get the following compile error in the NSKeyedUnarchiver+Dekoter.swift file inside the de_unarchiveObject method.

'unarchiveTopLevelObjectWithData' is unavailable

Ambiguous reference to member 'de_object(forKey:)'

Hi,

I'm getting the error Ambiguous reference to member de_object(forKey:) when calling the de_object method on UserDefaults.

Here's my code.

I have the following Store struct.

import Foundation
import Dekoter

public struct Store {
    public let id: String
    public let name: String
    public let thumbnail: String
}

extension Store: Koting {
    private struct Key {
        static let id = "id"
        static let name = "name"
        static let thumbnail = "thumbnail"
    }
    
    public init?(koter: Koter) {
        guard let id: String = koter.dekotObject(forKey: Key.id) else { return nil }
        guard let name: String = koter.dekotObject(forKey: Key.name) else { return nil }
        guard let thumbnail: String = koter.dekotObject(forKey: Key.thumbnail) else { return nil }
        
        self.init(id: id, name: name, thumbnail: thumbnail)
    }
    
    public func enkot(with koter: Koter) {
        koter.enkotObject(id, forKey: Key.id)
        koter.enkotObject(name, forKey: Key.name)
        koter.enkotObject(thumbnail, forKey: Key.thumbnail)
    }
}

In a VC, I'm simply creating an instance from that struct. de_set method works fine. It's the de_object method that gives the above error.

let s = Store(id: "1", name: "Food City", thumbnail: "")
UserDefaults.standard.de_set(s, forKey: "Store")
if let s = UserDefaults.standard.de_object(forKey: "Store") as? Store { // Ambiguous reference to member 'de_object(forKey:)'
    print(s.name)
}

I have the v0.3.0 of the library installed via CocoaPods. I uploaded a demo project here as well.

Enkoting of array leads to incorrect result

Hi,

It seems there is misprint in the realization of the public func enkotObject(_ object: [Koting]?, forKey key: AnyHashable). flatMap is used there and it leads to unwrapping of Data as an array of UInt8. As a result we get a massive collection of UInt8.
While dekoting we cast the collection to array of Data type and the cast always fails. Please, check the public func dekotObject<T: Koting>(forKey key: AnyHashable) -> [T]?.
I think simple use map instead of flatMap will make the mechanism behave as expected.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.