Life, people and technology.

Ending 2014 on a High Note With Hone

I haven’t posted much about work in the past few years. I’ve posted specific things about iOS, web and Mac engineering, but not really about the products that I’m working on.

Now, I can talk about work again. We launched not one, but two things in the past few weeks. For the world at large, mostly the first one matters. For me, both are huge.

First, there’s Wire. I was Wire’s first engineer. We’ve been working on it for a while and it feels immensely gratifying to finally ship a new product. I don’t have much to add to all the press and buzz, except to say that I’m privileged to work with a fantastic team on this, especially Priidu and others on the design side of the house, and it’s been an incredible learning and growth experience for me.

If you’ve worked with me, you know that I’m never entirely happy. There’s always more and better work to be done. This is true also for Wire, it was to be expected, and indeed we keep working on it. There are many aspects of the product that we need to improve, the team is looking at the qualitative feedback as well as numbers and stats, and continues to burn long nights and weekends to release new versions.

You’ll notice that the title of this post is not about Wire, though. Now that it’s out, I’m dialing back my personal involvement in it to focus on another project I’ve developed on the side, Hone. It’s in a much earlier stage, and it’s not yet launched in the sense of being publicly available. Rather, what we’re doing is inviting more people to join our closed beta. (If you’re a designer or engineer working on iOS or Mac apps, and Hone sounds interesting to you, I encourage you to sign up immediately. The response has been great and we’ll close the first round of signups very soon. We’ll probably have more rounds at some point in the future too.)

Hone is a very different product from Wire. It is a niche tool for designers and developers, people making things rather than consuming—but these days, “making” is a growing target, as software continues to eat the world. You can read more about Hone in my previous post and on the project blog.

I’ll continue to help the Wire team where I can, but I’ll mostly spend my time with Hone now. That involves two things: working tightly with the closed beta members to see how Hone can help them best, and adjusting the software and components based on the reactions that we get. I’ll need to balance between the vision of the product, and the results of that vision colliding with realities of other people and teams. Hone is based on many years of experience of myself and others, but we’ll need to test it with more people and teams before it’s ready.

So, thank you, 2014. You were a great year. You brought me lots of pain and misery as I was grinding my teeth on many hard problems while making Wire and Hone, but nothing beats the joy and satisfaction of great progress with both of them. 2015 will be great.

I Finished GTA3 on iPad

I recently finished playing GTA3 on iPad.

By “finished”, I mean I got through the final mission when the credits rolled and the game is officially considered “over”. There’s around 10 missions left according to the stats, but I don’t know how to find those, and I did not complete all the side missions (police, medical, or firefighting). But I do feel that the final mission brought some closure, and I’m probably done with the game.

If you’re not familiar with it, GTA3 is a key episode in the long-standing Grand Theft Auto franchise. It’s the first one that uses a 3D perspective, and was originally released in 2001. This is truly ancient in computer history.

Nevertheless, I never played it originally when it was released, because I wasn’t really into gaming and didn’t own any required console at the time. That it was released at all on iPad many years later, and that it plays so well, is a true testimony to its original high quality.

Yes, I can attest to that: it truly plays well on an iPad. Maybe not fully as well as some other titles that are designed for touchscreens, but remember, this is a game that was designed long before any good touch device became mainstream. They managed to port it over and it is highly playable. The touch countrols are a bit awkward for a game that was originally designed for a console controller, but it just takes a little bit of time to get used to, and you’ll be up and running in no time.

Now, I admit. It did take me more than a year of calendar time to get through this game. It’s ridiculously long, but of course, it wasn’t a sustained effort. Sometimes I wouldn’t play it for months, and then I would just pick it up again. That it kept sucking me in and re-inviting, is a testimonity to the thing that matters the most in games: the core gameplay experience.

The gameplay and missions were pretty smooth and straightforward, though I did have to try some of them many times. But the design is such that you can keep learning while repeating a mission until you nail it. This went on right until I got to the final mission, The Exchange, which I miserably failed the first few times. The final mission becomes much easier if you have access to a bulletproof car and all the weapons. You get the bulletproof Patriot car from one of the previous missions, and you can get the weapons by finding all the 100 Hidden Packages in the game.

So I took a bit of a detour and went on a journey to find all 100 of them, which is also a cool way to find more corners of the game universe than you otherwise woudl have. Of course, I wouldn’t go look for them all through the universe myself, but there’s some great guides available these days. I recommend this one that I followed myself and I indeed did get all the 100 packages using that guide. This gives you all the weapons, and makes the final mission a breeze and even somewhat anticlimactic, I completed it just after a few tries.

