A blog about iOS dev with RubyMotion

Paul Sturgess

Core Data in RubyMotion – Defining Your Schema in Code

One of the things I love about RubyMotion is being able to build applications without having to heavily rely on XCode. I feel far more comfortable in a text editor, where I can actually see the code that makes it work.

Typically Core Data schemas are defined in XCode, with text fields and drop down options used to define the attributes, relationships etc.

Fortunately for RubyMotion developers the fantastic ruby-xcdm allows us to define our schema in code.

This may seem like more work, but one of benefits to defining the schema in code is visibility. With the schema checked into version control, it’s easy to see how it has evolved over time. Who made the changes, when they made those changes and hopefully the commit message might even say why they made those changes.

The ruby-xcdm Readme does a great job of explaining how to set it up. However, I would like to talk a bit about versioning.

If you’re coming from a Ruby on Rails background (as I was) then CoreData doesn’t really ‘do’ migrations like ActiveRecord does. I’d recommend reading up on the term ‘lightweight migration’ in the Apple docs. Essentially for simple things, like adding or removing attributes, it can do these automatically and they can be performed using ruby-xcdm.

To make this work, all you need to do is copy your schema and create a new file. Just make sure you increment the schema number and as long as you have…

1
task :"build:simulator" => :"schema:build"

…in your Rakefile, then the schema will be built and CoreData will perform the migration.

Ruby-xcdm can handle typical relationship types like has_many, has_one and belongs_to. It also can handle entities that inherit from a base entity - just lookup the abstract: true and parent: options.

Shameless plug

This article is part of a series of blog posts about how I built Title Challenge – A football manager game for iOS.

Others i’ve written include:

Please do check out Title Challenge on the App Store.

Using PaintCode to Dynamically Create Images in the iOS Football Manager Game Title Challenge

One of the important and distinctive visual aspects of Title Challenge is for each football club to have their own shirt and colour theme. The home and away shirts are used on nearly all screens, with the front of the shirts representing a club and the rear of the shirts, with a number on the back, representing a player.

There are over 250 clubs in the database that ships with the game. Each club has 2 shirts and we need several different sizes for different screens. We also had to consider that club shirts will potentially change in both colour and style each season going forward. On top of this, we wanted to support different device resolutions including both retina and non-retina resolutions.

To create a fixed set of graphics of thousands of combinations in varying sizes, styles and colours would have been laborious to say the least and incredibly time consuming to maintain. It also would have enormously bloated the file size of our application.

Shirt Styles

We decided we wanted to be able to dynamically display, hide, colour and scale graphical components with code. This would allow us to display any club shirt on the fly.

The first phase was to decide on some core shirt styles that covered the majority of shirts worn by football clubs. We came up with 6: Plain, Stripes, Hoops, Sleeves, Quarters and Halves. We also have a goalkeeper shirt and a substitute bib used for players on the bench.

Shirt Layers image

The image above shows the different shirt styles in Title Challenge

Vector Layers

After deciding on the styles, Thom, our designer, produced a set of modular vector graphics that could be layered over each other to create any kit style required without duplicating any of the kit elements.

However, those vector graphics weren’t used directly in the game. The shirts you see are entirely generated from code. To achieve this, each individual shirt element was imported into a third party application called PaintCode.

Shirt Layers image

The image above shows the layers that make up a black and white striped shirt with a green substitute bib.

PaintCode

PaintCode is genius, it automatically generates drawing code from vector graphics.

The code to generate a shirt is actually composed of the same graphical modular layers composed on top of one another, but instead of vector graphics, they’re CALayers composed of UIBezierPath line segments with different fills and strokes.

Even the shirt numbers have been generated using PaintCode. We could have used a font, and we did look for one, but we couldn’t quite find the right one. We wanted one loosely inspired by the Mexico ‘86 world cup, but with a modern feel.

In the end it was easier to implement all elements of the shirt using the same code-based method. This was particularly true when we were dealing with scaling and animations on the tactics screen.

Paintcode screenshot

Screenshot of Paintcode with the code in the panel below the shirt vector graphic.

Turn it off and on again

To generate the front or rear of a shirt, we simply show/hide a different collar style. We also show/hide the player number as appropriate. The goalkeeper shirt is just a plain shirt with some extra sleeves added on. For bibs, we simply add the bib layer straight over the top.

