Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ovenbits/ModelRocket

An iOS framework for creating JSON-based models. Written in Swift.
https://github.com/ovenbits/ModelRocket

Last synced: about 2 months ago
JSON representation

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

Awesome Lists containing this project

README

        

![ModelRocket](https://s3.amazonaws.com/f.cl.ly/items/272x3d230n0Z3T300V1D/model-rocket.jpg)

[![Build Status](https://travis-ci.org/ovenbits/ModelRocket.svg?branch=master)](https://travis-ci.org/ovenbits/ModelRocket)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
![CocoaPods Compatible](https://img.shields.io/cocoapods/v/ModelRocket.svg)
![License](https://img.shields.io/badge/license-MIT-000000.svg)
![Platform](https://img.shields.io/badge/platform-iOS-lightgrey.svg)

An iOS framework for creating JSON-based models. Written in Swift (because [it totally rules!](https://youtu.be/Qq_NuyR8XPg?t=36s))

## Requirements

- iOS 8.0+
- Xcode 7.3
- Swift 2.2

## Installation

> **Embedded frameworks require a minimum deployment target of iOS 8**

### Carthage

[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.

You can install Carthage with [Homebrew](http://brew.sh/) using the following commands:

```bash
$ brew update
$ brew install carthage
```

To integrate ModelRocket into your Xcode project using Carthage, specify it in your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile):

```ogdl
github "ovenbits/ModelRocket"
```

Then, run `carthage update`.

Follow the current instructions in [Carthage's README](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) for up-to-date installation instructions.

### CocoaPods

[CocoaPods](http://cocoapods.org) 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:

```bash
$ gem install cocoapods
```

To integrate ModelRocket into your Xcode project using CocoaPods, specify it in your [Podfile](http://guides.cocoapods.org/using/the-podfile.html):

```ruby
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](https://swift.org/package-manager) is a dependency management tool provided by Apple, still in early design and development. For more infomation check out its [GitHub Page](https://github.com/apple/swift-package-manager).

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

```swift
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

```swift
// `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:

```swift
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

```swift
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:

```swift
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

```swift
// 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.

```swift
// 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

```swift
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

```swift
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:

```swift
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.

```swift
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.:

```swift
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.

```swift
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

```swift
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) -> 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

```swift
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:

```swift
let vehicleCopy = vehicle.copy() as! Vehicle
```

## License

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