I made my first Ruby gem and more importantly it solves a problem that really frustrates me (ever so occasionally).
The problem is that I tend to hang out on StackOverflow answering questions, which often means I end up writing code. To test the code works I’ll jump into Xcode and create a new project but save it to /tmp so I don’t get clutter on my hard drive. This leads to the following mess in my simulators directory
When it comes time to do some real work and I need to check the contents of a real app I hit this mess, which is annoying and often leads to applying a sort on date to find likely folders to look in.
So I took this opportunity to have a play with a few concepts
Making a gem
Making a command line tool
Playing with state machines
The quick project has indeed served it’s purpose - I actually wrote some code and didn’t just think about writing it and I got to play with the above concepts.
#The tool
I called it sidir as an abbreviation for “Simulator Directory” and because it sounds like the refreshing beverage.
So providing I didn’t mess something fundamental up the following should work (it works for me at least with Ruby 1.9.3)
gem install sidir
From there we can start it up with
sidir
We’ll get the sidir prompt which also tells you where it is in relation to the sim directory
sidir / >
(Serious draw back #1 it is hard coded to start in ~/Library/Application Support/iPhone Simulator/)
From here we can get the available commands with help
sidir / > help
cd <item> - change to directory at <item>
ls - list available simulators
show - open Finder here
So let’s see what is around with ls
sidir / > ls
4.3.2
5.0
5.1
I can get finder to open here with show but I’ll cd into the 5.1 sims directory
sidir / > cd 5.1
(There is some basic tab completion - another thing I wanted to play with)
Let’s see what commands we have available here
sidir /5.1 > help
cd <item> - open finder at <item>
cd .. - move back to simulators
ls - list available applications
show - open Finder here
rm <item> - delete the application <item> and all associated data
reset <item> - clean all paths in <item> as if the app is freshly installed
We have slightly more things to do.
Things to note
cd <item> will open up Finder - I didn’t bother going any further as it would just be replicating normal shell behaviour
rm and reset work on apps not the whole sims directory
The idea behind reset was to take an application directory and clean it all out just leaving what is installed by default. This kind of emulates a clean install - I’m not sure how useful this actually is as there is also rm which just deletes the whole application folder, which I find a lot quicker/easier than tap and holding in the simulator until the apps wobble and then clicking the x and then… you get the point.
#Conclusion
Making a gem was fun but I got tripped up a lot as it was my first gem and I had to learn about the structuring etc. I’ve got a simple utility that I find handy even if it just an elaborate way to easily get the application names. I’ve slapped this on github and it’s available as a gem - who knows someone else might find a use for it.
I have also found it amazing how just after I hit deploy on a gem, public github or a blog I find a great number of spelling mistakes, issues or oversights that I then have to jump in and correct. Upto version 0.0.3 already!!!
I’ve been developing for iOS for quite a while now and I’m always amazed at the things I don’t know that can really change my workflow. I’ve felt at home in the debugger for a while - printing objects and scalar variables, assigning values and generally navigating around fairly smoothly. Today I got a new tool to put in my programming belt with the knowledge that it’s possible to create conditional breakpoints - this is something that I wish I knew a long time ago.
##Problem in context
It’s a fairly common problem that you’ll have an issue that occurs only while in some kind of loop, whether it is a for loop or a user interaction loop of some kind e.g. tapping a button multiple times. It can be both tedious and error prone trying to get yourself set up to examine state when you have to perform repetitious work like pressing continue on the debugger. It’s easy to overshoot your assumed condition and have to start again or maybe it would just take far too long to get yourself in the correct state manually.
##Meet conditional breakpoints
Our test scenario is both silly and simple but it gives us something to work with.
“We have noticed that every 10th time through our loop we get an error - we would like to stop execution and examine our object.”
MyObject*myObject=[[MyObjectalloc]init];for(inti=0;i<1000;i++){[myObjectdoSomething];// Break point on this line}
###The old way
We set up a break point and we are happy to click continue as we only have to do it a few times. Hopefully our counting and hand to eye co-ordination is up to standard and we manage to click through correctly the first time to get us in the right state
We “fix” the issue but now it happens every 100th time through the loop. At this point we would probably abandon our tools and deteriorate to caveman debugging. Log everything and then scan the logs to see what is happening… but luckily we don’t have to give up just yet.
##Conditions
If you right click the breakpoint in Xcode you’ll see an option to “Edit Breakpoint…”.
As you can see there are some interesting configuration options here.
Condition - A condition to be met for the breakpoint to fire
Ignore - Amount of times the condition needs to be met before firing
Action - Any actions to run which can be Applescript, Capture OpenGL frame, Debugger Command, Log Message, Shell Command, Sound
Options - As it says in the image
Remember using Debugger commands alone we can access all kinds of information like the values of variables, back traces etc
###Basic condition
To start we’ll set the condition and run an action to see if we can grab the 100th iteration
Side note -p i will just print the variable i
We hit run and sure enough the debugger stops and in our console we get output like:
(int) $101 = 100
(lldb)
###Basic condition with ignore
To try out the ignore option we’ll stop on the 5th time that i == i, which will leave us with a value of 5.
This gives us output like:
(int) $1 = 5
(lldb)
###Non trivial condition
The Condition expression can be non trivial as long as llvm can understand it. For example if I wanted to examine the state around my object when one of the properties is set to the string broken I can get the breakpoint to fire like this:
Apart from the annoying cast this is nice and clean.
##”But I really like caveman debugging…”
You may be thinking “this is cool but I can just write the code that will print things when different conditions occur” - and you’d be right. There are two reasons why I prefer using the conditional breakpoint
Where possible I don’t like changing production code for development purposes.
If I logged the object out I would only get the information that I logged. With the breakpoint I can examine all the state around the object as well because the execution is paused.
##Crazy thoughts
I’m still only thinking about the wide range of debugging possibilities available but one far fetched scenario that could have uses in a collaborative environment could be for capturing those bugs that are hard to reproduce.
By setting up a conditional breakpoint and sharing it with the team, you could have multiple workstations + multiple developers going about their normal work but potentially capturing that awkward code path that you can never quite reproduce. At this point you can literally do anything - dump everything to a log, run a script to email you information, have the computer say "STOP you've found the lochness monster bug", who knows what fun you could have.
##Conclusion
Conditional breakpoints are very flexible and powerful. Although I prefer to avoid the debugger by writing the best code I can, knowing how to use the tools effectively really helps out in times of need. Don’t be fooled by only having 4 options to configure, these give you amazing power and versatility and should help avoid the times when you are stuck caveman debugging large repetitive tasks.
Sharing iOS projects can be a frustrating task. This has been greatly improved with tools like cocoapods maturing quickly. This certainly goes someway to alleviating the project sharing pain but we are not quite where I want to be. Cocoapods manages the Objective-C dependencies but a project may have other dependencies such as additional tools (often written in Ruby), which will have their own dependencies. I would often think it would be great if I could standardise my processes across my projects and add a simple of way of setting up a cleanly cloned project…
Then I remembered I post I had read called Setting up a new machine for Ruby development at 37signals.com about them having a rake setup task that would get your project set up and ready to run.
##A naive implementation
This got me to create my own simple rake setup task, here is the very simple script that saves a fair amount of bother when collaborating:
desc'Set up project'task:setupdosh'bundle install'sh'pod install'end
This of course comes with some requirements on the target machine. I currently use Ruby 1.9.3 managed with RVM. The bundle install is not really required by I tend to use Ruby for other tasks in my projects and find it easier to have Bundler manage my Ruby dependencies.
##Piecing it all together
A full example of working on a project with this in place is as simple as
git clone {Some git repository}
rake setup
##Conclusion
It really is worth making sure that your projects can be easily shared and set up. Using tools that are widely available always seems sensible, but in this case it does require that the target environment is suitable e.g. has ruby, rake and bundler.