Basically we just turn layers on and off and change the colours to produce the shirt combination we want. Taking this approach means that making tweaks are a joy to implement and not a chore to dread.

Another benefit is we were able to include the shirts as part of data editor in the game. So any shirt style can be created completely on the fly using a color picker.

We also changed the shirt style several times during the development of Title Challenge. I have no affiliation with PaintCode, but I’m so glad we used it!

Shameless plug

If you got this far and you’re still wondering what Title Challenge is or you found this post useful and just feel like supporting me – Please do check out Title Challenge on the App Store.

I’ve also written some other posts about developing Title Challenge: Debuging layout issues with RevealApp and How to use Core Data to make your app faster.

Tactics screenshot

How You Can Use Core Data to Make Your RubyMotion App Faster

This is my second post in a series explaining some of the technical decisions behind creating the football management iPhone game Title Challenge. The first post was a short one about using Reveal App as a useful debugging tool for building layouts.

From the outside Title Challenge may appear pretty simple, but under the hood there is a lot more than you might realise going on. Before I talk about why I chose Core Data (or even what Core Data is), let me explain a little bit about the processing the game needs to perform.

Lots of processing means loading screens

Each ‘game week’ you progress, the following needs to be updated:

  • Process player transfers (ensuring these are realistic to each club)
  • Simulate player training (players improve their skill gradually over time)
  • Update all injured players (each week they will be closer to full fitness)
  • Update exhausted players (players get exhausted if they are not rested enough)
  • Check player unhappiness (players can get unhappy at not being played enough)
  • Run scout reports (your scouts will report back on your shortlisted players)
  • Update the transfer list (again ensuring clubs are realistic in who they transfer list)

The key thing is that there could be 6,000+ players to update, with thousands of other related records to update in the database. I wanted this to run as fast as possible to reduce waiting times between game weeks.

While this is going on we show a loading screen. We decided to spend time making these look nice by including colourful player illustrations by Liverpool FC illustrator Dave Will.

Combined with a random humourous/entertaining/interesting quote (of which there are hundreds in the game) it makes waiting as pleasant as possible. Here’s an example:

Loading image  

Measuring Performance with RubyMotion-Benchmark

One way I made the code run faster was to run it, measure it, tweak it. Rinse and repeat. Motion Benchmark is a very, very simple gem but it is easy to use for this very task. It gives you precise control on what code is benchmarked, but this also makes it time consuming to identify what you should be benchmarking in the first place.

Find the bottlenecks in your code

This is where the Time Profiler in XCode’s Instruments tool is incredibly useful. It could be the subject of an entire blog post. In a nutshell it shows the exact time taken for each branch of code, even for each thread. It gives you a full stack trace to work so you can even see which line of code it is. This makes it easy to identify where the biggest bottlenecks are. I would always try and optimise those first to get some quick wins.

Optimise your code without even making it run faster

However, the main point of this blog post is to recommend thinking about how you can optimise your code without even making it run faster. This probably sounds like a an odd statement, but this is where Core Data comes in.

Many developers think Core Data is just another way to persist data and is comparable to using SQLite. Indeed, Core Data can use SQLite to persist data and I use Core Data with SQLite in Title Challenge. However, the fact Core Data can use SQLite is not important and you won’t be writing any SQL to use it. Core Data is primarily a framework for object life-cycle and object graph management.

Coming from a web development background it took me a while to get my head around this. I’ve seen (and written) my fair share of Rails apps tightly coupled to ActiveRecord and the database layer. Using Core Data forces you to understand that persistence is not the same thing as the object graph. So what is “object graph management”? The best way I can explain it, without loads of boring theory, is to tell you how I use it in Title Challenge.

Core Data and the Managed Object Context

I figured out the fastest way to perform the game week processing was to do it while the gamer was not even waiting for it to finish. I decided I wanted to perform the processing while the gamer was picking their team, analysing their squad, scouring the transfer list etc. I’m effectively doing all the processing for the next game week ahead of time.

Core Data allows me to do this because I can take a copy of all the objects in the “Object Graph” ie. all the Clubs, Players etc. (in what is known as a Managed Object Context) and manipulate them as necessary on a background thread. I can perform all the tasks I mentioned earlier (e.g. processing transfers), without any of the changes showing up in the user interface as as the gamer is navigating around the app.

