jspahrsummers / Pistachio

Generic model framework for Swift

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Pistachio

Pistachio is a generic model framework for Swift. Given the right value transformers, it can handle encoding to and decoding from any recursive data structure, like JSON, YAML or XML.

If you are already familiar with Argo, take a look at Pistachiargo.

Installation

Carthage

Carthage is a simple, decentralized dependency manager for Cocoa. You can install it with Homebrew using the following commands:

$ brew update
$ brew install carthage

Next, add Pistachio to your Cartfile:

github "felixjendrusch/Pistachio"

Afterwards, run carthage update to actually fetch Pistachio.

Finally, on your application target's "General" settings tab, in the "Linked Frameworks and Libraries" section, add Pistachio.framework from the Carthage/Build folder on disk.

Usage

Pistachio is all about lenses, which provide a view on your model. Let's define a simple model:

struct Origin {
  var city: String

  init(city: String = "") {
    self.city = city
  }
}
struct Person {
  var name: String
  var origin: Origin

  init(name: String = "", origin: Origin = Origin()) {
    self.name = name
    self.origin = origin
  }
}

Lenses are basically just a combination of a getter and a setter:

struct OriginLenses {
  static let city = Lens<Origin, String>(get: { $0.city }, set: { (inout origin: Origin, city) in
    origin.city = city
  })
}
struct PersonLenses {
  static let name = Lens<Person, String>(get: { $0.name }, set: { (inout person: Person, name) in
    person.name = name
  })

  static let origin = Lens<Person, Origin>(get: { $0.origin }, set: { (inout person: Person, origin) in
    person.origin = origin
  })
}

They can be used to access and modify your model:

var person = Person(name: "Felix", origin: Origin(city: "Berlin"))
person = set(PersonLenses.name, person, "Robb")
get(PersonLenses.name, person) // == "Robb"

And you can compose, lift, transform, ... them:

let composed: Lens<Person, String> = PersonLenses.origin >>> OriginLenses.city
person = set(composed, person, "New York")
get(composed, person) // == "New York"
var persons = [ person ]

let arrayLifted: Lens<[Person], [String]> = lift(composed)
persons = set(arrayLifted, [ person ], [ "San Francisco" ])
get(arrayLifted, persons) // == [ "San Francisco" ]
var result: Result<[Person], NSError> = success(persons)

let resultLifted: Lens<Result<[Person], NSError>, Result<[String], NSError>> = lift(arrayLifted)
result = set(resultLifted, result, success([ "London" ]))
get(resultLifted, result) // == .Success(Box([ "London" ]))
let valueTransformer: ValueTransformer<String, Int> = SocialSecurityNumberValueTransformer

let transformed: Lens<Person, Int> = transform(PersonLenses.name, valueTransformer)
person = set(transformed, person, 1234567890)
get(PersonLenses.name, person) // == "Felix"

Value transformers can be flipped, composed and lifted:

let flipped: ValueTransformer<Int, String> = flip(valueTransformer)
flipped.transformedValue(1234567890) // == "Felix"
let composed: ValueTransformer<String, String> = flipped >>> UppercaseValueTransformer
flipped.transformedValue(1234567890) // == "FELIX"
let dictionaryLifted: ValueTransformer<String, Int> = lift([ "Felix": 1234567890 ], 0, "Unknown")
dictionaryLifted.transformedValue("Felix") // == 1234567890
dictionaryLifted.transformedValue("Hans") // == 0
dictionaryLifted.reverseTransformedValue(1234567890) // == "Felix"
dictionaryLifted.reverseTransformedValue(0) // == "Unknown"
let optionalLifted: ValueTransformer<String?, String> = lift(UppercaseValueTransformer, "")
optionalLifted.transformedValue("Felix") // == "FELIX"
optionalLifted.transformedValue(nil) // == ""
optionalLifted.reverseTransformedValue("FELIX") // == "felix"
optionalLifted.reverseTransformedValue("") // == nil
let arrayLifted: ValueTransformer<[String], [String]> = lift(UppercaseValueTransformer)
arrayLifted.transformedValue([ "Felix", "Robb" ]) // == [ "FELIX", "ROBB" ]

With lenses and value transformers, you can create adapters for your models:

struct Adapters {
  static let origin = DictionaryAdapter<Origin, AnyObject, NSError>(specification: [
    "city_name": transform(OriginLenses.city, StringToAnyObjectValueTransformers)
  ], dictionaryTansformer: DictionaryToAnyObjectValueTransformers)

  static let person = DictionaryAdapter<Person, AnyObject, NSError>(specification: [
    "name": transform(PersonLenses.name, StringToAnyObjectValueTransformers),
    "origin": transform(PersonLenses.origin, lift(origin, Origin()))
  ], dictionaryTansformer: DictionaryToAnyObjectValueTransformers)
}

Uh, what was that? Right, the origin adapter was lifted to a value transformer. Use fix to create adapters for recursive models:

let adapter: DictionaryAdapter<Model, Data, Error> = fix { adapter in
  // use `adapter` to reference the currently created adapter
}

Adapters handle encoding to and decoding from data:

let adapter = Adapters.person

var person = Person(name: "Seb", origin: Origin(city: "Berlin"))
var data: Result<AnyObject, NSError> = adapter.encode(person)
// == [ "name": "Seb", "origin": [ "city_name": "Berlin" ] ]

adapter.decode(Person(), from: data.value!)
// == .Success(Box(person))

The return value of both encode and decode is a Result (by LlamaKit), which either holds the encoded/decoded value or an error. This enables you to gracefully handle coding errors.

About

Pistachio was built by Felix Jendrusch and Robert Böhnke. Greetings from Berlin 👋

About

Generic model framework for Swift

License:Other


Languages

Language:Swift 77.0%Language:Objective-C 20.5%Language:Shell 1.1%Language:Ruby 0.9%Language:C++ 0.5%