Doing repetitive work over and over is always frustrating. Thankfully good developers automate the things they do or find other people who have done the hard work already.
You don’t have to be amazing at scripting to hack something together that can really make things easier. People often stray away from tech they are not used to but it’s worth just “giving things a go” to see how you get on.
Here’s a simple Ruby script that I use several times a day when working with my Xcode projects.
##The problem
I often start from or find myself on the command line using Git or navigating my projects. I also like to use CocoaPods to deal with project dependencies. This results in an annoying issue when opening Xcode, for projects that use CocoaPods you need to open the *.xcworkspace and for projects that don’t use CocoaPods you need to open *.xcodeproj. I also do not enjoy using the graphical File->Open….
##The solution
Make a little script to deal with the inconsistency and allow me to open projects quickly form the command line.
The key to this script is that I don’t get bogged down with details of how to implement it perfectly. It does exactly what I need and should be easy to follow at a later date if I need to change anything.
##Further notes
The initial version of this script had a potential floor which was pointed out by my colleague Oliver Atkinson. In the first instance I didn’t use shellwords to escape the project name. This causes an issue if the name has a space in it, which had never affected me personally as I always camel case my project names, but it’s an edge case that will be hit often when sharing code with others.
The main take away from this learning is that I didn’t need to spend hours making the script perfect (and I’m sure it still isn’t) as it suited my requirements - as soon as the requirements change it’s time to fix it up and make it work.
One of the cool things about RubyMotion is that when you run your app you get a REPL (read, eval, print, loop) that you can quickly try some code out in. Although it’s not as easy to use, the debugger in Xcode (I’m using lldb) can give you some of the same experience when it comes to interrogating objects and trying basic things.
Here are some simple examples of techniques to use to help you out:
##Interrogation
I can never remember how I’m supposed to spell font names when using +[UIFont fontWithName:size:], so I’ll often just set a break point anywhere to get me into the debugger and then ask UIFont:
(lldb) po [UIFont familyNames]
(id) $1 = 0x06a9e450 <__NSCFArray 0x6a9e450>(
Thonburi,
Snell Roundhand,
Academy Engraved LET,
... snip
Oriya Sangam MN,
Didot,
Bodoni 72 Smallcaps
)
This is cool but I’m lazy and I want this output sorted so I don’t have to scan up and down
(lldb) po [[UIFont familyNames] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
(id) $2 = 0x06aa28f0 <__NSArrayI 0x6aa28f0>(
Academy Engraved LET,
American Typewriter,
Apple Color Emoji,
Apple SD Gothic Neo,
... snip
Verdana,
Zapf Dingbats,
Zapfino
)
Now that’s much easier to look at.
Just a quick note there are a couple of ways I could have done this. I used [UIFont familyNames] nested inside the sortedArrayUsingSelector: method, but I could have just as easily used
The memory address of the originally returned array [0x06a9e450 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
The variable that the result of the first expression was assigned to [$1 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
Now I’m deeply saddened that Comic sans is not in this list but I can see something that sounds equally as awesome “Chalkboard SE”. So to answer my original question and find out what I need to use in my +[UIFont fontWithName:size:] I will again ask UIFont:
Excellent now I know the exact string I need to be using for the different weights.
Make sure you see beyond the example above and the see the power of interrogating objects and calling their methods.
##Changing values
Sometimes you want to just jump in and see how changing a value effects something without rebuilding. Again you can actually do a fair amount in the debugger
I can add a breakpoint just after the assignments and then change both of these values using expr
expr someFloat = 4.32
expr someInteger = 20
To confirm the change I can inspect with
(lldb) p someFloat
(CGFloat) $1 = 4.32
(lldb) p someInteger
(NSInteger) $2 = 20
###Objects
I can also manipulate objects the same way
If I have the following code, which currently doesn’t work because the dateFormatter has the wrong format. I can find out what the format is and play around until I get the correct result:
I add a breakpoint after the startDate is declared and then play. This is where we start to see that it can be a little tricky to make lldb do what we want
###1. Find out what the current dateFormat is
(lldb) po self.someDateFormatter.dateFormat
error: property 'dateFormat' not found on object of type 'NSDateFormatter *'
error: 1 errors parsing expression
So above we can see that dateFormat needs to be called as a method, :( no dot syntax this time…
(lldb) po [self.someDateFormatter dateFormat]
(id) $1 = 0x06d79cd0 dd/mm/yyyy
OK so we can now see why this is not working as the dateFormat is completely wrong for the input string. Let’s try a different format
(lldb) expr [self.someDateFormatter setDateFormat:@"yyyy-MM-dd"]
error: no known method '-setDateFormat:'; cast the message send to the method's return type
error: 1 errors parsing expression
Mmmm I scratch my head and then try doing what it asks by adding a (void) cast on the return…
You’ll find that you actually need to cast a lot so just be very aware of that.
Here’s a couple examples of that
(lldb) p [self.view bounds]
error: no known method '-bounds'; cast the message send to the method's return type
error: 1 errors parsing expression
(lldb) p (CGRect)[self.view bounds]
(CGRect) $9 = origin=(x=0, y=0) size=(width=320, height=460)
(lldb) expr (void)[self.view setBackgroundColor:[UIColor redColor]]
error: no known method '+redColor'; cast the message send to the method's return type
error: 1 errors parsing expression
(lldb) expr (void)[self.view setBackgroundColor:(UIColor *)[UIColor redColor]]
<no result>
(lldb) expr (void)[self.view setBackgroundColor:(id)[UIColor redColor]]
<no result>
##Experimentation
Now I have no idea if this is useful/safe but you can actually mess around fairly freeform
I’ll start off by making a command alias to run the runloop
Now I actually have a label on the screen with my text in it, without leaving the debugger.
##Conclusion
The examples provided here are fairly contrived, but the take away is that if you get familiar with the debugger you can actually do some cool experimentation. Here I’ve shown how to do some basic interrogation of objects without adding NSLog()’s and rebuilding, which is pretty cool. I’ve certainly had occasions where I’ve had a designer stood over my shoulder to tweak some UI and I would have loved to not have to rebuild repeatedly while we both wait just to check that the pixel adjustments were correct, especially when the view being edited was deep in a navigation stack and required a lot of clicking.
There’s the old adage “You can’t polish a turd”, which just sounds like a challenge to me (this was of course proved wrong by the Mythbusters).
If I see some particularly unsightly code I’ll either fix it there and then or if it’s a tricky one I’ll make a note to stew on it and come back for a fix.
The aim of this post is not to point and make fun of some code but to examine what kind of steps can be taken to polish a fresh small dung, before it’s start to rot and make a real mess in your code base.
#Starting point
Here is todays example from a real project, the participants will not be named to save their blushes (technically I have done work on this project but Git blame has gotten me off the hook for this snippet).
The code essentially fills out some text boxes and then changes their background colour to indicate that they are filled out. Currently there are multiple statements per line and there is a lot of repetition.
There are many angles I can come at this dung with my polishing cloth but here is how I actually did tackle it.
#Step 1
The first change I would like to make is to lie to the compiler (just a little bit). The method -[UIView viewWithTag:] has a return type of UIView *, which means to stop the compiler moaning in this case the return value has to be cast to a UITextField *. By casting the value I am essentially saying
“Hey compiler I know you are really clever and all but on this occasion I know better and I’m telling you this is going to be a UITextField *”
(it’s perfectly normal to have a conversation with the compiler whilst you code right?).
So I start to think - seems as I am already stepping in and telling the compiler that I know better, why not go the whole way and save some pixels by using id instead of UITextField *. That saves me some space and asterisks (which tend to draw my attention for no reason) and looks like this
The next thing I want to get rid of is the kWhiteColor. It’s name is too specific and I don’t like the use of #define to make it. This colour is also used throughout the project so I want it to be globally accessible and easy to change in one place. For this I’ll use a category.
I make sure to prefix the method to avoid collisions and in the process I have given the method a more appropriate name for what the colour actually represents in the problem domain as opposed to it’s actual colour.
As a result of this change the code is now slightly longer again as the old kWhiteColor is much shorted than the new [UIColor ps_completedFieldBackgroundColor]. I can live with this for now as I’ve gotten rid of the #define, given the colour a more appropriate domain specific name and made it into a normal method. This is how we stand now:
The next thing I want to tackle is the tags. I’m not fond of tags especially when you have to maintain them in two places (code and xibs - I’d quite like to see some imaginary construct like IBConstant which makes a constant available in IB, but that still wouldn’t help the problem much).
These views are in fact created in a xib so it’s a simple case of making some outlets and hooking them up.
I’m liking the way the code is taking shape and looking less like a spreadsheet of repeated rows (cough cough ignore lines 1 .. 5).
#Step 5
This is still not as DRY as I would like as the textField ivars are mentioned twice. To solve this I can take a slightly different tactic and make an intermediary mapping. Annoyingly UIView does not conform to NSCopying so it can not be used as a key in a dictionary but I can make do with the slightly backward mapping of using the word first. The final result is something like:
By going step by step I’ve managed to (IMHO) clean up this snippet. I have taken a dense block off code that reused the same local variable over and over to perform multiple tasks per line and elegantly split it up into a mapping step followed by an action step. I’ve used a category so that I can use language appropriate to the problem in a normal way and I’ve made the code much DRY’r and hopefully easier to maintain.
This code takes up more vertical space than the original (it wouldn’t if the original was broken down to one statement per line) and has some new files for the category, but it’s much more self descriptive and does not repeat it’s self at any point.
I can now put my polishing cloth away and keep my eyes peeled for the next turd.