Skip to content
AliSoftware edited this page Sep 24, 2017 · 2 revisions

Classroom Walkthrough (Part 8)

Discover gyro

$ gyro --help
$ gyro --list swift3
  • Download one of the xcdatamodel used by gyro's unit tests (in the repo's specs/fixtures/xcdatamodel folder) as a starting point. We'll start with an existing model first to understand the command line and the output.
    • I suggest you the global.xcdatamodel one which is the more complete.
    • Download the repo's zip or clone it and copy the spec/fixtures/xcdatamodel/global.xcdatamodel into CodeGenDemo/Resources for example: cp -R ~/Downloads/gyro-master/spec/fixtures/xcdatamodel/global.xcdatamodel CodeGenDemo/Resources
    • Open the xcdatamodel in Xcode to look at its content and see the entities we designed there
  • Try to use gyro on this xcdatamodel with one of the provided template.
    💡 You should create the output directory manually first, in the Finder or with mkdir).
$ mkdir CodeGenDemo/CodeGen/Models
$ gyro --template swift3 --model CodeGenDemo/Resources/global.xcdatamodel --output CodeGenDemo/CodeGen/Models
  • Open the generated files in Xcode or your Text Editor to see what was generated. Especially Animal.swift features a lot of different use cases we can encounter in a Realm.Object subclass: Attributes, Relationships (List<Pedigree>), optional String (via the String? type), optional Int32 (via the RealmOptional<Int32> type), …

Create our own data model

