ModelRocket

by ovenbits

ovenbits / ModelRocket

An iOS framework for creating JSON-based models. Written in Swift.

453 Stars 20 Forks Last release: over 4 years ago (1.3) MIT License 38 Commits 8 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

ModelRocket

Build Status Carthage compatible CocoaPods Compatible License Platform

An iOS framework for creating JSON-based models. Written in Swift (because it totally rules!)

Requirements

  • iOS 8.0+
  • Xcode 7.3
  • Swift 2.2

Installation

Embedded frameworks require a minimum deployment target of iOS 8

Carthage

Carthage is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.

You can install Carthage with Homebrew using the following commands:

$ brew update
$ brew install carthage

To integrate ModelRocket into your Xcode project using Carthage, specify it in your Cartfile:

github "ovenbits/ModelRocket"

Then, run

carthage update
.

Follow the current instructions in Carthage's README for up-to-date installation instructions.

CocoaPods

CocoaPods is a dependency manager for Cocoa projects.

CocoaPods 0.36 adds supports for Swift and embedded frameworks. You can install it with the following command:

$ gem install cocoapods

To integrate ModelRocket into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'ModelRocket'

Then, run

pod install
.

Swift Package Manager

The Swift Package Manager is a dependency management tool provided by Apple, still in early design and development. For more infomation check out its GitHub Page.

You can use the Swift Package Manager to install

ModelRocket
by adding it as a dependency in your
Package.swift
file: ```swift import PackageDescription

let package = Package( name: "PROJECT_NAME", targets: [], dependencies: [ .Package(url: "https://github.com/ovenbits/ModelRocket.git", versions: "1.2.3" ..< Version.max) ] ) ```

Usage

Creating a custom object

class Vehicle: Model {
    let make  = Property(key: "make")
    let model = Property(key: "model", required: true)
    let year  = Property(key: "year") { year in
        if year < 2015 {
            // offer discount
        }
    }
    let color = Property(key: "color", defaultValue: UIColor.blackColor())
}

NOTE: As with all Swift variables,

let
should always be used, unless
var
is absolutely needed. In the case of Model objects,
let
should be used for all
Property[Array|Dictionary]
properties, as it still allows the underlying
value
to be changed, unless you truly need to reassign the property

Supported Types

  • String
  • Bool
  • Int
  • UInt
  • Double
  • Float

In addition to the core types above, ModelRocket also supports serialization for several other classes out of the box:

  • NSDate
    — ISO8601-formatted string (
    2015-05-31T19:00:17.000+0000
    )
  • UIColor
    — hex-color string (
    #f6c500
    )
  • NSURL
    — any url string (
    http://ovenbits.com
    )
  • NSNumber
    — any number, can be used in place of
    Double
    ,
    Float
    ,
    Int
    , and
    UInt

Creating an object with a typed array

// `Model` subclasses get `fromJSON` and `toJSON` implementations on `JSONTransformable` for free,
// but explicit `JSONTransformable` conformance is still required
extension Vehicle: JSONTransformable {}

class Vehicles: Model { let vehicles = PropertyArray(key: "vehicles") }

PropertyArray
conforms to
CollectionType
, therefore, the
.values
syntax is not necessary when iterating through the values. For example:
let allVehicles = Vehicles(json: )

// using .values syntax for vehicle in allVehicles.vehicles.values { }

// using CollectionType conformance for vehicle in allVehicles.vehicles { }

Creating an object with a typed dictionary

class Car: Vehicle {
    let purchasedTrims = PropertyDictionary(key: "purchased_trims")
}

PropertyDictionary
conforms to
CollectionType
, therefore, the
.values
syntax is not necessary when iterating through the keys and values. For example:
let vehicle = Vehicle(json: )

// using .values syntax for (key, trim) in vehicle.purchasedTrims.values { }

// using CollectionType conformance for (key, trim) in vehicle.purchasedTrims { }

NOTE: All object in the dictionary must be of the same type. If they're not, the app won't crash, but values of different types will be discarded

Initializing and using a custom object

// instantiate object
let vehicle = Vehicle(json: json)

// get property type println("Vehicle make property has type: (vehicle.make.type)")

// get property value if let make = vehicle.make.value { println("Vehicle make: (make)") }

Model objects also contain a failable initializer, which will only return an initialized object if all properties marked as

