NSCoding's counterpart for Swift structs.

Overview

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.

Comments
  • Enkoting an array of structs causes a crash

    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.

    bug 
    opened by Isuru-Nanayakkara 14
  • Removed the redundant use of NSData

    Removed the redundant use of NSData

    The use of NSData is causing a compile-time error in Swift. Removing that doesn't seem to affect the overall functionality.

    Also bumped the version in the Podspec to reflect the update in CocoaPods.

    opened by Isuru-Nanayakkara 2
  • Saving a class with Dekoter

    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.

    opened by Isuru-Nanayakkara 2
  • 'unarchiveTopLevelObjectWithData' is unavailable

    '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

    opened by Isuru-Nanayakkara 2
  • CocoaPods installing an old version

    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.

    bug 
    opened by Isuru-Nanayakkara 2
  • Release new version

    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.

    opened by letko-dmitry 2
  • Enkoting of array leads to incorrect result

    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.

    bug 
    opened by letko-dmitry 2
  • Ambiguous reference to member 'de_object(forKey:)'

    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.

    question 
    opened by Isuru-Nanayakkara 1
  • NSKeyedArchiver and NSKeyedUnarchiver extensions

    NSKeyedArchiver and NSKeyedUnarchiver extensions

    • Contains a public interface breaking change: methods to encode/decode objects are moved from the Koting protocol to NSKeyedArchiver and NSKeyedUnarchiver extensions.
    • Unit tests are better structured.
    • UserDefaults extension saves/loads arrays.
    • The Array extension is deleted.
    opened by artemstepanenko 0
Releases(0.3.1)
  • 0.3.1(Apr 2, 2018)

  • 0.3.0(Jun 2, 2017)

    Features

    • JSONSerialization extension.
    • The Koting protocol is split into Dekoting and Enkoting.
    • Decoded object structure is simplified (a breaking change).
    Source code(tar.gz)
    Source code(zip)
  • 0.2.2(Apr 5, 2017)

  • 0.2.1(Feb 27, 2017)

  • 0.2.0(Jan 31, 2017)

    Features

    • This release contains a public interface breaking change: methods to encode/decode objects are moved from the Koting protocol to NSKeyedArchiver and NSKeyedUnarchiver extensions.
    • UserDefaults extension.
    • Features section is added to the readme file.
    • NSKeyedArchiver extension.
    • NSKeyedUnarchiver extension.
    Source code(tar.gz)
    Source code(zip)
  • 0.1.1(Jan 22, 2017)

  • 0.1.0(Jan 8, 2017)

    Features

    • Basic functionality: a Koting protocol and a Koter.
    • API is polished
    • Unit tests are added
    • Readme is done
    • Basic Travis setup is done
    • Podspec is prepared to be pushed
    • Cocoapods' statuses are added to the readme
    • Xcode documentation is added
    Source code(tar.gz)
    Source code(zip)
Owner
Artem Stepanenko
Artem Stepanenko
Commonly used data structures for Swift

Swift Collections is an open-source package of data structure implementations for the Swift programming language.

Apple 2.7k Jan 5, 2023
Fast sorted collections for Swift using in-memory B-trees

Fast Sorted Collections for Swift Using In-Memory B-Trees Overview Reference Documentation Optimizing Collections: The Book What Are B-Trees? Why In-M

null 1.3k Dec 20, 2022
Examples of commonly used data structures and algorithms in Swift.

Swift Structures This project provides a framework for commonly used data structures and algorithms written in a new iOS development language called S

Wayne Bishop 2.1k Dec 28, 2022
Simple diff library in pure Swift

Diff Simple diffing library in pure Swift. Installing You can use Carthage or Swift Package Manager to install Diff. Usage Start by importing the pack

Sam Soffes 120 Sep 9, 2022
A functional tool-belt for Swift Language similar to Lo-Dash or Underscore.js in Javascript

Dollar Dollar is a Swift library that provides useful functional programming helper methods without extending any built in objects. It is similar to L

Ankur Patel 4.2k Jan 4, 2023
Swift type modelling the success/failure of arbitrary operations.

Result This is a Swift µframework providing Result<Value, Error>. Result<Value, Error> values are either successful (wrapping Value) or failed (wrappi

Antitypical 2.5k Dec 26, 2022
Swift μ-framework for efficient array diffs and datasource adapters.

Buffer Swift μ-framework for efficient array diffs, collection observation and data source implementation. C++11 port here Installation cd {PROJECT_RO

Alex Usbergo 348 Aug 2, 2022
A Graph Data Structure in Pure Swift

SwiftGraph SwiftGraph is a pure Swift (no Cocoa) implementation of a graph data structure, appropriate for use on all platforms Swift supports (iOS, m

David Kopec 700 Dec 16, 2022
A Generic Priority Queue in Pure Swift

SwiftPriorityQueue SwiftPriorityQueue is a pure Swift (no Cocoa) implementation of a generic priority queue data structure, appropriate for use on all

David Kopec 350 Dec 22, 2022
Super lightweight DB written in Swift.

Use of value types is recommended and we define standard values, simple structured data, application state and etc. as struct or enum. Pencil makes us

Naruki Chigira 88 Oct 22, 2022
A fast Swift diffing library.

HeckelDiff Pure Swift implementation of Paul Heckel's A Technique for Isolating Differences Between Files Features This is a simple diff algorithm tha

Matias Cudich 166 Oct 6, 2022
Algorithms and data structures in Swift, with explanations!

Welcome to the Swift Algorithm Club! Here you'll find implementations of popular algorithms and data structures in everyone's favorite new language Sw

raywenderlich 27.3k Jan 8, 2023
A Distributed Value Store in Swift.

Impeller is a Distributed Value Store (DVS) written in Swift. It was inspired by successful Distributed Version Control Systems (DVCSes) like Git and

David Coyle 1 Jun 17, 2020
🦀Amazingly incredible extraordinary lightning fast diffing in Swift

DeepDiff ❤️ Support my apps ❤️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcut man

Khoa 2k Dec 29, 2022
Swift library to generate differences and patches between collections.

Differ Differ generates the differences between Collection instances (this includes Strings!). It uses a fast algorithm (O((N+M)*D)) to do this. Featu

Tony Arnold 628 Dec 29, 2022
A Swift probability and statistics library

Probably Probably is a set of Swift structures for computing the probability and cumulative distributions of different probablistic functions. Right n

Harlan Haskins 270 Dec 2, 2022
The simplest abstraction to synchronize local data with remote source. For iOS, wirtten in swift.

Purpose The simplest abstraction to synchronize local data with remote source. For iOS, written in swift. Overview Many applications uses remote serve

Siarhei Ladzeika 7 Mar 17, 2022
💻 A fast and flexible O(n) difference algorithm framework for Swift collection.

A fast and flexible O(n) difference algorithm framework for Swift collection. The algorithm is optimized based on the Paul Heckel's algorithm. Made wi

Ryo Aoyama 3.3k Jan 4, 2023
Simple implementations of various dynamic data structures in Swift.

SwiftDataStructures Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installatio

Hector Delgado 0 Oct 21, 2021