Unread is Now a Supertop App

Supertop, the folks behind Castro — my favorite podcast app — are the new home for Unread, the RSS reader for iPhone and iPad that you have probably heard of by now. I am really happy for this news. Since I moved on to my new job at Bloglovin, Unread had become something akin to a beloved but grumpy family dog in a home with a fragile new baby. It’s better for everyone that Unread has moved on to a new home: better for me, for Supertop (obviously), and most of all for Unread’s users.

I’m proud of the work I put into Unread, and can’t wait to see what Supertop does with the foundation I laid down. Unread has the cleanest code I’ve ever written for a personal project, so I’m hopeful that it won’t be a burden for Oisin and Padraig to wander through it. The hardest part has been dealing with maddening App Store policies. You would not believe the hoops they’re having to jump through to try to migrate existing users to the new Supertop version. They’re working really hard to make it a great experience for everyone who is already an Unread customer.

|  11 Sep 2014




The Best of All Possible Xcode Automated Build Numbering Techniques

At Bloglovin, we use an automated build numbering setup in Xcode that sets the build number to the current number of git commits in the active git branch. It has worked mostly well, though our technique has not been without frustrations.

The Frustrating Old Way

The technique we have been using is based on this post by the inexcusably-named “Cocoa is my Girlfriend”1. It works as follows:

  1. A run script grabs the git commit count and echo's it out to a file called InfoPlistWhatever.h. The name doesn’t matter.

  2. The build settings for the target enable Preprocess Info.plist File and set the value of Info.plist Preprocessor Prefix File to our file from step 1.

  3. The file from #1 sets the git commit count as the value of a #define preprocessor variable called CUSTOM_BUILD_NUMBER or something to that effect.

  4. The Info.plist screen in Xcode is updated to use CUSTOM_BUILD_NUMBER instead of an actual build number.

This technique works as advertised, but it has several really annoying drawbacks:

The Best Possible Way

After much Googling, I came across this post by an anonymous commenter. This technique is far better. It avoids all of the pitfalls of our previous technique, and is even easier to set up.

All you need to do to enable this technique is to add a run script build phase any time after “Copy Bundle Resources”:

#
# Set the build number to the current git commit count.
# If we're using the Dev scheme, then we'll suffix the build
# number with the current branch name, to make collisions
# far less likely across feature branches.
# Based on: http://w3facility.info/question/how-do-i-force-xcode-to-rebuild-the-info-plist-file-in-my-project-every-time-i-build-the-project/
#
git=`sh /etc/profile; which git`
appBuild=`"$git" rev-list --all |wc -l`
if [ $CONFIGURATION = "Debug" ]; then
branchName=`"$git" rev-parse --abbrev-ref HEAD`
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild-$branchName" "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
else
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
fi
echo "Updated ${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"

This script uses the PlistBuddy command line tool to edit the build number of the Info.plist in the /foo/DerivedData/bar directory, the target’s build directory. This is why git isn’t dirtied when the build number changes. It doesn’t matter what you’ve entered in your Info.plist screen in Xcode. It also updates the build number for every build, not just the first build after a total clean.

The code above includes some modifications to the code posted by that anonymous commenter. It uses the git commit count for the build number, but if it detects that we’re using our Debug build configuration in Xcode, it suffixes the build number with the current git branch name. This avoids potential build number collisions across feature branches under simultaneous development.2

Update Sep. 14, 2014 - Johan Kool, arguably the man with the coolest name on the Internet, has kindly corrected some potential bugs in my new run script. The corrections have already been applied above.


  1. Seriously. It’s 2014. Rename it. 

  2. At Bloglovin, we have fix or six new feature branches in active development at any given time. Build number collisions happen daily. While not a huge problem, I don’t like that feature-specific builds will be hard to identify in crash logs. This new method of suffixing the build number should alleviate this problem. 

|  10 Sep 2014




Time-Saving TextExpander Snippets for Xcode

I literally cannot get through a work day without using TextExpander. It’s one of the first things I install on a new Mac. Over the years I’ve built up a lot of time-saving Xcode TextExpander snippets. I’ve compiled all the universally applicable ones into this exported backup. Here are some of the highlights from my collection:

slef, vodi

self, void

These fix my two most common typing mistakes in Xcode.

ttableview

This fills out an entire stub .m file for a table view controller.

First copy the name of your new UITableViewController subclass into the clipboard. Delete all the text from the stock .m file that’s created. Then type ttableview. It expands into a bare-bones table view controller. It’s especially handy when you know you won’t need to uncomment the editing methods of the default Xcode template.

ssingleton