GTA3 had a rich universe and my favorite style of open-world gameplay. You do have missions, but you can pick at your leisure when and how you go through them. In the meantime, you can wonder around the world, do side missions or just generally cause mayhem. Another more recent title of the same genre I would recommend is Far Cry 3. I’ll definitely try also Far Cry 4 that will come out later this year.

Why do I keep spending my time on these games? It would be an interesting psychological exercise. As I go forward, I’ve found that I keep developing my taste for games that I like and don’t like. Open-world single-player shooters a la GTA and Far Cry seem to be my favorite.

Gaming device choice is another interesting question. I recently got a retina iMac with maxed out specs that is a fantastic computer. Yet, sometimes I use my iPad Mini just to “get away” from my computer (nevermind that it is also a powerful computer, just in a different form factor). It’s also more convenient when traveling. Touch controls can’t entirely match a mouse and keyboard, but they come pretty damn close in most of the gaming scenarios that I see.

GTA3’s distributor Rockstar Games has released some other GTA classics for iDevices (GTA San Andreas and GTA Vice City). Will I play those as well? Not sure at this point. I might.

How to Install Windows With Boot Camp on New Retina iMac

I got a new Retina iMac recently. It is every bit as fantastic machine as the hype says. Since I use it for both work and play, I did not hesitate to splurge a ridiculous amount of money on it and max it out in all the tech specs, as I will be heavily using it for the next several years. I guess I might write more posts about different aspects of the machine.

The one thing I do want to write about today is how to get Windows to work with Boot Camp. My main (well, the only) use case is Windows games with Steam.

TL;DR summary: don’t bother with Windows 7. The installation doesn’t work correctly and the whole system is aging. Just buy the new Windows 8 from Microsoft (Windows 8.1 is the current version as of this writing) and live happily ever after.

Here’s a bit more detail about how I got to that conclusion.

I have a legal box copy of Windows 7 Home Premium that has been serving me well over the years. Windows 8 caused a big fuss with its Metro interface and did not sound like a good idea on desktop, so I’ve been avoiding it until recently.

So, my first step was to try to get Windows 7 installed on Mac Bootcamp partition. The way to do it these days is to run Boot Camp Assistant on Mac, that prompts you for the location of Windows ISO image, and then prepares a bootable USB stick containing both the ISO image and the extra needed drivers.

So first I had to convert the DVD into an ISO image. No biggie, plenty of ways to do that. And then ran the Assistant that prepared the USB stick, Windows partition, and rebooted into Windows installation.

That’s where trouble started, and I had to revise yet another piece of my hardware knowledge. I had a USB keyboard and mouse available, that I thought I could just plug in to the computer, and they would be backwards compatible with the ports and software and would work under any conditions, including Windows installation. Apparently, this is no longer the case. The USB3 ports on recent Macs are apparently not supported without drivers in Windows at all, and so what the Boot Camp Assistant does, is augment the Windows installation image with these drivers. And it sort of worked, I could see the mouse cursor. Sometimes. But then, installation froze at 65%, which is supposedly another artifact of the USB system (remember, the installation media is also on a USB drive that is plugged in to the USB port). When I rebooted straight into Windows installation, there often would not be any mouse cursor and keyboard access at all.

All in all, Windows 7 just wouldn’t install for me correctly at all. Fair enough, let’s give Windows 8 a shot.

I was prepared for many hours of ordeal and wasted money, trying to sort through Microsoft’s different Windows versions and licensing options. If it is an “upgrade” version, do I need to have previous versions around as well? What if there are similar driver troubles and I find myself having wasted money on a license, but still unable to install?

Fortunately, none of this happened, and all went fine. First, you head to Microsoft Store and buy a Windows license. Windows 8.1 comes in regular and Pro versions. You don’t need any Pro features for gaming, so I just got the regular one for 119 EUR. This gives you the license key. Very important. Write it down and hold on to it.

Next, you need the install media. You can click the “Download” link on your order page in the store, or you can go to this page. You can’t just download an ISO image: you need access to another Windows machine to run the installer that downloads the image. If you do not currently have access to Windows, I don’t know what to recommend you. Luckily enough, I had a Parallels Windows VM around in my Mac, so I could just run that downloader. It first prompts you for the license key, and makes it look like you are about to install Windows, but don’t be scared and just follow the prompts carefully. At one point, it offers you to save an ISO image and that is what you want.