required = true
are non-nil.
// instantiate object, only if `json` contains a value for the `make` property
if let vehicle = Vehicle(strictJSON: json) {
    // it's best to avoid implicitly unwrapped optionals, however, since `vehicle` is initialized iff `make` is non-nil, if can be force-unwrapped safely here
    println("Vehicle make: \(vehicle.make.value!)")
}
else {
    pintln("Invalid JSON")
}

Subclassing a custom object

class Car: Vehicle {
    let numberOfDoors = Property(key: "number_of_doors")
}

Adding a custom object as a property of another object

The custom object must conform to the JSONTransformable protocol by defining the following variables/functions

  • class func fromJSON(json: JSON) -> T?
  • func toJSON() -> AnyObject
class Vehicle: Model {
    let manufacturer = Property(key: "manufacturer")
}

class Manufacturer: Model { let companyName = Property(key: "company_name") let headquarters = Property(key: "headquarters") let founded = Property(key: "founded") }

extension Manufacturer: JSONTransformable { class func fromJSON(json: JSON) -> Manufacturer? { return Manufacturer(json: json) } func toJSON() -> AnyObject { return self.json().dictionary } }

Using an enum as a property

ModelRocket supports enum types for

Property[Array|Dictionary]
properties, as long as the enum conforms to the
JSONTransformable
protocol.

As a simple example, the material type of a vehicle's interior could use an enum like this:

enum VehicleInterior: String {
    case Fabric = "fabric"
    case Leather = "leather"
}

extension VehicleInterior: JSONTransformable { static func fromJSON(json: JSON) -> VehicleInterior? { return VehicleInterior(rawValue: json.stringValue) } func toJSON() -> AnyObject { return rawValue } }

class Vehicle: ModelRocket { let interior = Property(key: "interior") }

Property
postProcess
hook

The

Property
postProcess
closure (also available on
PropertyArray
and
PropertyDictionary
) provides a mechanism for work to be done after all properties of a
Model
object have been initialized from JSON but before the
Model
object has finished initializing.
class Vehicles: Model {
    let vehicles = PropertyArray(key: "vehicles") { (values) -> Void in
        for vehicle in values {
            println("postHook vehicle: \(vehicle.make.value!)")
        }
    }
}

.value
accessor usage pattern

A ModelRocket property is of type

Property
. When accessing the property's value, you go through
Property.value
, e.g.:
let vehicleMake = make.value

It is perfectly acceptable to to utilize

Property
s and access the property
value
directly. However, you may want a different public API for your model objects.
private let _make = Property(key: "make")
public var make: String {
  get {
    return make.value ?? "unknown make"
  }
  set {
    make.value = newValue
  }
}

This usage pattern enables:

  • A cleaner public API
  • A public API that makes the type more proper: we expect "make" to be a string, not a
    Property
  • value
    is optional because it must be for general applicability, but your API may be more correct to have non-optional. Of course, if your API wants an optional, that's fine too.
  • The ability to process or convert the raw JSON value to other values more proper to your object's public API
  • Whereas a
    Property.value
    could be set, you could omit the set accessor for a read-only property, again helping to expose exactly the API for your object that you desire.
  • This usage of the bridge pattern enables ModelRocket to become an implementation detail and minimize dependencies and long-term maintenance.

Implementing a class cluster

Override the

modelForJSON(json: JSON) -> Model
function
class Vehicle: Model {
    let make = Property(key: "make")
    let model = Property(key: "model")
    let year = Property(key: "year")
    let color = Property(key: "color")
    let manufacturer = Property(key: "manufacturer")

override class func modelForJSON(json: JSON) -&gt; Vehicle {

    switch json["type"].stringValue {
    case "car":
        return Car(json: json)
    case "plane":
        return Plane(json: json)
    case "bike":
        return Bike(json: json)
    default:
        return Vehicle(json: json)
    }
}

}

Then to access subclass-specific properties, use a switch-case

let vehicle = Vehicle.modelForJSON(vehicleJSON)

switch vehicle { case let car as Car: // drive the car case let plane as Plane: // fly the plane case let bike as Bike: // ride the bike default: // do nothing }

Obtaining the object's JSON representation

Calling the

json()
function on a Model subclass returns a tuple containing:
  • dictionary: [String : AnyObject]
  • json: JSON
  • data: NSData

Obtaining a copy of a custom object

Call

copy()
on the object, and cast to the correct type. Example:
let vehicleCopy = vehicle.copy() as! Vehicle

License

ModelRocket is released under the MIT license. See LICENSE for details.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.