+ (instancetype)sharedInstance {
    static dispatch_once_t once;
    static %clipboard * sharedInstance;
    dispatch_once(&once, ^ { sharedInstance = [[self alloc] init]; });
    return sharedInstance;
}

Used prudently, a singleton isn’t a liability. But if you’re going to use it, make sure you’re initializing it correctly: once and in a thread-safe manner. Like the ttableview one above, start by copying your class name into your clipboard.

dmain

dispatch_async(dispatch_get_main_queue(), ^{

});

This is the preferred way to kick off a block of code to be executed on the next turn of the main thread runloop. No, _async is not a typo. I have never had a need to use _sync, and often just the opposite: the synchronous call can cause terrible race conditions.

dbg

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

});

dafter

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        
});

I use this one a lot when debugging new code, for example, when simulating a long-running background operation before that background code is available to build against.

wself

typeof(self) __weak weakSelf = self;

People have all kinds of solutions for this problem, but I prefer to use a native solution over a third-party dependency when it’s convenient to do so. This snippet makes it trivial.

kkvo

static void * JTSSomeContext = "JTSSomeContext";

#pragma mark - KVO

- (void)addObservationsTo:(NSObject *)object {
    [object addObserver:self forKeyPath:@"keyPath" options:NSKeyValueObservingOptionNew context:JTSSomeContext];
}

