An iOS framework for creating JSON-based models. Written in Swift.
An iOS framework for creating JSON-based models. Written in Swift (because it totally rules!)
Embedded frameworks require a minimum deployment target of iOS 8
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 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.
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
ModelRocketby adding it as a dependency in your
Package.swiftfile: ```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) ] ) ```
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,
letshould always be used, unlessvaris absolutely needed. In the case of Model objects,letshould be used for allProperty[Array|Dictionary]properties, as it still allows the underlyingvalueto be changed, unless you truly need to reassign the property
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
// `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") }
PropertyArrayconforms to
CollectionType, therefore, the
.valuessyntax 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 { }
class Car: Vehicle { let purchasedTrims = PropertyDictionary(key: "purchased_trims") }
PropertyDictionaryconforms to
CollectionType, therefore, the
.valuessyntax 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
// 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 = trueare 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") }
class Car: Vehicle { let numberOfDoors = Property(key: "number_of_doors") }
The custom object must conform to the JSONTransformable protocol by defining the following variables/functions
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 } }
ModelRocket supports enum types for
Property[Array|Dictionary]properties, as long as the enum conforms to the
JSONTransformableprotocol.
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") }
postProcesshook
The
Property
postProcessclosure (also available on
PropertyArrayand
PropertyDictionary) provides a mechanism for work to be done after all properties of a
Modelobject have been initialized from JSON but before the
Modelobject has finished initializing.
class Vehicles: Model { let vehicles = PropertyArray(key: "vehicles") { (values) -> Void in for vehicle in values { println("postHook vehicle: \(vehicle.make.value!)") } } }
.valueaccessor 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
Propertys and access the property
valuedirectly. 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:
Property
valueis 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.
Property.valuecould 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.
Override the
modelForJSON(json: JSON) -> Modelfunction
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
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 }
Calling the
json()function on a Model subclass returns a tuple containing:
dictionary: [String : AnyObject]
json: JSON
data: NSData
Call
copy()on the object, and cast to the correct type. Example:
let vehicleCopy = vehicle.copy() as! Vehicle
ModelRocket is released under the MIT license. See LICENSE for details.