In fact, none of these changes are persisted to disk until the gamer hits continue. This is one of the major differences of Core Data from how you use a database in a web app. With Core Data you make as many changes as you like and at some later date you tell it to save.

Core Data is smart enough to handle the merging of all the changes from the background thread in with the changes made by the gamer on the main thread. The beauty of this technique is it doesn’t really matter how fast/slow my processing code runs, if the gamer is not even aware its running in the background.

This approach has worked really well, especially on older devices where sometimes it doesn’t matter how much you optimise your code, it’s going to run slowly.

Some Complications

It becomes more complicated when the gamer interacts with players that have also changed in the context on the background thread. This is pretty rare in practice, but I’ve written code to handle any conflicts so it resolves them before continuing to the next game week.

The other thing to watch out for is high memory usage. I found as I was processing nearly every player and every club, each game week, these were all getting pulled into memory and not released. Core Data will hold onto objects unless you tell it let go. This is great for performance, but not so great when memory is at a premium. I highly recommend reading up on refreshObject in the Core Data docs. Call this method on objects when you’re finished with them to release them and control memory spikes.

Shameless plug

If you got this far and you’re still wondering what Title Challenge is or you found this post useful and just feel like supporting me – Please do check out Title Challenge on the App Store.

Using Reveal App to Build a RubyMotion Football Manager Game

This is the beginning of a series of blog posts I intend to write on how I’ve built Title Challenge – Football Management.

It’s an iOS game I’ve written in RubyMotion. I’m a Ruby on Rails web developer turned app developer. Before building this game, I had no prior knowledge of building mobile applications.

Hopefully this series of posts will inspire other Ruby developers out there to start building really cool mobile apps.

Reveal App was one of the first third-party tools I downloaded when I started building Title Challenge 18 months ago and I’ve used it nearly every day since. At first glance it might look like a bit of a gimmick to see your app in 3D but it is incredibly useful for debugging.

Reveal App screenshot

I’ve used Reveal to show me when a UIView is tucked behind some other UIView that I couldn’t see in the Simulator. Sometimes a UIVIew might not have a height set or something stupid like that and Reveal would instantly show me.

You can rotate your app round to see it from angles you can’t see in the Simulator.

Another really useful feature is being able to tweak attributes live in the Reveal and the Simulator reflects your changes. For example, you can change the frame of a UIView and see it move in the simulator live.

I’ve got no affiliation with Reveal but I definitely recommend it for App developers.

Tweeting a Screenshot in RubyMotion With UIActivityViewController

The easiest way to allow users to share content from inside your app is to use UIActivityViewController.

Use it when you want to allow the user to:

  • Send a Tweet
  • Share on Facebook
  • Save an image to photos
  • Send an SMS message / iMessage
  • Send an email

In iOS 8 it slides up a panel at the bottom of the screen. The user chooses how they want to share the content. The options are based on the applications they have installed. You can also specify which ones to include/exclude.

Here’s enough code to create an app with a button that brings up the UIActivityViewController, takes a screenshot and allows the user to share it along with some text.

Hopefully the code and my comments are self-documenting enough to make sense.

I figured this out through a combination of the Apple docs and this NSHipster article.

class AppDelegate

  attr_reader :window

  def application(application, didFinishLaunchingWithOptions:launchOptions)
    rootViewController = MyController.alloc.init
    rootViewController.title = 'ActivityViewController Demo'
    rootViewController.view.backgroundColor = UIColor.whiteColor

    navigationController = UINavigationController.alloc.initWithRootViewController(
      rootViewController
    )

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = navigationController
    @window.makeKeyAndVisible

    true
  end
end

# This subclass is not technically required
# It allows you to switch the text based on the type of service it will be shared on
class ShareText < UIActivityItemProvider

  attr_accessor :text

  # The placeholder text
  def activityViewControllerPlaceholderItem(controller)
    ""
  end

  # The text that will be inserted into the Tweet / Email / SMS etc.
  # You could easily respond to more activity types if you wanted
  def activityViewController(controller, itemForActivityType: activityType)
    if activityType == UIActivityTypePostToTwitter
      "#{text} #{twitter_handle} #{hashtag}"
    else
      "#{text} #{website}"
    end
  end

  def hashtag
    "#yourhashtag"
  end

  def website
    "http://yourwebsite.com"
  end

  def twitter_handle
    "@YourTwitterHandle"
  end