- (void)removeObservationsFrom:(NSObject *)object {
    [object removeObserver:self forKeyPath:@"keyPath" context:JTSSomeContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (context == JTSSomeContext) {
        if (object == self.myObject) {
            if ([keyPath isEqualToString:@"keyPath"]) {
                // Stuff
            }
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

I don’t ever use the result of this snippet as-is — it needs a specific target class and key path — but it sure saves a helluva lot of time and potential errors, like forgetting to call super.

fformat

[NSString stringWithFormat:@""]

This one even drops your cursor between the double quotes in the format string.

llocal

NSLocalizedString(@"", @"DESCRIPTION_HERE")

This one, like fformat above, drops your cursor between the first double quotes. You’re less likely to forget to localize a string if you make this snippet a muscle memory.

lltab + suffix variants

NSLocalizedStringFromTable(@"

If my project is using NSLocalizedStringFromTable() instead of NSLocalizedString(), I use the lltab snippet in combination with a project-specific suffix snippet. For example, in Unread I use lltun which turns into:

", @"Unread", nil)

The workflow is then lltab + text + lltun, the latter of which becomes a muscle memory pretty quickly as I get deep into a project.

ccoding

#pragma mark - NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [NSCoder encodeObjectIfNotNil:_item forKey:@"key" withCoder:aCoder];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        _item = [aDecoder decodeObjectForKey:@"key"];
    }
    return self;
}

bgtask

 UIBackgroundTaskIdentifier backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        // handle expiration
   }];
    
    // Do some long running operation then:

   [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier];

|  31 Aug 2014




Finding Objectivity When You Feel Lost as a Designer

Francesco Di Lorenzo, an iOS developer, wrote to me1 a while back:

I am stuck with personal projects because I feel like I have reached the limit of what I can do with my current design skills. Could you give me some hints on where to go to learn the basics of design? I can’t get my head around even really basic stuff like color theory and simple typography because I miss the fundamental concepts. As of today I design following my instinct and by imitation.

The bad news for Francesco is that he wrote to the wrong guy. I feel the same way, all the time! I especially identify with that last line:

I design following my instinct and by imitation.

The good news for Francesco is that this is, from what I have observed, a universal experience among people in creative professions. Creative work is open ended. There are no objectively wrong solutions to creative challenges, yet there is still a sense that you’re obligated to demonstrate expertise. Folks in creative professions end up caught in the tension between the freedom to do anything and the duty to become an expert. I am not referring to what is sometimes called "Imposter Syndrome," though that is a related concept. Instead, I’m talking about how we judge what is good and what is not good.

There’s a scene in the film Basquiat in which Andy Warhol, played by David Bowie, is collaborating with Basquiat on a painting. Warhol is taken aback by the changes Basquiat makes:

Basquiat scribbles lines and text across pristine areas of Warhol’s silk-screened logos.

WARHOL

What are you doing? You’re painting out everything I do!

Warhol goes silent while Basquiat keeps going. Warhol looks like he’s on the verge of changing his mind.

WARHOL

[Genuinely] Wow, that’s great.

Basquiat steps back from the canvas.

BASQUIAT

There. That’s better.

WARHOL

You really think so?

Warhol sighs.

WARHOL

I can’t even see what’s good anymore.

I have no idea if that bit of dialogue is true-to-life, but I love it just the same. It’s both reassuring and terrifying to watch an artist like Warhol — either a revolutionary new master or an iconoclastic fool, depending on whom you ask — find himself just as lost in front of his work as you or I might feel in front of ours.

There are no Absolutes

Whatever your religious or moral convictions might be in the rest of your life, in art there are no absolutes. Nothing is truly good or bad. There are only two forms of certainty:

  1. Does the work successfully embody the artist’s intention?

  2. Is the artist’s intention admirable?

Let’s use television shows as an example. The original Law & Order may not have the same gravitas and high-art merit as a golden-age-of-TV series like The Sopranos, but that doesn’t mean it isn’t a great show. The shallow characters and cheeseball zingers are part of the game. Law & Order is a near-perfect expression of a certain kind of entertainment. The intention behind it is different from shows like The Sopranos. It’s unfair to judge them by the same criteria.

The same is true of any creative work, from iOS app design to fusion cuisine. You choose your rules out of thin air. But choose them carefully. There’s no objectivity outside of your intention, except the question of whether your intention is admirable in itself. A pornographic film, for example, might be of good or bad quality, but the intention behind it is less admirable than the intention behind a given art house film with the same visual content. It takes wisdom to see past the particulars and into the intention.

Objectivity and Taste

I think the way out of a dilemma like Francesco’s is to shove aside one’s fears and focus first on choosing an appropriate set of rules. There’s no pure objectivity, so we need to find something to do the job of objectivity in its place. We just pick something. Anything. It’s messy, but it isn’t as hard as it sounds.

It starts with taste. Everyone has her own taste. Your taste is a composite of the tastes you’ve borrowed from people you admire and the taste you discover for yourself. Wading through this mixture, you will usually find yourself drawn to certain kinds of artistic intention. The important part is choosing an intention that you can cling to when the work gets hard.

I’m reminded of my friend Kris Donegan, a Nashville session guitarist and one of the best musicians I have ever known. Kris doesn’t play every style of music. He could, if pressured to do so, play highly technical, meedly-meedly-meedly-mow, Malmsteemish math rock — but it would require an enormous amount of effort and practice to get there. Instead, Kris plays a distinct style of gutsy Americana rock guitar. He works diligently on crafting a tone that is uniquely his own: a certain touch on the strings, a curated collection of effects and boutique amplifiers. Kris is an ingredient which, when added to a song, makes it richer and more flavorful. To a math rock fan, Kris’ playing is all wrong. But to the people who continue to seek out Kris for their albums, his playing is just right.

Always Bust Ass Like a Beginner

In his email to me above, Francesco asked:

Could you give me some hints on where to go to learn the basics of design? I can’t get my head around even really basic stuff like color theory and simple typography because I miss the fundamental concepts.

Like Francesco, I have little or no formal training in these subjects, but I don’t think that’s a problem — not for creative work. In creative work, formal training is ninety percent bullshit. The only thing that really matters is your willingness to always be learning, to always bust some ass like an eager beginner. I read with awe this post by the developer of Capo, in which Chris Liscio describes how he took on the task of learning how to write machine learning software in order to improve a chord detection feature by leaps and bounds:

When I returned from NAMM and came down from all the excitement of the show, I decided that enough was enough and I needed to tackle all the research I was uncomfortable and afraid of. I had never taken an Artificial Intelligence or higher level Statistics courses at school, but all the research papers I was reading over the years made frequent references to concepts that I was completely unfamiliar with.

I re-read all the papers I’ve used over the years for reference, and read them again. I got in touch with Taemin Cho to get some clarification on some of his work, and he led me to newer papers which required additional learning on my part. For a solid 6 weeks I was doing nothing but reading papers and exploring in MATLAB.

I resolved to not just build a new chord detection engine for Capo, but to build an entire chord detection engine factory. Armed with my copies of MATLAB and Xcode, and an unwillingness to fail, I set forth on my quest.

So, Francesco, my answer would be to look in places so difficult to understand that they scare you. You can handle it. Let me know how it goes, you’re probably braver than me.


  1. Thanks, Francesco, for permitting me to repost this here. 

|  31 Aug 2014




Quotebook: a Commonplace Book for Your iPhone or iPad

My friends at LickabilityMatt Bischoff, Brian Capps and Andrew Harrison — have launched Quotebook on the App Store today.

Quotebook is a clean take on a very old tradition. Long before the typewriter, but sometime after the printing press, avid readers would collect their favorite quotes in a single place. This collection was typically gathered into a hand-written journal called a “commonplace book,” but “Quotebook” is objectively a better name.

I like apps with attention to detail, and Quotebook has it, right down to making sure every button and label is set in a custom font and color — even the action sheets. Get it today.

|  27 Aug 2014