So, now I had the ISO image of Windows 8.1, as well as a license key. I could run the Boot Camp Assistant that again prepared me install media, booted into Windows installation … and from there on, everything was smooth. Apparently Windows 8.1 has better USB3 drivers, so mouse and keyboard worked, as did the rest of the installation. Only at one point, it tells you “Almost done, do not turn off your computer” and seems like it is stuck on this screen, I think it was like 15 minutes for me. Eventually, though, it goes on, and the installation finishes fine.

8.1 is also much friendlier than 8 on the desktop. There is still some Metro interface around, and some dialogs talk about “Tap here”, but overall, it’s fine and you can stay in the regular desktop environment for most of the time.

Cultural Studies Based on Salsa Tequila

Every summer must have its summer hit. Two years ago, it was “Gangnam style”. Last year, it was some Avicii stuff and maybe “What does the fox say”. I did not know this summer’s, but I just recently found out about “Salsa Tequila”. This must be it.

The song climbed the charts in Nordic countries, the Netherlands and Belgium.

All that they did is just mash together a bunch of popular Spanish words. Add an inane but catchy tune, and a silly video, and make sure the instrumental version is available, and what do you get? Infinite mashup potential, that’s what.

Indeed, many people have taken to converting this to their own language, or some other language/culture they want to make fun of. I recently spent some time going through a bunch of them, because why not. Here’s my selection of the better ones.

Kudos to the video-makers for turning this to a meme. For example, pay attention to the sound device and where it’s inserted, some of these are pretty creative.

German. (Made by some Dutch guys.)

Dutch. It’s well made (by the same guys) but I don’t know enough about Dutch culture/language to appreciate the jokes.


Finnish. Appropriately named “Sauna and Nokia”.


Russian. These guys are lazy and didn’t even remix the song to their language. But they did make a video.

Heavy metal. (Well, it’s not a language, but cool video and music.)

“Folly of Fools”, Deceit, and Self-deception

I recently finished reading “The Folly of Fools: The Logic of Deceit and Self-Deception in Human Life” by American evolutionary biologist and sociobiologist Robert Trivers.

This is part of my reading series on the intermingling topics of security, psychology, and behavioral economics. The last book I read in the series was “Liars and Outliers” by Bruce Schneier.

I read these things because in the modern world, it is increasingly difficult to answer simple questions: What is real? What is important? Why and how do we make decisions? There are many companies who enormously profit from distorting your view of these questions. This has, of course, always been true with advertising and such, but increasingly so in the tech world.

This book is useful to reason about these topics. You’ll note mixed reviews on Amazon, and some of the one-star ones are from scientists who don’t see this book as scholarly. They’re right. It is based on science, but it is not a scientific/academic book.

It begins with an examination of evolutionary biology where the author’s background lies. While deception is “bad” as a human moral concept—thou shalt be honest and not deceive others, right—deception and the tools to counter it have been crucial in evolution and continue to be so in the modern human world.

Family and sexual topics are examined, and then onwards to many practical applications and anecdotes. I was delighted to see my own field of usability and human factors also covered, which was done in the form of discussing hierarchy and relations in airplane crews, and how obedience and self-deceptions have caused several airplane crashes.

War, history and religion are covered. In the part about false historical narratives, the discussion is around US, Palestine and Israel—probably among the most loaded topics there can be, but deconstructing the narratives from self-deception perspective can help bring rationality to the table. Modern Russia was not mentioned, but would fit into the series very well.

How I Fixed HTML List Items Jumping Around

Does this kind of thing drive you nuts?

I was working on a list of items in HTML. It is a bunch of list items, and the selection is indicated with a border. Every time you change the selection, though, the list jumps around by a few pixels. And it is terrible.

(None of this is particularly insightful to anyone who works on this stuff day to day. But I am brushing off my web skills that haven’t gotten much use for a while, and I was glad I could reason through this problem like I did. Maybe this is useful to someone else too.)

The problem is caused by how the CSS box model works. Border adds to the dimensions of the item. The non-selected items do not have a border, and are thus narrower. When a box appears, the width of the item changes, and the items after it shift right. When a box disappears, the width decreases, and the trailing items shift left.

Annoying. But easy to fix.

