I'm not a big fan of singletons, especially when they are used as they often are - as glorified globals. That being said, it occasionally makes sense to have some kind of shared instance of an object; not a true singleton that strictly prevents more than one allocation, but a convenient default instance.
Apple uses this pattern frequently throughout their frameworks: [NSUserDefaults standardUserDefaults], [UIScreen mainScreen] and many, many more.
Conceptually, I like to think of these methods as convenient accessors to instances representing a shared or common resource rather than singletons and I still try and avoid using them as globals where possible.
Singletons: the wrong way
If you were trying to find out a way of implementing a singleton or shared instance in Objective-C, you might well find yourself implementing Apple's own example. You might even find yourself using Matt Gallagher's SynthesizeSingleton macro.
No disrespect to Matt, but please don't do this. You almost certainly don't need such a strict implementation. Furthermore, much of the code isn't even relevant in our shiny ARC future.
Singletons: the right way
Up until recently, if anybody asked me what the best way of going about creating a singleton in Objective-C is, I'd point them towards Chris Hanson's great article on the subject. In fact, I still recommend you read it.
But there is an even easier way using Grand Central Dispatch; I've seen this mentioned in several Apple WWDC videos and Colin Wheeler mentions it in this blog post but it doesn't seem to have gained much traction, possibly due to people still supporting iOS 3. But with iOS 5 round the corner, I think it's time to move on and embrace blocks, GCD and eventually, ARC.
Here's what the GCD (and ARC) version looks like:
+ (id)sharedInstance
{
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init]; // or some other init method
});
return _sharedObject;
}
The block given to dispatch_once will, as the name implies, only ever be called once. This will ensure only one instance is created. It's also fast and thread safe: no synchronized or nil checks required.
We can go one step further and wrap this up in a convenient macro. Now all you need to do is this:
+ (id)sharedInstance
{
DEFINE_SHARED_INSTANCE_USING_BLOCK(^{
return [[self alloc] init];
});
}
A bit simpler than the Apple version, don't you think?
Reading Marcus Zarra's own style guide, I thought I'd write a post outlining my own conventions.
I don't agree with all of Marcus' guidelines; some I positively disagree with. I'll try and outline some of those below and my reasons why.
I try not to be religious or zealous when it comes to coding conventions; the most important thing, when working on a team, is to establish the guidelines up front and stick to them.
Consistency trumps personal opinions. If that means having to stick to conventions that you wouldn't normally in your own code when working on different teams, then so be it. The more zealous you are about your own personal style, the harder you will find it to adapt.
If you are new to Objective-C or Cocoa/CocoaTouch and want some general guidance on good coding conventions, it's well worth reading Apple's suggestions. Google also have a style guide.
Here's my guidelines; if there is something I do not cover, it either means I'm yet to form a firm opinion or simply do not care either way:
Dot notation
Dot notation should always be used for properties and should also be used for accessor/mutator-like methods that aren't declared properties. Generally speaking, dot notation should only be used for reading an object's state (the exposure of which should be kept to a minimum) or setting it directly. Objects that perform a commands or have side-effects should never be called using dot-notation.
White space
Two spaces, no tabs. There should be no extraneous white space at the end of a line. Apple's templates tend to use four spaces and I have occasionally considered switching but due to me using two spaces in all of my Ruby code, I tend to stick with two spaces for consistency.
Method braces always appear on a line of their own. Other braces (if/else/switch/while etc.) open on the same line as the statement but close on a new line.
There should be no spaces between parentheses and their contents.
Variables
Variables should be named as descriptively as possible. The only time a single letter variable is considered acceptable is in for() loops. Asterisks indicating pointers belong with the variable, not the type.
Direct instance variable access should be avoided where possible, except for in init and dealloc methods. Consider declaring private properties in a class continuation in the implementation file to encapsulate memory management.
When releasing variables in dealloc, it is not necessary to nil them out as well. Properties should not be used in dealloc or init; the variables should be released directly. dealloc should only be used to release variables, nil delegates or anything else related to releasing memory.
When ARC is released and production ready, all arguments regarding best practice when it comes to variables, nilling them and what to call in dealloc become moot. Move along and use ARC as soon as you can.
Method signatures
The only spaces that should appear in a method signature should be after the scope indicator and in any pointer variables (see above).
Naming
Apple naming conventions should be adhered to wherever possible, especially those related to memory management rules (init, new, alloc, copy etc.). Long, descriptive methods names are good. Class name prefixes should always be used when writing shared/open-source library code.
Code structure
Header files should only ever declare the public interface. Private/protected methods should be contained in class continuations inside the implementation file.
Within an implementation, methods should be logically grouped using #pragma marks, including protocol implementations. @synthesize statements should appear at the top of the class, followed by any init methods, then the dealloc method. @dynamic properties should be explicitly declared.
Protocols should be declared in their own files.
Project organisation
All classes should be kept within a "Classes" folder on the file system. Test classes should be kept in a folder called "Tests", or "Specs". Resources and nibs should be kept in a "Resources" folder. Frameworks should be kept in a "Frameworks folder" and external projects and repositories (e.g. git submodules) should be kept in an "External" folder.
Further organisation of classes should be done using Xcode groups. Trying to keep Xcode groups in sync with actual file system directories is not worth the hassle.
The only files that should appear in the root of a project directory are the Xcode project file, Info.plist and any prefix header. Occasionally, other files may appear (README, LICENSE files in open-source projects).
Comments
Comments should be used sparingly. When they are used, they should be used to explain why a particular piece of code does something.
If you've chosen a particular algorithm or have made some kind of non-obvious performance optimization, comments are a great way to explain why. If you find yourself writing comments that act as a narrator to your code, explaining what is happening, stop and think about how you could refactor your code to make it make it self-documenting.
Any comments that are used, must be kept up-to-date or deleted.
The only exception to the above commenting rules are those used to generate documentation.
Postscript
Like Marcus' guide, this should be considered a living document. I will try and keep it updated and will consider changing a particular practice if I feel there is a good reason to do so. If you want to discuss this with me, Twitter is the place to do it.
Today marks the beginning of the Apple WWDC 2011. I'm not normally one for attempting predictions, especially not on my blog, so this won't be another predictions post. If you want one of those, then Gruber is your man.
However, with iOS 5 expected to be a significant update, more so perhaps, than the last few big point releases, I have decided to write up a short wish list of features that I'd like to see, both from the perspective of a user and of a developer.
Notifications
This is the obvious and probably most requested improvement that Apple could make. Let's face it, the iOS notification system sucks. Everybody knows it. When the only notifications you received were missed calls, voicemails and SMS alerts, a modal alert box was acceptable. Throw in alarms, reminders, local and remote push notifications and various others and it's one huge clusterfuck.
One notification overwrites the previous one. There is no way to see a history of notifications. The modal dialog is annoying; especially if it pops up in the middle of another task for instance, in the middle of a game or typing an email. They are as obnoxious and irritating. If iOS 5 had one single improvement, this has to be it.
Contexts / User Modes
This is a feature that is borne out of a relatively recent pain of mine, yet it's something that I remember having on my phone years ago. We all use our iPhones in different contexts; at home, in the car, in the office. The chances are, we probably have different settings for our phone in these different contexts.
At home or in the office, I want to turn off Bluetooth to save battery and connect to my Wi-Fi network. In my car, I want Bluetooth on so I can stream audio to my car stereo using A2DP and disable Wi-Fi so I don't get network discovery alerts appearing while I'm driving. Currently, whenever I get in the car, to enable Bluetooth - from the lock screen - it requires SIX taps. When you get in and out of your car frequently, this gets annoying fast.
First and foremost I'd welcome a way of accessing frequent settings more quickly (without jailbreaking) but to be able to have different contexts with different settings and be able to switch between them quickly would be really handy.
Services
This is as much a developer want as a user want. If you're writing an app and you want to provide a "Share to Facebook/Twitter" function, an "Upload this photo to Flickr" function, or a "Send to Instapaper" function you have to roll your own (or use an open source component).
Wouldn't it be better if instead, a Twitter app installed on your phone could provide a "Post to Twitter" service which other apps could use? Services would be registered with the user's permission when the app first launches and can be disabled at any time from the Settings app. Other apps could then use the SDK to query for services they can make use of. Services would handle requests in the background making the process as transparent as possible.
Improved lock screen
The current iPhone lock screen is a big ol' waste of space. I'd expect it to be part of any improved notification system so you could see, at a glance, your recent notifications.
One more thing...wireless synching
People have been talking about wireless synching of devices ever since the iPhone debuted. With Apple's new iCloud service right around the corner, those voices have been getting louder.
Realistically, I still believe that cable is going to be the main method of synching your iPhone in iOS 5; at least for music anyway. USB 2.0 remains faster than Wi-Fi and USB 3.0 and Thunderbolt are much faster. But there are other things that could be synched wirelessly - app updates for instance, could happen automatically in the background. I'm rather obsessive about clearing my iOS badge notifications as it is and I'm incredibly bored of having to launch the App Store just so I can hit the "Update All" button.
I'd also like to see better synchronisation of preferences and state across devices. Where it makes sense, apps (games included) should be able to synchronise their state - the last position in your Twitter stream, your game progress etc. - including your Mac! Incremental OS updates would also be welcome.
Sync is a problem that developers have tried to solve themselves and as they have found out, it's not easy. Some kind of native sync API in the SDK, one that perhaps ties in with Core Data, would be a massive feature for developers and ultimately, for users.
It's the software, stupid
Whatever Apple has up their sleeves today, I'm certain that the focus would be joined-up computing. Lion, iOS 5 and iCloud, whatever it is, have to be aimed at making the multi-device experience better because as things stand today, it's pretty half-baked.
I expect WWDC to demonstrate why it's the software that matters and that it's software that gives Apple the competitive advantage.
Twitter for iPhone (formerly Tweetie) has long been one of my favourite iOS Twitter clients (#dickbar fiasco not withstanding) and has some innovative UI features, particularly it's much imitated "pull-to-refresh" behaviour.
One particular feature I like is the ability to swipe on a tweet to reveal contextual buttons underneath. Like a lot of gestural UI patterns, it does suffer from lack of discoverability but as a shortcut to common operations it's a nice power feature that makes it possible to fit more UI within the constraints of the iPhone's relatively small screen space.
LRSlidingTableViewCell is my attempt at creating a reusable sliding table view cell that you can use in your projects - as with most of my open source code, it's released under the MIT license and while you do not have to, a credit within your app if you use this is always appreciated.
Using the cell is straightforward; it's API is simple and can generally be used as a drop-in replacement for a standard UITableViewCell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = @"CELL_IDENTIFIER";
LRSlidingTableViewCell *cell = (LRSlidingTableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[LRSlidingTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
}
...
return cell;
}
In order for the cell to be useful, you need to add your foreground content to the cell's contentView, like you would with a normal cell (or you can use the built-in text labels) and provide a custom background view for the cell by assigning it to the cell's backgroundView:
cell.textLabel.text = @"My sliding cell";
cell.backgroundView = [[[MyCustomView alloc] init] autorelease];
When the user swipes over a cell (from left to right), the content view will slide off-screen to reveal the background view. The cell can be reset by manually calling slideInContentView on the cell.
To complete the user experience, you should slide the content view back in to view when the user swipes on a different cell, or starts scrolling. LRSlidingTableViewCell doesn't do this automatically but this can be achieved easily using a few delegate hooks. To see how this is done, check out this example controller.
Until recently, deploying ad-hoc builds of iOS apps to beta testers was a pain. You needed to maintain a list of testers, obtain their UUIDs, manage provisioning profiles and compile and distribute the builds (and provisioning profiles) yourself.
As of iOS 4.0, it has been possible to package up builds as IPA files and distribute them via the web, making it possible for your testers to install your builds with a single tap.
Back in August last year, iOS developer Hunter Hillegas released iOS BetaBuilder, a small OSX utility that allowed you to take your built app and create a distributable package, including an HTML index page and manifest file that you could upload to your website and send the link to your testers.
Whilst this was a great first step, I felt it suffered from shortcomings, namely that it was GUI app that I was required to run manually. In addition, I still had to launch Xcode and run a Build and Archive myself. Ideally, I wanted the release process to be as automated as possible.
Introducing the BetaBuilder gem
As a Ruby developer, I am used to using Rake to run automated tasks so I took the various snippets of code that I had been using to automate the various parts of the Xcode build process and packaged them up as a Rake task library that I released, with Hunter's permission, as the BetaBuilder Ruby gem.
Using the gem is straightforward, and you can use it like any other Rake task library. If you're not familiar with Rake, there is a nice article by Martin Fowler that covers a lot of the basics.
Here's a simple BetaBuilder configuration:
BetaBuilder::Tasks.new do |config|
# your Xcode target name
config.target = "MyGreatApp"
# the Xcode configuration profile
config.configuration = "Adhoc"
end
Adding this to your project's Rakefile will generate two tasks in the beta namespace (you can override this). You can view the available tasks by running rake -T from the command line. The following tasks are available:
rake beta:build # Build the beta release of the app
rake beta:package # Package the beta release as an IPA file
So, you now have a way of quickly building and packaging your app as an IPA. You don't have to run them both; Rake has task dependencies and running rake beta:package will automatically run the build task first.
So what about deploying your app?
Deploying your app to TestFlight
In the first release of BetaBuilder, it was possible to distribute your built package using the same web-based method as the iOS BetaBuilder OSX app. Since then, BetaBuilder has introduced the concept of deployment strategies.
The gem currently bundles two strategies (and it's possible to create your own too). The first is the original web-based deployment strategy: your app will be packaged up with an HTML index file and manifest file and will be copied over to a remote server using SCP. For more information on this, check out the README on Github.
What I wanted to focus on in this post is distributing your app via the excellent TestFlight service, which has just launched publicly at the time of writing, having been in private beta for several months.
TestFlight is designed to be an end-to-end solution for ad-hoc app distribution, including managing of all of your apps, releases and testers. It also lets you create distribution lists to group your testers (e.g. Internal, Beta Testers). Better yet, it's free for developers.
Having used TestFlight during it's beta period, I'm convinced that this is the best way of distributing your ad-hoc builds. It makes everything simple for both you as a developer, and your testers. Better yet, they provide a simple API for publishing builds so it made perfect sense to create a TestFlight deployment strategy for the BetaBuilder gem!
Once again, configuration is straightforward; all you need are your TestFlight API token and team token:
BetaBuilder::Tasks.new do |config|
config.target = "MyGreatApp"
config.configuration = "Adhoc"
# configure deployment via TestFlight
config.deploy_using(:testflight) do |tf|
tf.api_token = "YOUR_API_TOKEN"
tf.team_token = "YOUR_TEAM_TOKEN"
end
end
Now, in addition to the previous tasks, a new task called beta:deploy will be available. When you run this task, you will be prompted for the release notes for your build, then your package will be build and uploaded directly to TestFlight.
It is also possible to configure the task to fetch your release notes programatically (e.g. from a CHANGELOG file) and you can also specify which distribution lists the release should be made available to. Again, see the README for more details.
Finally, if you would rather not fire up Terminal.app to deploy your builds and would rather do everything from Xcode, that is easy too. Simply create a new Shell Script target in your project that contains only a "Run Script" build phase containing:
rake beta:deploy
If you have problems, you my need to make sure that your PATH and other environment variables are set up correctly. I usually find adding a source ~/.profile to the beginning of my Xcode script build phases ensures everything works as expected - there may be a better way of doing this however.
Now you simply need to select the new target in Xcode and hit build. Thanks to Colin Humber of TestFlight for this suggestion (he tells me that they are using the gem for some internal projects at TestFlight - cool!).
The gem is licensed under the MIT license and the code is available on Github. If you have any ideas for improvements or alternative deployment strategies, please get in touch, or fork the project on Github and send me a pull request.
Addendum: archiving your builds
One thing I didn't originally cover was archiving your builds. @simonj asked me on Twitter:
Hi Luke - cheers for update of betabuilder. Was wondering though, what do you do about archiving dsyms?
It's a good question; it's usually a good idea to archive your builds (including their dSYM files) so you can symbolicate any crash logs that users send you. Xcode can do this for you with it's "Build and Archive" option. It packages up the build and the dSYM in an "apparchive" file (basically a zip file) in ~/Library/MobileDevice/Archived Applications/.
The good news is, BetaBuilder can do this for you too. It generates a beta:archive task that will do exactly the same thing as "Build and Archive". It even saves the archive to the same location. You can also configure BetaBuilder to automatically archive each build by setting the auto_archive option to true (it's false by default). You can then use something like Dropbox to back-up this archive directory.
Since I bought my new MacBook Pro around 6 months ago, I've suffered with an intermittent issue where, on waking from sleep, I would be presented with nothing but a black screen and a visible cursor.
Despite much googling, I struggled to find references to other people having this issue. I turned up numerous search results regarding a "black screen of death" issue dating back several years but this doesn't appear to be the same problem.
The screen is on, the MacBook is awake (I can VNC and SSH into the machine), and there is a cursor, I just can't see the desktop. The screensaver kicks in just fine (and is visible). Connecting and reconnecting to an external display didn't fix the issue.
I've always had to hard reset, potentially losing any work I may have been in the middle of, until today, when I decided to ask on Twitter if anybody else had suffered with this problem. I had several affirmative replies but no fixes other than rebooting, until I received this tweet from @BorrOs:
@lukeredpath I always get that black screen. I simply type my password to unlock and I have my session back!
And it worked! He went on to explain that what appears to be happening is that OSX is asking for my password to unlock the screen after going to sleep, but the dialog box is hidden due to some unknown bug. The text field is focussed, you just can't see it, hence the above workaround.
Now, I'm not entirely sure under what circumstances this bug is triggered. I use NetworkLocation to turn on the screensaver password only when I'm outside of my home (usually, when I'm working on a client site) and it's possible that it could be related to that (e.g. I put my MBP to sleep by closing it when leaving my client's site and re-open it when I get home).
Whatever the cause, I struggled to find much information about this so I hopefully this post should be of some help to others in the future.
I was recently working on an odd bug in one of my client's iPhone apps; the app in question fetches data from a Rails-based REST API and stores the records in a local Core Data store.
Unfortunately, some users were reporting that some records were simply not appearing. Try as I might, I was unable to reproduce this issue locally.
With some help of one of the users who was having this issue, I was eventually able to track down the core of the problem; the records were fetched from Core Data using a predicate that matched records with a createdAt attribute greater than a particular date however, the records in the database all had nil createdAt dates.
Having located the root cause, the next step was to determine why those dates were nil and the problem lied in the way the dates were being parsed.
The dates returned by the Rails API were in the format '2010-11-28T20:30:49Z' and were in UTC. The method that parsed this string and tried to convert it to an NSDate was implemented as a category on NSDate that looked like this:
@implementation NSDate (StringParsing)
+ (NSDate *)dateWithISO8601String:(NSString *)dateString
{
if (!dateString) return nil;
if ([dateString hasSuffix:@"Z"]) {
dateString = [[dateString substringToIndex:(dateString.length-1)] stringByAppendingString:@"-0000"];
}
return [self dateFromString:dateString
withFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"];
}
@end
The code first checks that the string is not nil, then replaces the 'Z' at the end of the timestamp with a zero UTC offset string that we can match using NSDateFormatter. Finally, it calls this method:
+ (NSDate *)dateFromString:(NSString *)dateString
withFormat:(NSString *)dateFormat
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:dateFormat];
NSDate *date = [dateFormatter dateFromString:dateString];
[dateFormatter release];
return date;
}
This is fairly straightforward; it initialises a new NSDateFormatter, sets the format of the string we are trying to parse and then parses the date.
Ensuring consistent NSDateFormatter behaviour
It's hard to see what could be going wrong with this, especially as I couldn't reproduce the issue. I had already confirmed that the customer with the issue was running the latest version of iOS (as was I) and we were both using the same locale ("en_GB" in this case).
Stumped, I headed to StackOverflow and Twitter, where Ben Dodson pointed me in the right direction.
The solution was simple but not obvious; to ensure that the NSDateFormatter behaviour is consistent across locales and date/time settings, you should explicitly set the appropriate locale of the date formatter which in this case, was "en_US_POSIX". The new method looked like this:
+ (NSDate *)dateFromString:(NSString *)dateString
withFormat:(NSString *)dateFormat
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:dateFormat];
NSLocale *locale = [[NSLocale alloc]
initWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:locale];
[locale release];
NSDate *date = [dateFormatter dateFromString:dateString];
[dateFormatter release];
return date;
}
Apple explains this in even more detail in this tech note - if you ever work with dates and timestamp strings from web APIs, I highly recommend you read it.
I spent several hours banging my head against a wall on this one, so hopefully this post will prove useful to somebody else in the future.
Back in July, I wrote a blog post explaining how I automate the process of getting my monthly iTunes Connect reports for my iPhone apps and converting them into FreeAgent invoices using a couple of shell scripts.
The scripts save me a lot of time but I still had to go through the process of running the processing script against each individual report (or reports). This was fine as some months I wouldn't generate an invoice for a particular region (as I wouldn't have sold enough units to receive any money from Apple).
However, not long after I wrote that post, Apple implemented a new payment system whereby all regions are paid in one single payment, meaning that all regions would be cleared each month.
This meant, since then, that each month I would routinely fetch all of the reports and run my processing script against each report downloaded (i.e. all of them). Yet another opportunity to automate presented itself.
The solution is a very simple Ruby script that loops through all of the reports and runs them through my processing script. It groups the US and WW reports into one invoice as they both share the same currency. It automatically works out the location of the reports because my report downloader uses a fixed directory name format - it just looks for last month's reports.
If you wanted, you could go one step further and combine the calls to the report downloader and this script into one - you could even run it on a fixed schedule (cronjob, launchd task) each month although I kind of like the monthly ritual; it's up to you.
View a list of all previous posts