end

# This is a simple controller with one button
class MyController < UIViewController

  def viewDidLoad
    button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
    button.setTitle('Start', forState:UIControlStateNormal)
    button.addTarget(
      self,
      action: 'button_tapped',
      forControlEvents: UIControlEventTouchUpInside
    )
    button.frame = [[100, 260], [view.frame.size.width - 200, 40]]
    view.addSubview(button)
  end

  # This method generates the image of the entire screen
  # Note it will not include the status bar
  def screenshot
    window = UIApplication.sharedApplication.keyWindow
    UIGraphicsBeginImageContextWithOptions(
      window.bounds.size,
      false,
      UIScreen.mainScreen.scale
    )
    window.drawViewHierarchyInRect(
      window.bounds,
      afterScreenUpdates:true
    )
    image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    image
  end

  # We create an instance of our ShareText subclass
  # Pass it into the UIActivityViewController along with the screenshot
  # The UIActivityViewController handles everything else, all you have to do is present the controller.
  def button_tapped
    tc_share_text = ShareText.alloc.init
    tc_share_text.text = "Check out this screengrab!"
    controller = UIActivityViewController.alloc.initWithActivityItems(
      [tc_share_text, screenshot],
      applicationActivities: nil
    )

    controller.excludedActivityTypes = [
      UIActivityTypePrint,
      UIActivityTypeAssignToContact,
      UIActivityTypeAddToReadingList,
      UIActivityTypePostToFlickr,
      UIActivityTypeAirDrop
    ]

    self.presentViewController(
      controller,
      animated:true,
      completion:nil
    )
  end

end

Using NSUserDefaults in RubyMotion

Apple describe NSUserDefaults as “a programmatic interface for interacting with the defaults system”.

It also provides a convenient store for persisting and retrieving user preferences for your application. You can think of it as a key value hash like structure, although it is a bit smarter than that.

Here’s a really simple implementation I’ve been using:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Cache

  class << self

    def set_default(key, data)
      return if read(key)
      write(key, data)
    end

    def write(key, data)
      NSUserDefaults.standardUserDefaults[storage_key(key)] = data
      NSUserDefaults.standardUserDefaults.synchronize
    end

    def read(key)
      NSUserDefaults.standardUserDefaults[storage_key(key)]
    end

    private

    def storage_key_prefix
      "#{cache_namespace}_"
    end

    def storage_key key
      "#{storage_key_prefix}#{key}"
    end

    def cache_namespace
      raise NotImplementedError
    end

  end

end
1
2
3
4
5
6
7
8
9
10
11
12
13
class UserSearchSettings < Cache

  class << self

    private

    def cache_namespace
      "user_search"
    end

  end

end

I use it like this:

1
2
3
4
5
6
UserSearchSettings.write("sort_by", "name")
=> true
NSUserDefaults.standardUserDefaults.dictionaryRepresentation
=> {"user_search_sort_by" => "name"} # note there will be a lot of other key values in the hash
UserSearchSettings.read("sort_by")
=> "name"

My intention is not to call the Cache class directly and to only use the subclass UserSearchSettings. This forces me to group settings with a prefix defined by the cache_namespace method. This will help avoid accidentally defining the same key name for different values. It also nicely groups related settings.

Each application has its’ own NSUserDefaults.standardUserDefaults so there is no need to prefix the keys with the application name.

This store will persist until the user removes your application from their device.

Using MagicalRecord and Core Data in RubyMotion

MagicalRecord is a wrapper around Apple’s Core Data Framework. Written in Objective-C, it’s one of the most popular and mature libraries for working with Core Data.

I really like it as it simplifies a lot of the code, whilst it still allows you to ‘get your hands dirty’ when necessary.

This article details how I’m using it. This is by no means the ‘perfect’ solution, as I am evolving it all the time, but it is working well for me. By all means get in touch if you think I’m missing any obvious tricks.

One thing I have found is that it’s pretty much impossible to hide the fact you are using CoreData. Particularly with the requirement of a different context for each thread. But what I do want to do is make things as simple, consistent and maintainable as possible.

Installation

At the time of writing I’m using the MagicalRecord 3.0 branch, installed via motion-cocoaopds. My Rakefile includes:

1
2
3
app.pods do
  pod 'MagicalRecord', :git => 'https://github.com/magicalpanda/MagicalRecord.git', :branch =>'release/3.0'
end

Wrapping MagicalRecord

First I have a Database class to wrap common MagicalRecord tasks.

The main reason for this class is so that I do not sprinkle MagicalRecord calls all around my code. Having them all in one place will make it easy to update if/when the MagicalRecord api changes. It also encourages consistency in my usage of MagicalRecord as, for example, there are many ways to persist your data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
class Database

  def self.filename
    "YourApplicationName.sqlite"
  end

  def self.path
    File.join(
      NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true),
      "YourApplicationName",
      self.filename
    )
  end

  def self.created?
    File.exist?(self.path)
  end

  def self.loadOrCreate
    MagicalRecord.setupAutoMigratingStack
  end

  def self.createTestDB
    MagicalRecord.setupStackWithInMemoryStore
  end

  def self.delete
    if self.created?
      self.cleanUp
      File.delete(self.path)
    end
  end

  def self.reset
    Database.delete
    Database.loadOrCreate
  end

  def self.cleanUp
    MagicalRecord.cleanUp
  end

  def self.defaultLocalContext
    MagicalRecordStack.defaultStack.context
  end

  def self.backgroundLocalContext
    NSManagedObjectContext.MR_confinementContextWithParent(defaultLocalContext)
  end

  def self.save_specific_context(localContext, callback = nil)
    localContext.MR_saveToPersistentStoreWithCompletion(
      lambda { |success, error|
        NSLog("success: %@", success)
        if success
          callback.call if callback
        else
          NSLog "Error saving Seed Data"
          NSLog("description: %@", error.description)
        end
      }
    )
  end

  def self.save_test_db!
    defaultLocalContext.MR_saveToPersistentStoreAndWait
  end

  def self.save_on_main_thread!(callback = nil)
    defaultLocalContext.MR_saveToPersistentStoreWithCompletion(
      lambda { |success, error|
        NSLog("success: %@", success)
        if success
          callback.call(defaultLocalContext) if callback
        else
          NSLog "Error saving Core Data"
          NSLog("description: %@", error.description)
        end
      }
    )
  end

  def self.save_on_background_thread!(callback = nil, completion_callback = nil)
    MagicalRecord.saveWithBlock(
      lambda { |localContext|
        callback.call(localContext) if callback
      },
      completion: lambda { |success, error|
        NSLog("success: %@", success)
        if success
          completion_callback.call if completion_callback
        else
          NSLog "Error saving Core Data"
          NSLog("description: %@", error.description)
        end
      }
    )
  end

end

In AppDelegate didFinishLaunchingWithOptions I call Database.loadOrCreate. As the name implies, this will either load my existing Core Data stack or it will set up a new one.

I also cleanup the database when my app closes via this method in the AppDelegate:

1
2
3
  def applicationWillTerminate application
    Database.cleanUp
  end

Entities

I create my Core Data entities in Xcode (although I do intend to look at the ruby-xcdm gem so I can stop using Xcode). I’m now using the ruby-xcdm gem to define my schema in code. Here’s a blog post about it.

For now, the ib gem is great for allowing us to fire up Xcode just when we need it. The gem is mostly geared around using Interface Builder but I don’t use it for that.

Once you’ve installed the gem run:

$ rake ib:open

This will open Xcode. Inside the Resources folder on the left hand side there will be a .xcdatamodeld file. Select this and you can create your entities.

One thing to remember is to set the Class of each Entity to the corresponding Class in your app. Otherwise it will be a standard NSManagedObject.

So for each Entiy I create a corresponding class like so. Note it inherits from my own CustomNSManagedObject.

1
2
3
class ToDoItem < CustomNSManagedObject
  # more methods go here
end

I give each Entity created_at and id attributes in order to help with querying.

ActiveRecord Style Behaviour