Now, it is possible to hack the box model in various ways. I don’t know if there is a box model that would end this jumping around without the modification that I did. My fix is dead simple though: let’s add an invisible border to all the elements. There is no border-opacity CSS property, but this can easily be done with using RGBA color for the border, instead of the more common hex format. This way, the size of the elements stays fixed, just that the border is visible for the selected one, and transparent for the others, but the border is always there.

The result is much nicer. No more jumping around.

See and play with all the code in the fiddle.

A Tragic Story That Happened to Someone I Know

I heard a tragic story recently. One of those things that you see in movies, or news. Sadly, this is real and recent.

There was a couple planning to get married. He was older and had a family before. The marriage had been planned for a long time and everyone was looking forward to it.

The bride and groom were from different countries. The wedding was set to happen in the bride’s home country. A few weeks before the wedding, the groom flew in and they got busy with the final arrangements. Everything was set, and ready, and beautiful. The bride and groom were looking forward to the wedding, and another week after it that they would spend together.

A few days before the event, the groom’s family flew in. Two days before the wedding, the guys went out partying to town. Among them was the groom’s son from a previous marriage.

They got really drunk. At one point, the team dispersed, and the son wondered off. In the middle of a night, all by himself, and wasted beyond oblivion, he stumbled across a high ledge, and fell down.

The fall was many meters, and he was stopped by some bushes. Luckily, this was in a city and not in the woods, and he was soon found. He broke most of his bones and went into a coma.

The couple decided to cancel the wedding. They would still get married eventually, but much, much later, and somewhere else, and not in the elaborate way they had planned. Now, instead of spending time together as newlyweds, they had to spend time in the hospital and insurance, arranging the son’s medical evacuation to his home country by emergency flight.

I hope they do get married eventually, but events like this clearly leave a scar.

The saddest part is that this was entirely avoidable and unnecessary. Someone’s reckless behavior caused angst and suffering not only to themselves, but the whole family.

We’re all free people. We do what we want. Just be safe and don’t be dumb. A police officer once said that if you took away “alcohol” and “stupid”, 90% of police work would disappear. And you would avoid a lot of pain and misery.

How to Correctly Configure Building Private OS X Frameworks

I’ve been recently dealing with building a private / embeddable framework on OS X. By “private framework”, I mean a framework that’s not installed system-wide, but one that’s bundled with an OS X application. Now that frameworks are also coming to iOS, it’s probably of wider interest, although this specific post is only about OS X. I spent several hours yesterday trying to get it to work, and thought to just document it, so that I wouldn’t forget myself.

TL;DR version: you need to change some build settings to the following values:

Skip Install = YES
Installation Directory = @executable_path/../Frameworks

You also need to add a “Copy files” build phase to your application target to copy the framework into its “Frameworks” group. And you also need to think about code signing, which I’m not covering here.

Okay. That was the summary. Below is an exploration of my adventures. And grab the example project.

The test project

I created a test project with Xcode 5.1.1, consisting of the default Cocoa application target. I then added a Cocoa Framework target with default settings. My test app is called HelloFramework, and the framework is simply called Hello. The code for the framework is very simple, here’s the header.

@interface Hello : NSObject

+ (void)helloFromClass;
+ (instancetype)sharedHello;
- (void)helloFromInstance;


And the implementations imports the framework: #import <Hello/Hello.h> and just show some modal dialog boxes a la good old Javascript alerts.

Okay, so far, so good. We add the Hello framework to our app target in “Link Binary with Libraries” phase. We add a new “Copy Files” phase to bundle the framework into the application. We run it in Xcode, and everything looks good. Dialog boxes come up.

Now, things start to get a bit weird.

Archiving trouble

Let’s archive the app target. The first sign of trouble is that the archive is not of type “Mac App Archive” as expected for the Mac app, but is of type “Generic Xcode Archive”, which most of the time in Xcode means “Probably Not What You Want”. But okay. Let’s try distributing it. I only have the choice of “Save Built Products” instead of exporting the app for distribution. Let’s do this anyway.

Here’s what’s exported.

So there’s both the application and the framework. But we don’t need the framework; we just want the application. If we peek inside the app bundle, we even see that is has a correct Frameworks group with the Hello.framework nicely sitting in there.

Well, let’s try to run the app. BOOM, it crashes.

What the what? Well, if we look at the error, it says right there what the problem is.