Step 8.1

  • Now that we've seen how that works, delete global.xcdatamodel and all the generated files in CodeGenDemo/CodeGen/Models, then go to Xcode and create our own new, empty Data Model file (let's call it Model.xcdatamodeld) in CodeGenDemo/Resources

  • Select the Model.xcdatamodeld file you just added in Xcode from the Project Navigator on the left, then go on the Utilities pane on the right, and in the "File Inspector" pane (Cmd-Alt-1), be sure to uncheck the Model.xcdatamodeld from the CodeGenDemo target, as we'll use that xcdatamodel only for visual model edition but we don't want it to be included in the app and final .ipa as we're not using it for CoreData

    • In fact you could totally remove the Model.xcdatamodeld from your Xcode project (just keep it in the Finder but not in the project) as it will only be used as an input for gyro. I still like to keep it in the Xcode project though (as long as it's not in any target) for easy access and edition, but it's really up to you.
  • Add a new entities called Person, Address and Phone and add the proper attributes and relationships to it (to match the ones we already use in our CodeGenDemo/Sources/Models)

    • Especially we'll have a 1-to-1 relationship from Person to Address, and a 1-to-many relationship from Person to Phone
    • For the model property of the Phone, being of type enum PhoneModel: String in our case, let's just use an String in the xcdatamodel
    • Don't forget to uncheck the "Optional" box for all the attributes, as in our current demo app none of our model have any optional property (we only use non-optional String and PhoneModel, so…)

Step 8.2

  • Run gyro on our new data model

💡 Be sure to point to the Model.xcdatamodel file which is inside the Model.xcdatamodeld. gyro expect you to point to an xcdatamodel, not a xcdatamodeld bundle.

$ gyro --template swift3 --model CodeGenDemo/Resources/Model.xcdatamodeld/Model.xcdatamodel --output CodeGenDemo/CodeGen/Models
  • Look at the generated code. We're missing something in Phone.swift: model is a String, but we want it to be exposed as an enum. Let's fix that

  • Go back to gyro's README. You can see it's explained that we can customize our data model a bit more using "User Info" keys in our xcdatamodel. Follow the link in the README to go to the documentation for those keys in USER-INFO.md, then jump to the "Handling enum" section. You'll see that we'll have to add 2 "User Info" keys to the model attribute of our Phone entity, one to give the name of the enum, the other to give it the values.

  • Select the model attribute of your Phone entity

  • Go to the "Core Data Inspector" pane on the right, and in the "User Info" section, add a new enumName key with value PhoneModel

  • You'll also have to add an enumValues key as well, and as a value, paste all the phone models as a comma-separated list.

    • I suggest you copy-paste the content between the curly braces of our CodeGenDemo/Sources/PhoneModel.swift to have all the case …, paste it in your favorite text editor, then remove the case keyword & the spaces, and replace CRLF with commas, so you have that one-line comma-separated list of models ready.
  • Run gyro again to re-generate the model files with those changes

    • look at the new code for the generated CodeGenDemo/CodeGen/Models/Phone.swift. See the modelEnum generated variable?
    • observe that gyro also now generated a CodeGenDemo/CodeGen/Models/PhoneModel.swift file with our enum and all its cases!

Step 8.3

Now we'll be able to use our new Realm models instead of the structs we manually wrote before.

  • Remove the files in CodeGenDemo/Sources/Model from your Xcode project (you can even delete them from the disk)
  • Install Realm in your Project (for this classroom we'll follow the "Dynamic Framework" installation method)
  • Add the files generated by gyro to your Xcode project in CodeGenDemo/CodeGen/Model group
  • Build. You'll obviously see a lot of errors, because now we're using classes instead of structs for our model types, the Ref type (which allowed us to wrap a struct into a reference type as a cheap workaround) doesn't exist anymore, etc…

Step 8.4

We'll fix those issues one at a time to make the code compile:

  • Replace Ref<Person> with just Person
// PersonListViewController.swift
-  private var dataSource: [Ref<Person>] = [] {
+  private var dataSource: [Person] = [] {

// PersonRecordViewController.swift
-  var personRef: Ref<Person>!
-  var person: Person { return personRef.object }
+  var person: Person!
  • Similarly, replace every usage of personRef.object with person now that it's directly a class
  • Replace the usage of the phone.model property (which is now a String) with the phone.modelEnum property. (Note: In future versions of gyro it's likely that we'll do the opposite logic for enum, where the enum computed property would be named model and the backing property for Realm would be named modelString or similar, but that feature is not there yet)
  • Notice that since we removed our manually-written models and that they are now code-generated, they are no longer annotated. We'll also need to fix that:
    • AutoJSONDeserializable is not applied anymore to Person and others, so we can't call Person(JSONObject: json) anymore. As this type is code-generated and we can't modify manually the generated file otherwise it would be rewritten on next build, there's no easy way to add the // sourcery: AutoJSONDeserializable annotation to fix that here. This could show the limitation of using annotations like that over the other solution of using a Phantom Protocol.
    • However, in our case, we could use gyro to generate the JSON parsing instead, using the JSON Mapping UserInfo keys if necessary and using one of the decodable or object-mapper template to generate the JSON parsing code from that. But is that even necessary?
    • Actually probably not anymore anyway, as all our JSON keys are exactly named the same as our model properties without any customisation needed, so we can actually directly use the Person(value: json) constructor for Realm.Object to pass the dictionary of properties/values in our case! ➡️ So in the end, just replace Person(JSONObject: json) with Person(value: json) and our demo project will work!
    • But for a more contrived example and cases where your JSON keys are named differently from your Realm.Object's property names, you could totally use gyro to generate the JSON parsing code using Anviking's Decodable or ObjectMapper. This step is left as an exercise for the user 😜
  • Also AutoCases and AutoStringProperties annotations/conformances are not there anymore (because now our code-generated model happen not to conform to those phantom protocols at the declaration site), we need to re-annotate the types by making them to conform to those phantom protocols again.
    • To do that, we could totally modify the gyro templates to add conformances to those phantom protocols in the template, but is that really worth it in that specific case?
    • As Swift allows us to conform to a protocol using an extension, it will be way simpler to do it that way!
    • ➡️ go to SourceryProtocols.swift and add the following lines at the end of the file:
extension PhoneModel: AutoCases {}
extension Person: AutoStringProperties {}
extension Address: AutoStringProperties {}
  • Build and run the project, all our compiler errors should be fixed now and the project be working again!

💡 As those "fix compiler error" steps are less detailed as the previous steps in this walkthru, don't hesitate to look at the diff made by commit "Step 8.4" to understand exactly what to do to fix the build. Or you can even jump directly to that "Step 8.4" commit to have the fixes already applied and a buildable project again.


The rest is left as an exercice of the user:

  • you can now use your Realm.Object, create a Realm, modify your model objects and save them to your Realm… whatever you'd do with objects in a project using Realm
  • you can also stop reading the data.json file at the beginning of PersonListViewController (or at least only if your Realm() is empty to populate some initial values), and instead read your dataSource from Realm, so that modified data are persistent across launches
  • etc… pick whatever you can do now that you have Realm in this project with auto-generated models, it's up to you (and if you don't know Realm yet, it's time to switch to a tutorial about Realm now 😉 )

Clone this wiki locally