The subclass of NSManagedObject I use looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class CustomNSManagedObject < NSManagedObject

  def self.defaultContext
    CurrentSaveGame.localContext
  end

  def self.all(context = nil)
    localContext = context || defaultContext
    self.MR_findAllSortedBy("created_at", ascending:true, inContext: localContext)
  end

  def self.first(context = nil)
    localContext = context || defaultContext
    self.MR_findFirstOrderedByAttribute("created_at", ascending:true, inContext: localContext)
  end

  def self.last(context = nil)
    localContext = context || defaultContext
    self.MR_findFirstOrderedByAttribute("created_at", ascending:false, inContext: localContext)
  end

  def self.count
    self.MR_numberOfEntities
  end

  def self.find(id, context = nil)
    localContext = context || defaultContext
    self.MR_findFirstByAttribute("id", withValue:id, inContext: localContext)
  end

  def self.new(attributes)
    self.build(attributes)
  end

  def self.build(attributes = {}, context = nil)
    localContext = context || defaultContext
    model = self.MR_createInContext(localContext)
    model.setValuesForKeysWithDictionary(attributes)
    model.created_at = Time.now
    model
  end

  def switch_context(localContext)
    self.MR_inContext(localContext)
  end

  def attributes
    self.entity.attributesByName.keys.each_with_object({}) do |attribute, attributes_hash|
      attributes_hash[attribute] = self.send(attribute)
    end
  end

end

Essentially this class gives me ActiveRecord like behaviour. Also, like my Database class, it ensures I contain some more MagicalRecord calls to a single place.

This means I can do:

ToDoItem.build(
  {
    id: 1,
    action: "Lorem ipsum dolor sit amet"
  }
)

My build method automatically inserts a created_at for every new record.

Data store

By default MagicalRecord.setupAutoMigratingCoreDataStack will use SQLite to persist your data.

Note that I don’t include a save method in CustomNSManagedObject. Saving an individual record isn’t the most efficient way to persist changes in Core Data. Everything is held in memory until the relevant context is told to save. Hence why I’ve put the save method in the Database class.

Persisting data on the main thread

If you’ve used any of the CustomNSManagedObject methods like find, all, or build without passing in a context then using Database.save_on_main_thread! will persist any changes made. For example:

1
2
3
4
5
6
7
new_item = ToDoItem.build(
  {
    id: 2
    action: "Some other todo item"
  }
)
Database.save_on_main_thread!

Persisting data on background threads

However, if updates are made on a background thread, then we need a new (temporary) context which is handled by Database.save_on_background_thread!.

For example:

1
2
3
4
5
6
7
8
9
10
11
Database.save_on_background_thread!(
  lambda { |localContext|
    new_item = ToDoItem.build(
      {
        id: 2
        action: "Some other todo item"
      },
      localContext
    )
  }
)

Alternatively if you don’t want to wrap everything in the block. Create a localContext and hold on to it. Make your changes and then save later down the line.

1
2
3
4
5
6
7
8
9
localContext = Database.backgroundLocalContext
new_item = ToDoItem.build(
  {
    id: 2
    action: "Some other todo item"
  },
  localContext
)
Database.save_specific_context(localContext)

Note most of the class methods in CustomNSManagedObject take an optional context so they can be used on background threads.

Testing

For tests I use the in memory data store and when saving it is synchronous. This ensures the data is there before the assertions!

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
describe "SomeTest" do

  before do
    Database.cleanUp
    Database.createTestDB
    @localContext = Database.defaultLocalContext
  end

  describe "some_method" do

    context "when some scenario" do

      it "should assign something is true" do
        # ...
      end

    end

  end

end

Logging to the Console in RubyMotion

The simplest way to output to the console from your RubyMotion app is to use puts. This works fine when your app is running in the simulator. However, puts does not output to the console when your app is running on the device itself.

You can view the live logs while your app is running on your usb connected device via:

$ motion device:console

You will see messages from your application alongside those from other apps or the system itself.

Instead of using puts, you’ll need to make use of NSLog. For example

NSLog("My debug messge")

Note, however, that the first argument for NSLog is actually a String Format Specifier.

The following are equivalent:

puts "A #{variable} goes here"
NSLog("A %@ goes here", variable)

Just remember that any NSLog messages you leave in your app will be visible to the curious, even after you’ve released to the App Store.

Using LLDB to Debug a RubyMotion App

RubyMotion now uses LLDB for debugging. Previously it used GDB.

If you’re getting random crashes with little or no backtrace it may be down to a memory allocation issue.

Launch your application with the debugger running via:

$ bundle exec rake debug=1 NSZombieEnabled=YES MallocStackLogging=1

Trigger your bug and you should get output in Terminal like this:

2013-10-16 10:17:19.594 YourAppName[68253:a0b] *** -[UIBarButtonItem isSystemItem]: message sent to deallocated instance 0x9fd56f0
Process 68253 stopped
* thread #1: tid = 0x3127cd, 0x03ac0811 CoreFoundation`___forwarding___ + 769, queue = 'com.apple.main-thread, stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x03ac0811 CoreFoundation`___forwarding___ + 769
CoreFoundation`___forwarding___ + 769:
-> 0x3ac0811:  jmp    0x3ac090c                 ; ___forwarding___ + 1020
   0x3ac0816:  movl   %edi, (%esp)
   0x3ac0819:  calll  0x3bb840e                 ; symbol stub for: class_getSuperclass
   0x3ac081e:  movl   %eax, %edi
(lldb)

Leave your application running and open a separate tab in Terminal. You can inspect the malloc_history of the object that has been deallocated via:

/usr/bin/malloc_history 68253 0x9fd56f0

Note the first argument is the Process ID as indicated where it says Process 68253 stopped. Note that the Ruby process that performs the build and launches your app (i.e. rake) is not the same as your app process.

The last argument is the Object Reference as indicated by message sent to deallocated instance 0x9fd56f0.

This (hex) number is the address of a piece of memory where the object we are interested in (was) located. The other traces in the malloc history are cases where that same memory location was used.

Running the above command will give you a dump of information from the stack logs. I’ve truncated the following example, as the information is plentiful, but it shows the kind of output you can expect:

malloc_history Report Version:  2.0
Process:         YourAppName [68253]
Path:            /path/to/YourAppName
Load Address:    0x1000
Identifier:      YourAppName
Version:         ??? (???)
Code Type:       X86 (Native)
Parent Process:  debugserver [68922]

Date/Time:       2013-10-16 11:14:24.104 +0100
OS Version:      Mac OS X 10.8.5 (12F45)
Report Version:  7

ALLOC 0x9fd56f0-0xa64da98 [size=425]: thread_4925a28 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | ...

FREE  0x9fd56f0-0xa64da98 [size=425]: thread_4925a28 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | ...

ALLOC 0x9fd56f0-0xa64dacf [size=112]: thread_4925a28 |start | main | UIApplicationMain | GSEventRun | GSEventRunModal | CFRunLoopRunInMode | CFRunLoopRunSpecific | ... | rb_scope__initWithFillColor:__ | vm_dispatch | rb_vm_dispatch | builtin_ostub1(objc_object* (*)(objc_object*, objc_selector*, ...), objc_selector*, objc_object*, unsigned char, int, unsigned long*) | _objc_rootAlloc | class_createInstance | calloc | malloc_zone_calloc

The output shows there had been two mallocs in the life span of the app and only one was freed.

If I was not expecting the first ALLOC to be freed, then I would investigate the trace of the FREE to figure out why it was freed. Alternatively if I was expecting the second ALLOC to be FREEd as well, then I would know that did not happen.

I knew I was expecting the memory to be freed and it was towards the end of the second ALLOC block I could see my own method initWithFillColor being called.

I was subclassing UIBarButtonItem with my method initWithFillColor like so:

  class MenuButton < UIBarButtonItem

    def initWithFillColor(fillColor)
      UIBarButtonItem.alloc.initWithCustomView(button(fillColor))
    end

    private

    def showMenu
      # ...
    end

    def button(fillColor)
      UIMenuButtonIcon.alloc.initWithFrame(CGRectMake(0.0, 0.0, 25, 15), fillColor:fillColor).tap do |button|
        button.addTarget(self, action:"showMenu", forControlEvents:UIControlEventTouchUpInside)
      end
    end

  end

My initWithFillColor method obviously doesn’t need to create another instance of UIBarButtonItem. So I updated it to the following and my memory bug was gone:

def initWithFillColor(fillColor)
  initWithCustomView(button(fillColor))
end

Hopefully this is a good start for debugging any memory bugs in your RubyMotion apps.

Thanks to the latest HipByte employee, Eloy Durán, for the tips!

How to Debug Core Data Output to the Console in RubyMotion

Core Data can show you the underlying queries and execution times whilst you are running your app. Just fire it up with:

rake args="-com.apple.CoreData.SQLDebug 1"

Note you can bump the number up to 2 or 3 depending on how much information you want. 3 goes as far as to show you the result set Core Data has fetched.