Dyld Error Message:
  Library not loaded: /Library/Frameworks/Hello.framework/Versions/A/Hello
  Referenced from: /Users/USER/Desktop/*/HelloFramework.app/Contents/MacOS/HelloFramework
  Reason: image not found

Okay, so it’s trying to load the library from a global path. Why is that?

System vs private frameworks

There’s two kinds of frameworks. The system ones go in /Library/Frameworks, and can be used by multiple applications. The default Cocoa Framework template seems to be set up for building these. I’m not sure what their future is, though, since a lot of app distribution happens through Mac App Store and these apps are sandboxed and I don’t think they can install system frameworks.

The other kind of frameworks, private frameworks, are the ones that are embedded in the actual app bundles. These are what we’re trying to build, and are failing so far in the example.

Turns out that the install location of a framework is part of its build settings. When we look at the Installation Directory of our framework target, it is by default, unsurprisingly, set to $(LOCAL_LIBRARY_DIR)/Frameworks, which resolves to /Library/Frameworks on my system. This is not what we want. We want the framework to be embeddable in apps, and so the installation path should be relative to the app that contains the framework.

What’s the right value? I found this post to be useful when working through this topic, but they recommend changing a bunch of values that don’t seem to be necessary. I found the official doc which says, hidden inside all the other wisdom, that the Installation Directory for private frameworks should be @executable_path/../Frameworks, which looks pretty reasonable.

Great! Let’s change the installation path. When we now archive our app, we see that it creates “Mac App Archive” as expected, we can run it, and it no longer crashes. Fantastic.

There’s a warning when archiving the app target, though. Let’s get rid of that.

Fixing the last warning

The warning says:

Warning: Installation Directory starts with '@executable_path' but Skip Install is disabled.

It’s not super clear, but it’s clear enough if you’ve dealt with iOS app archiving troubles before and are familiar with Skip Install build setting. Basically, for a given target, Skip Install says (in the negative) whether it’s an installable target or not, which roughly translates to “Is this the app that the user will run” (in my mind, anyway). Why it needs to do that in the negative, I don’t know, calling it “Installable” would be much clearer. But just negate it in your head and it makes sense.

Skip Install = NO makes sense for system frameworks. You do want them to be installable. Although how that actually works beyond them being exported when archiving, I don’t know exactly. Anyway, in our case, for our private framework, we don’t need it to be installable so we set it to YES, and boom, the warning goes away.

The one thing to note is that there’s no nice way to archive the framework now, since it’s not an installable target. If we want to use it in another app as a pre-built thing, I just build it in Xcode and then grab the build result from the build products folder on the disk, probably with Release configuration if it’s meant to be used by others.

The mysteries remain

A few more things to note.

How is a normal person supposed to find and gather this info? I spent a few hours googling and trying out random stuff, as I wasn’t too familiar with frameworks, before I converged on this info. I think it could be a lot cleaner.

Why did the framework with wrong installation settings work when I was running the app in Xcode, and crash after exporting and trying to run the same app outside Xcode? I guess Xcode tries to be helpful here, when the framework project is part of your app workspace, and somehow masks out all these installation problems. But I think it was harmful in this case, because it really obscures the problem and shows there’s no substitute for testing the exported end product. Some of Xcode’s ways remain mysterious to me.

How does all this interact with code signing, and how will this work with Xcode 6 and iOS 8 where frameworks are now also supported? I don’t know enough about these topics right now to cover them myself, but maybe I’ll do a follow-up once I drill down into that.

Anyway, you can grab my example project here.

xScope 4 Breaks How Loupe and Guides Work Together

Update, November 2014: the problem that I describe has been fixed in Xscope 4.1. So I can finally remove v3 and start using v4.

I do a lot of precise UI layout work. This means that I must be able to measure miscellaneous forms of comps, prototypes, and layouts. xScope has been an indispensable tool in doing this, but the latest version, xScope 4, breaks a particular behavior, that I thought might be useful to document here, so that you could also consider this when buying the upgrade.

I often use Guides and Loupe together. I position the Loupe into a particular position, and then move the Guides in for precise measurements. The magnified guides show up with the rest of the content while I make pixel-precise adjustments to the Guide positions.

For example, in the above workflow, I used the Loupe and Guides together this way, to determine that the spacing between baselines of two lines of text in OS X System Preferences, a.k.a the line height, is 28 pixels, or 14 points, since this was captured on a Retina display. (And xScope 4 lets you toggle between pixels and points in this case, which is great.)

The important point to note above is that in the z-ordering of tools, Loupe is always positioned above Guides, so it shows the magnified guides together with the content, which is absolutely my desired behavior.

The z-ordering of tools has changed in xScope 4. Sometimes, xScope 4 looks like above. And sometimes, it looks like this.

This latter look is useless for me because the guides don’t relate to anything.

See, the z-ordering of tools in xScope 4 is based on which tool is currently active. If you interact with the Loupe, it is above guides, and looks correct. But as soon as you try to start moving the guides, the guides are moved above the Loupe, and it looks incorrect.

This is a deal breaker for me and makes xScope quite useless, so much so that I have to keep using the previous version.

I have contacted xScope support team who essentially confirmed that the above description of the behavior is indeed correct. I don’t yet have an answer about whether this change in xScope 4 is a bug or a feature, or what’s next with this.

I hope that they change xScope 4 to make it possible for me to keep using it like this, with Loupe always above Guides. One way to think about it is that Loupe is a meta-tool that should magnify the output of all the other tools along with the content. I don’t want to use the guides to examine Loupe UI: I want to use them to examine the same magnified content that I am looking at with the Loupe.

I don’t care how exactly they fix it: whether they make this a preference, or default behavior, or I must do some sort of hack (change NSDefaults or whatever) to go back to the previous behavior. I just hope that there will be a way to do this. I will update this post when I get more info about this change.

By the way, if you made it this far, I’m still looking for people for closed beta of my new thing. Check it out and contact me if interested.

Why NSNotificationCenter Sometimes Deadlocks When Trying to Deliver Notifications

I’m working on something that involves some multi-threading with Grand Central Dispatch and multiple queues. Always a fun topic and a can of worms. Today, I hit a bug around this area that made me pause and think for a bit. I’m happy with myself that I was able to reason through the situation, and thought I’d post the writeup.

I’m synchronizing access to a resource using a particular serial queue. Any thread or queue posts stuff it wants to do with the resource on this queue, and it’s nicely serialized.

At one point, though, I hit a deadlock with NSNotificationCenter. Multiple queues were seemingly waiting on the lock, and the synchronization queue was blocked on posting a NSNotification. Wait, what?

When I was less experienced, these kinds of concurrency bugs used to freak me out and I didn’t really know what to do. I’d try random things and hope it worked, and sometimes it did, but I never used to develop a conclusive solution and explanation. But I guess I‘ve been doing this for a while now, and today I was able to resolve this with hypothesis-driven methodic approach. So let’s drill down. In the end, it was actually quite an easy fix. Silly, even.

One of the things that the serial queue does is post a notification about something that changed. The queue doesn’t redirect this posting anywhere else, i.e it ends up being posted on the same queue. But the posting call was blocked.

Well, let’s look at who’s consuming the notification. The only consumer was in a view controller:

someObserver = [[NSNotificationCenter defaultCenter] addObserverForName:notificationName
  queue:[NSOperationQueue mainQueue]
  usingBlock:^(NSNotification *note) {
      [weakSelf doSomethingInUi];

What’s going on in the main queue? The debugger shows that it is waiting to post some code in the same serial queue who’s trying to post the notification. From here on, it was easy.

So, the situation was that the serial queue was trying to post something on the main queue, while the main queue was blocked trying to post in the serial queue, and wouldn’t accept the notification. Classic deadlock.

Breaking the deadlock is easy. The technique of having notifications delivered on the main queue with the above API is usually useful, because it means you don’t have to redirect the execution yourself to another queue, but in this case it is hurting us, because the main queue may be blocked. So we’ll need to do a bit of manual legwork to more directly redirect the execution.

Also, from a requirements perspective, since the effect of the notification is that it shows some feedback to the user in the UI, it doesn’t have to execute instantly, together with the actual change. Well, it has to be fast enough for the user to perceive it as instant, but it definitely doesn’t have to happen during the same runloop.

So, let’s put the pieces together and fix the notification consumer.

someObserver = [[NSNotificationCenter defaultCenter] addObserverForName:notificationName
  usingBlock:^(NSNotification *note) {
      dispatch_async(dispatch_get_main_queue(), ^{
          [weakSelf doSomethingInUi]; 

Using nil for queue means “use whatever queue is sending this notification”, which in my case was the serial synchronization queue. And we then schedule the callback code ourselves to be execute asynchronously.

And voila, the main queue is not blocked any more and this worked fine.