I got some great advice from Trevor Squires and other experts: use a private context that’s just for tags.
Trevor also advised me to think about it this way: in a case like this there should be one authority. One place where tags can be created. (He’s exactly right.) So I created a VSTagAuthority class.
Other advice: I should cache the object IDs (not the managed objects) in an NSMutableDictionary, so I can avoid doing fetches.
So I present VSTagAuthority. It may need some debugging, but it’s pretty close if not all the way there. (Close enough to be illustrative.)
The ideal API looks like this:
- (VSTag *)existingTagWithName:(NSString *)name;
- (VSTag *)tagWithName:(NSString *)name;
(tagWithName creates a tag if needed.)
I came pretty close:
- (VSTag *)existingTagWithName:(NSString *)name context:(NSManagedObjectContext *)context
- (VSTag *)tagWithName:(NSString *)name context:(NSManagedObjectContext *)context error:(NSError **)error;
If I start embedding gists, this post is going to get huge, so I’ll just link to them: here’s VSTagAuthority.h.
The init method takes an NSPersistentStoreCoordinator and creates a private context.
Then it fetches all the existing tags and stores their objectIDs in self.tagIDs. (See the fetchTags method.)
Then it implements the API: existingTagWithName and tagWithName.
In both cases it checks self.tagIDs to get the objectID for the tag with that name. If an objectID is found, then it calls objectWithID to get the tag for that objectID and then returns that tag.
tagWithName goes one step further: if the tag doesn’t exist, then it inserts a new tag, saves the context, then stores the objectID in self.tagIDs. (Then returns that new tag.)
Gist-land. Here’s VSTagAuthority.m.
One of the nice things about this is that the private context here doesn’t need to get changes from other contexts. It knows about the tags, and that’s all it knows about, and that’s totally cool.
Another nice thing is that we don’t have to do any locking around access to self.tagIDs, since we use the context’s performBlock(AndWait) methods, which run in a serial queue. (Correct?)
There are also some parts I don’t like. The API doesn’t match the ideal API. But the big thing is that tagWithName can block the caller as it does a database insert, and the caller might be on the main thread. That’s unlikely to be a big deal, but I have to be aware of it.
A digression about data
At Seattle Xcoders last night we were talking about Core Data, and Luke Adamson made the observation that most apps could just write their data to a plist using NSCoding.
Core Data — and SQLite — is overkill for many apps that use it.
I could probably get away with this in Vesper for most users. Just hold all the tags and notes in memory, and write to disk on a background queue on changes. (Using coalescing, of course, so it’s not constantly writing to disk.)
If I thought I could get away with this for 100% of users, I’d do it. There’s no need to bring in the relatively heavy machinery of a database just for jazz.
After writing this code I went a little squirrelly
I noticed that VSTagAuthority is very, very close to how tags are handled in the shipping version of Vesper, which doesn’t use Core Data.
But, rather than just compare that existing code to this new Core Data code, I decided to write a matching VSTagAuthoritySQL that would do the same thing as VSTagAuthority — except that it would use FMDB/SQLite instead.
I wondered how it would compare.
The API matches the ideal API:
- (VSTag *)existingTagWithName:(NSString *)name;
- (VSTag *)tagWithName:(NSString *)name;
The init method takes a QSDatabaseQueue (the app’s one-and-only serial database queue) instead of an NSPersistentStoreCoordinator.
The init method stores a reference to the database queue. It then fetches all the tags and stores the objects in a dictionary. (The actual tag objects, rather than some form of ID, are stored.)
Then it implements the API: existingTagWithName and tagWithName.
existingTagWithName checks self.tags for that tag. If found, it returns it.
tagWithName does the same thing but takes an extra step: if the tag doesn’t exist, it creates a new tag object, caches it in self.tags, then returns the tag.
tagWithName updates the database by adding a block to the serial database queue that does the insert — see insertTagInDatabase.
There are some nice things about this. One is that the main thread is not blocked with database access in tagWithName (unless fetchTags hasn’t completed). Another is that the API matches the ideal API. Another is that the implementations of existingTagWithName and tagWithName are very vanilla, plain-jane Cocoa.
There are, of course, things I don’t like. It uses locking, while the Core Data version used a serial queue instead. (But, on the other hand, it can be argued that a little OSSpinLock is simpler than having to use blocks.) Except while the initial fetchTags is happening, those locks will be extremely short-duration, since there’s no database access inside the locks.
I don’t call out the SQL bits as something I don’t like, because I like SQL. But I can imagine many people don’t like SQL. Totally understood.
Comparing the two approaches
Both do the same thing in almost exactly the same amount of code. (Though, in real life, some of VSTagAuthoritySQL would be moved into VSTag, as noted in the comments.)
It’s a classic case of trade-offs and values.
The Core Data version wins if you value the things Core Data provides: the data modeler, faults, NSFetchedResultsController, relationships, and so on. It’s an even stronger win if you don’t like SQL. And it wins if you value working with the mainstream Cocoa framework for data — it’s what most everybody else uses, and that’s important.
The SQL version wins if you value its ideal API, its simplicity of implementation, and that it can’t block the main thread with database access (except at startup, while the Core Data version can block on any call to tagWithName). It also wins if you value being able to look at all the source (FMDB and SQLite). And it gets bonus points if you actually like SQL. (Which you probably don’t.)
Paul Goracke talks to me and Chris on the latest episode of The Record — about contributing to CodeWarrior, the early days of Seattle Xcoders, and getting Think C for $25.
Somewhere along the line the words “disciplined Perl” are uttered, to the amazement of the interviewers.
Update 12:15 pm: Paul writes on his blog about being on The Record.
I’m still not sure how to handle this scenario because I’m still learning Core Data and concurrency.
On your day phone you create a new tag called “Birthday Party.” (You’re planning a birthday party.)
At the same time, the sync server tells your day phone about the “Birthday Party” tag you created on your night phone. (You started planning last night.)
So now you have two tag objects called “Birthday Party,” because your user change happens in the main thread context and the sync change happens in a private queue context.
This is not good. Core Data will not consider this a conflict.
My first thought was to have a
tagWithName: method that 1) finds a tag, and 2) creates it if necessary, then 3) returns a tag object.
The find part is done with a fetch request.
I think — correct me if I’m wrong — that this fails when the tag has been created in Context A but not yet saved, and the fetch request is done on Context B. Context B can’t possibly know about that tag yet.
So it seems as if a save is needed on Context A before Context B could see the tag object.
If I keep that same
tagWithName: method and revise it like this:
Lock it via OSSpinLock. (I hate locks as much as you do, but I’ll use them when it’s the lesser evil.)
Make it save the context in the case where it creates a tag.
So the method looks like this:
Do a fetch request for the tag
If not found
Create the tag
Save the context
Return the tag
Is this bullet-proof? Will this work as I expect it to?
If there’s a better way to do this, what is it?
(Yes, I realize there’s an optimization where I can maintain an NSMutableDictionary that maps tag names to managed object IDs. I can avoid the fetch request in that case. I’ll do that only if profiling tells me I need it. It doesn’t change the question, though.)
It’s not just Cocoa. Every window system that ever lived suffers from something like this affliction. Either the system routes everything through the view (or the controller), which leaves a complex view looking like an old-fashioned switchboard operator, or instantiating a simple pane makes you create a whole slew of of handlers and delegates and thumgummies, and that drives programmers bats. Remember OpenDoc?
The main idea behind my using parent/background and child/main-thread contexts is to prevent blocking the main thread as much as possible.
The issue of blocking the main thread is one that causes alarm bells to go off constantly in my head as I work with Core Data.
When I write my own persistence engine, I do things in a different way. Like this:
Model objects are uniqued and live on the main thread. They exist mainly for the benefit of view controllers.
Detached model objects are not uniqued. They’re immutable copies of model objects. They exist for the benefit of API calls, and can be passed around to any thread.
All database access — reading and writing — is done in a background serial queue. Changes happen in the correct order.
This system has some nice advantages — particularly that the main thread is never, ever blocked by the database. It takes an entire performance issue and tosses it away.
But it has plenty of disadvantages, too. Many of the nice things about Core Data — the data modeler, NSFetchedResultsController, easy handling of relationships, and faulting — don’t exist. (Though I could write some of them, if I wanted to spend the effort.)
So it’s not that, in order to work with Core Data, I have to learn to ignore those alarm bells about main thread database access — instead, I have to pay more attention to them so I can hear which ones are louder.
Here’s what I insufficiently considered previously: how much data will the main-thread context be saving?
If it’s saving a bunch of data, then it absolutely makes sense to have the main-thread context a child of a parent/background context. This pushes saves off the main thread.
But it won’t be saving much data. The main thread context gets only user-generated changes — you add a tag, change the text, etc. As long as it’s not saving on every keystroke, as long as there’s coalescing, then it should not be an issue to do saves on the main thread.
Exactly. If the main context only deals with saves from the UI it should be fine without a parent.
So now my thinking that the best thing for my app is to with a setup like this:
Main thread context for UI. Independent.
Background, private queue context for syncing. Independent.
They share a store coordinator, but there’s no parent/child relationship.
In addition, I’m going to try creating the background context as needed, a new one for each sync session. I don’t need to keep a long-running background context around, particularly given that syncing is random enough that caching doesn’t help that much. (I could be wrong about this; I’ll find out.)
If it creates pervasive performance hotspots then you can review. It's not hard to swap strategies.
In Vesper Sync Diary #2 I wrote:
I set up two
NSManagedObjectContexts: one on the main thread and one with private queue concurrency. The main thread context is the child of the private queue context.
This way, doing a save on the main thread doesn’t hit the database: changes get saved to the private queue context, which then does a save to the database.
This agrees with Matt Drance’s advice yesterday:
For ongoing sync, you should consider a private queue parent context w/ main queue as child.
There’s just one thing that worries me about this, and it’s Florian Kugler’s Performance Shootout, where he found that independent contexts spend far less time on the main thread.
But it should be noted that his performance shootout doesn’t include this particular configuration, with parent as the background context and child as the main thread context.
So… I think that my setup should perform fine. It’s the one Matt suggests. But I’m still at the point where I look at each of my decisions critically.
If you — who have more Core Data experience than I do — know of a good reason not to set up my contexts as private/parent and main/child, please let me know on Twitter.
Update 10:15 pm: the solution is at the end. (Spoiler: MartianCraft saves the day!)
* * *
I’m not sure what I’m doing wrong, but you might, since you’ve probably worked with Core Data more than I have. So I’ll write it up.
Vesper’s table view has an NSFetchedResultsController watching for changes to notes. Keep that in the back of your mind.
The app has a main thread context. The data migration system uses a temporary private queue concurrency context. I use it with performBlock:. (The contexts are not parent/child contexts.)
When the data migration is finished, the private context saves. The main thread context then calls mergeChangesFromContextDidSaveNotification (on the main thread, via performSelectorOnMainThread).
This all appears to work splendidly.
Meanwhile, back at the table view…
The table view controller gets the controllerDidChangeContent: message from its NSFetchedResultsController.
Cool — that’s how things should work. The number of fetchedObjects is correct, even. So happy.
So happy until I notice that all the objects have nil properties, with the exception of properties with default values (as defined in the data model).
They are not faults, and accessing any of the values (such as note.text) just returns nil. (If they were faults, accessing note.text and so on would work.)
And the table view appears empty.
I don’t know why.
Some things I tried
If I navigate to another screen and back, everything works correctly. Properties get their values.
If I use the main thread context for data migration, then I don’t have this problem. (But I don’t want to use the main thread context.)
It occurred to me that perhaps I wasn’t doing the merge-changes thing properly.
So I put an NSLog in the method that gets that notification, so I could look at the to-be-merged data.
The method looks like this:
NSLog(@"userInfo: %@", [note userInfo]);
Guess what? It works then. The table view is not empty; objects have expected values.
Take the NSLog away, and it doesn’t work.
So then I wondered — what would happen if I take the NSLog away, but set a breakpoint on the remaining line, and do a
po [note userInfo].
Well, it works in the sense that po does what I expect — but the objects have nil properties rather than expected values. It does not have the same effect NSLog has.
This is Core Data getting back at me.
P.S. On a hunch I tried one more thing instead of the NSLog:
sleep(1). Guess what — it does the trick. Table view is non-empty and the objects have properties with values.
(But of course I can’t leave it that way.)
Solution - updated 10:15 pm
Stupid? Embarassing? Sure thing.
I was listening to the wrong notification. I was listening to
NSManagedObjectContextObjectsDidChangeNotification when I should have been listening to
Total rookie mistake. (Because I am, in fact, a Core Data rookie.)
I’ve seen some feedback that suggests I don’t need to use a custom notification for when the sync server triggers the deletion of a note.
In general, it’s best just to watch for model object changes, via an NSFetchedResultsController, KVO, or other means, and then do the right thing.
While I agree with the general case, there are times when that’s not going to do what I want.
Consider again the case of a note displayed in Vesper’s detail view. There are two cases where it might get deleted:
User taps the trash button.
Sync server tells the app that the note was deleted on another device.
In both cases, the note gets deleted. But what if we want to have different behavior for the second case?
It would be easy to justify the following behavior: if the person is now editing that previously-deleted-elsewhere note, then that person implicitly wants that note not to get deleted.
So that note would get un-deleted. (Technically implemented by making a copy of the note with a new ID. The user wouldn’t be able to distinguish that from un-deletion — it amounts to the same thing.)
If I just watched for the note to be deleted, if I just watched the model object change, I wouldn’t be able to distinguish one event from the other. (And I couldn’t make the copy, since it would be too late at that point.)
So I use a custom notification that notifies the detail view that the sync server just triggered a note deletion, and that note is about to get deleted. That gives the detail view a chance to react and do the right thing.
* * *
Syncing adds a bunch of cases like this. Consider the simple case of what happens when the text changes for a note.
note.text = updatedText;
As with deleting, I have to distinguish between a user-generated change and a sync-server-generated change.
If it’s a user-generated change, then I also have to set note.textModificationDate to the current date. The client then knows it needs to send the change to the sync server.
If it’s a sync-generated change, then I have to set note.textModificationDate to whatever the sync server says it is.
The same pattern holds for a whole bunch of note object properties.
I have not found a great pattern for this. What I do is have methods on the note object like
userDidUpdateText: so I can distinguish one change from another.
Let there be no doubt: this is a pain. (But, to be clear, it’s not a Core-Data-specific pain.)
In the happiest world there is no need for custom notifications or methods like userDidUpdateText — I’d just observe model objects in the normal ways and everything would work.
But syncing is the meany. Syncing is why we can’t have nice code.
Watch for notifications on objects that can be deleted under you. React to those notifications. Plan for them. Presenting an edit view and the object is deleted? React!
The one tricky case in Vesper I have to deal with is, as I wrote about, the case where the sync server tells the app that a note has been deleted — while that note is displayed in the detail view.
But, as Marcus points out, this is fairly easily dealt with. I’ll send a custom notification when that note is about to be deleted. The detail view will watch for that notification and do the right thing (which includes dropping its reference to that note). Then the note will be deleted.
So: not tricky at all.
(The only other issue is the timeline, but NSFetchedResultsController — again, as Marcus points out — deals with that quite nicely.)
This is the not-fun part. Not only has the data model changed, we’ve moved from FMDB/SQLite to Core Data. There’s no way out of doing data migration.
As with syncing, any bugs here are potential data-loss bugs. This code demands the utmost care.
Assume tests. Assume that the code is written to handle unexpected conditions so that it won’t trigger an exception. Assume careful memory use.
The biggest concern is crashing in the middle of the migration. While I believe the migration code is crash-proof, I still have to consider that bad things could happen. A person might run out of battery as the migration is running. Given enough users, even the unlikely things will happen to somebody.
Here’s what I’m doing. I have two rules:
Rule #1: don’t block the main thread
If somebody has a large number of notes on an old device, the migration won’t happen as quickly as expected.
How long before the app would get killed for locking up the main thread? I don’t know the answer — and I deliberately try not to remember how much time I have, not just because it can change, but also because the minute I ask that question is the minute I’m trying to get away with something risky.
It’s entirely likely that I could get away with doing this on the main thread for 99% of users. It would happen so quickly that they’d never even notice. But this is a case where 99% is not enough.
Rule #2: make it repeatable
If there has been a partial migration, make it so the migration runs again at the next launch, and make it so that we don’t end up with duplicate notes.
This is fairly easily done.
On completing the migration — and only on completing — does the app set a BOOL in NSUserDefaults that says the migration is complete.
Future runs check that BOOL. If it’s not there or is false then the app runs the migration.
While doing the migration, each note is checked to see if it already exists in the Core Data store. There’s a
uniqueIDv1 property that we look at. If that ID exists in the Core Data store, then we skip it and go to the next note.
This also allows for the weird case where that BOOL I mentioned gets nuked from NSUserDefaults. (I don’t know how this would happen, but I have to assume weird things are possible.) This would result in the migration being run again — which would be harmless and wouldn’t result in duplicates.
Update 9 pm: Feedback from Twitter — Matt Drance, who knows things — says:
A UD bool is a state bug waiting to happen. Migrate to new-temp.sqlite and rename when finished.
Agreed. Makes sense. There’s a more general point he also makes:
This is basically the systemVersion vs respondsToSelector debate. Do you want to trust or verify?
A question that’s been bugging me for a while is what to do about the tutorial notes that you get when you first launch Vesper.
Launch Vesper on your day phone. The tutorial notes are created with some random IDs.
Launch Vesper on your night phone. Tutorial notes are created there too, with some other random IDs.
Now turn on syncing on both phones. What happens?
You get multiple copies of the tutorial notes on both phones, since, technically, they’re separate notes.
Original sync architecture
Even though 1.0 didn’t have syncing, it was built with syncing in mind. One of the early decisions was that each note would have a UUID that would be unique across the entire system.
So every time the app generated a tutorial note, it would generate a new UUID. Which meant that the same tutorial note on different devices was really two different notes.
Updated sync architecture
Along the way toward actually implementing syncing I made a change to how notes are identified. Each note has a 64-bit integer ID, and it’s unique only for a given account.
Back to the problem of tutorial notes
As I was thinking about this problem again today, I realized that it’s now safe to use predefined IDs for notes.
I could always give the first tutorial note an ID of 1, for instance — because it’s totally fine that the IDs aren’t unique to the system.
(I revised the note ID generator so that it reserves 0 - 1000 for app use.)
But how do we do this retroactively?
What about people who are already using Vesper and already have the tutorial notes — and they already have note IDs?
We have data migration code mandated by the switch from FMDB/SQLite to Core Data. While migrating, that code checks a note to see if it’s a tutorial note, then, if it is, assigns it the correct predefined ID.
(All note IDs are reassigned anyway since we’re going from UUIDs to 64-bit integers.)
There’s just one remaining edge case. The problem comes with detecting that an existing note is a tutorial note. The tag has to be the “Tutorial” tag and the text has to match exactly. If a tutorial note has been edited, it won’t be detected as a tutorial note.
Which means that you could end up with that edited version plus a new version of that note from another device.
This is just going to have to be fine. It’s not ideal, but I can’t think of a way to handle it. (I wish that I had marked tutorial notes as such in the v1 database, but I didn’t. I am now, though.)
The claim — which apppears solid — is that not only did J.D. Salinger continue writing while in New Hampshire, he also directed his literary trust to publish several books after his death. The first could appear as soon as 2015.
Which gives us a year to re-read The Catcher in the Rye, Nine Stories, Franny and Zooey, and Raise High the Roof Beam, Carpenters and Seymour: An Introduction.
My favorite of these is Nine Stories. The story that made the biggest impact on my life, though, is Seymour: an Introduction.
I’ll re-read these in the order listed above, which means Catcher goes first. Of the four, it interests me least, because my memory whispers — subversively, unafraid to break hearts and make enemies — that it’s actually a whiny and annoying book.
I loved it as a teenager, but then teenagers are made to fall in love with whiny and annoying things. We can’t, for one thing, explain The Smiths without introducing biology — we need an understanding of hormones and the development of the prefrontal cortex to make sense of it. But, as with The Smiths, I suspect I’ll like Catcher better than I think I will. The Smiths has Johnny Marr, and Catcher has J.D. Salinger’s sentences.
I can’t help but wonder too how Holden Caulfield would have reacted to the age of the Like button. The way we live now is overdue for some charismatic puncturing.
But, anyway, first things first: I’m in the middle of Mission to Paris by Alan Furst. I’m a big fan of spy novels (like you, I bet) and I thoroughly enjoy Furst’s novels. Good stories, written well. Recommended.
The best advice I’ve heard about deleting managed objects is to 1) not do it, or 2) do it only at startup, before any references to those to-be-deleted objects can be made.
Based on this advice, here’s how I’m handling deleted notes in the next version of Vesper:
Notes have a
userDeleted BOOL property.
When a note is deleted, that property is set to YES.
The various fetch predicates all specify
userDeleted == NO, so that deleted notes are ignored. (This is a little ugly, but I accept it.)
At startup, the app fetches all notes where
userDeleted == YES and deletes them, before any references could be made.
My questions: is this overly-paranoid? Is there a better way of handling deleted objects?
Update 11:45 am:
Some people on Twitter have suggested listening to contextDidSave.
Gorby, however, warns, “There's no way to ensure safety if you only listen to contextDidSave due to a race.” I’m inclined to listen to him.
RiFi (I just made that up, though he’s probably heard it before) says: that “we broadcast notification about deleting objects when we do it. Anyone with a ref is responsible for dropping the ref.”
Paul Burford asks if I also wear a belt and braces. I don’t — but only because I’d rather my pants fall down in public than to ship a persistence bug.
The only place I have to worry about this in the detail view. In the timeline view I can rely (I would think) on NSFetchedResultsController to do the right thing. (I’ll find out if I’m wrong.)
But in the detail view it’s possible you could be editing a note while a sync is happening, and the sync server might say that that note was previously deleted. So the detail view needs to do the right thing anyway.
So the better solution, better than what I’d implemented, is as Monsieur Fillion describes: send a notification when a note is about to be deleted. Have the detail view get that notification and do the right thing.
Here’s a case in the in-progress version of Vesper:
Tag objects are always created on the main thread, because tags are usually created via the UI. (There’s a dictionary that stores tagname/tag pairs.)
Dealing with data returned by the sync server always happens in a separate NSManagedObjectContext (private queue concurrency).
During merge I sometimes need to create new tags. I can call the main thread, create some tags, and return managed object IDs.
Those IDs will be temporary IDs sometimes. But the main thread might save the NSManagedObjectContext before the private queue gets the chance to retrieve those tag objects by ID. Which means — I think — that it will fail.
Do I understand correctly?
In other words, is it unsafe to use temporary managed object IDs across threads?
* * *
Update 11:40 am: It is unsafe, I’ve just learned, via email from Paul Goracke (local Core Data expert).
So I have some options. I’ll figure those out and update this post.
* * *
Update 12:10 pm: Options. Essentially as Paul laid them out, but rewritten. (Any weirdness is therefore mine, not Paul’s.)
Option #1: Create tags using current NSManagedObjectContext
The problem with this approach is that a user could create a tag named “ice cream” at the same moment the syncing code creates a tag named “ice cream.” I could end up with two tags with the same name.
This is not as unlikely as it sounds. Tag names aren’t random. If you’re planning a birthday party, then you might well create duplicate “Birthday Party” tags.
A Core Data merge wouldn’t call this a conflict. Would I need to create some custom validation or a custom merge policy, or both?
I don’t know how to handle this, though I’m sure there’s a way.
Option #2: Continue creating tags on main thread
Continue with my current system, but use
-obtainPermanentIDsForObjects:error: and return permanent object IDs.
I like this because permanent object IDs are safe — I can use those across contexts. (At least, I think this means that the other thread could retrieve the tag, though maybe not — maybe it requires an actual context save.)
What I don’t like is that there might be an error here, and I have no idea how I’d recover from that error.
That error puts the app in the state where the tag object exists, but we can’t tell the other thread about it, so the other thread has to... do something. I don’t know what. Create a duplicate tag? No.
Option #3: Don’t use Core Data
Paul didn’t mention this option, but I can’t help but think of it. It’s a trivial matter to unique tag objects and make them thread-safe, and to have a shared NSMutableDictionary (locked with OSSpinLock) that allows fast look-up from any thread.
There would be no duplicate tags; there would be no need to deal with validation or merging. Calling my
tagWithName: method would always succeed. (It returns an existing tag or creates a new one.)
I keep resisting this option, though, because I really am trying to use Core Data with this app.
Option #1 sounds like the right way to go, since it means that the sync code isn’t coupled to multiple contexts. But I am worried that dealing with merging is a rabbit hole. My worry may be misplaced, however — it might be pretty straightforward.
If you’ve been, like me, a Cocoa developer for a bunch of years, you may have missed one of the coolest things about cloud services: most systems let you bump up the number of instances of your server so you can handle more traffic.
Dave Winer does a video demo showing this with Heroku. So easy.
Related and required reading: The Twelve-Factor App.
Doug Russell’s old podcast included a fake This American Life about food poisoning.
Human Salad is my third appearance on Unprofessional. Which makes me thoroughly unprofessional, I suppose.
Seems like there was a whole lot about pants. (But it was recorded so awfully long ago — last week — that I don’t remember that well.)
I don’t think I’ve ever used random numbers in all my years of shipping code. I’ve always figured that if I’m deliberately introducing randomness, then I’m doing something wrong.
So it never would have occurred to me — but in this case it’s perfect.
It also occurred to me that I can check for collisions on generating an ID just by maintaining a set of the existing IDs. (Recall that IDs need to be unique per-account, not system-wide.)
This means a collision could happen only in the set of un-synced notes for a given account, which will tend to be very, very small to zero.
In More about Unique IDs I decided to use a client-generated UUID for notes.
The thing about 64-bit values is that they’re much easier to handle than 128-bit values. For example, the Core Data modeler has Integer 16, Integer 32, and Integer 64, but no Integer 128.
So the idea is this: when I need a 64-bit ID, I create a new UUID and then pass it to CityHash64().
There is no practical reason to worry about collisions — especially given that an ID has to be unique per-account, not system-wide.
Nevertheless, a test to put my mind at ease would be a good idea. I’ll write a little app that generates a few million of these from UUIDs and make sure I don’t see collisions.
Omnizen and fellow Ballard-ite Kyle Sluder has some thoughts about the behavior of super:
It’s sometimes useful to skip the superclass’s implementation from within an override, calling the grandparent class’s implementation directly. (Bug workarounds are the primary use case that comes to mind.) This can’t be done with the
superkeyword, but the underlying
objc_msgSendSuperruntime call can express this without any difficulty.
I propose that the super keyword be extended to take an optional class argument.
This talk was pretty much a complete last minute change from what I’d prepared and presented internally to Black Pixel coworkers two days earlier because I wasn’t happy with the boring textbook feel of that talk. If you don’t feel it was a polished set of slides, you’re absolutely correct.
Some new thoughts:
Why I can’t use creation date as client ID
It doesn’t matter that the date might be inaccurate — the important thing is that it would be unique for a given person.
But I forgot to consider automation (scripting, importing) which could generate non-unique creation dates.
So using creation date is out.
Looking again at client-created IDs
I’ll recap this idea.
The client generates an ID. That ID needs to be unique for a given account. I’d use a UUID, which is an 128-bit number. (That’s double the ideal size of an ID, but I’m not too concerned about that.)
(A UUID is necessary because there’s no way to coordinate an incrementing 64-bit number.)
This solves the case where a user signs out of their account and signs in with a different account (which may or may not be new). Those client-generated IDs would survive this process — which is what I want. Though notes under a different account are different notes, and would get stored as such on the server, those same client IDs would be fine since the server enforces uniqueness on account ID + client ID.
What worried me last night, though, was the bad client scenario. An attacker.
A digression about security
I design as if anything can happen. An example: what if the server-side code got leaked? Would the system be hackable? (No.)
What if the client-side code also got leaked? (Still not hackable.)
But let me be more specific: if an attacker knows the API and has a user’s credentials, that attacker could conceivably access and change that user’s data.
I don’t know of a way around this.
But this is no different from any other service. For example, if you learn my Twitter credentials, you can tweet as me. (You don’t even have to reverse-engineer the Twitter API first.)
Bad clients and client IDs
The scenario that worried me last night was the attacker who figured out our API and had a user’s credentials.
That attacker could supply bogus client IDs for notes.
The damage would be limited to just that user, since uniqueness is account ID + client ID. (That attacker wouldn’t get access to data from another user, in other words.)
Also: if the attacker supplied bogus client IDs that were actually unique for that account, it wouldn’t matter. The system would work properly.
Furthermore, I could add a check: two ostensibly-the-same notes identified by client ID + account ID must have the same creation date, since creation date can’t change. Otherwise it’s an error.
But here’s why this scenario isn’t worth worrying about: what are the chances that an attacker just wants to screw around a little by supplying bogus IDs?
Most likely that attacker would do much worse things. (Consider that someone hacking my Twitter account wouldn’t want to do some slight and hard-to-detect mischief — they’d do something more dramatic.)
The only scenario that I can think of where an attacker supplies bogus IDs is where they’re trying to get information from other accounts. But we’ve made that impossible by saying that IDs are unique only to a given account. (Lookups on the server are always done with an ID/accountID pair.)
Going with client-generated IDs
That’s the upshot. I’m going with client-generated IDs.
Update 1 pm: Regarding coordinating incrementing a 64-bit integer, Kyle Sluder reminded me of vector clocks. Good point, but not worth the trouble here.
In an RSS reader there are a couple ways of setting unique IDs.
If the sync server also fetches the feeds — if it’s a content service — then the sync server can arbitrarily assign unique IDs. (An auto-incrementing 64-bit integer would suffice.)
(Yes, it has to be 64 bits, not 32. I remember when NewsGator’s content server broke the 32-bit max barrier. Some of the client apps weren’t expecting this and had to be revised quickly. Luckily NetNewsWire was prepared for this, but it was more-or-less by chance.)
A second way is to have the sync server and all client apps agree on an algorithm for generating unique IDs. It might be feedURL plus guid (if present, permalink if not, etc.)
But Vesper is a note-taking app, where the content is generated on the device. And a note doesn’t have any relatively stable properties you can use to create an ID (while an RSS item’s guid or permalink usually doesn’t change).
How should it generate unique IDs?
Generating IDs on the client
If a client app generates a unique ID, it can’t use an auto-incrementing 64-bit integer, since there’s no good way to coordinate these between clients and the server. You’d get collisions for sure.
A client app could instead generate a UUID when creating a note and send that to the server with the note.
There are some drawbacks to that:
A UUID is a 128-bit number. It sures seems like 64 bits ought to be enough for any unique ID, but this is double the size. The database will be larger than it needs to be.
A bad client could send a bad UUID. It could do this on purpose, in hopes to disrupt the integrity of the data or to gain access to data it shouldn’t get.
UUIDs could collide, and the result could be someone getting someone else’s data.
I’ll dispense with these. Mostly.
The size issue (#1) isn’t that bad. A 64-bit unique ID would be best, but using 128 bits instead shouldn’t have a significant impact on performance or costs.
The issue of bad clients (#2) and collisions (#3) can be mitigated by making it so that a unique ID is considered unique only for a given user. (Not that there ever was a practical chance of UUID collisions. The chance of a bad client is higher.) (In the database I’d make the primary key a compound key of uniqueID and userID.)
A bad client would thus be able to trash an individual user’s data but wouldn’t be able to affect the rest of the data.
(I have to design as if terrible things could happen — because by doing so I can better prevent terrible things from happening.)
Generating IDs on the server
You’re nodding your head. “Brent! Yes! This is the way to go! Don’t trust the client!”
I hear you.
The server could generate an auto-incrementing 64-but unique ID. And I wouldn’t have to do compound primary keys, since I could trust the unique ID.
Were I doing a web app I’d certainly make it work this way.
But since it’s a mobile app, everything is asynchronous. Sending a note to the server and getting back a unique ID happens some time after it’s created. (Could be a fraction of a second; could be hours, days, or weeks.)
To make matters worse: a note might get edited as it’s in flight to the server.
It seems like the way to make this work is to give a note a temporary client-specific unique ID. When sending the note to the server to get its canonical unique ID, the client would expect the server to return that temporary ID along with canonical ID. (That way I could match up the local note with its canonical ID.)
That’s do-able. That’s just housekeeping code. Not grave.
Here’s the scenario that stops me
Say you sign out. All the canonical unique IDs stored locally are deleted — because, after all, they’re no longer valid, since you might sign back in with a different account.
Then you sign back in. Maybe it’s the same account and maybe it’s a different one. How does the client know which notes are already stored in the server for that account and which ones aren’t?
Consider this: the client then sends a note to the server that the server already has, but the client didn’t send the server’s unique ID, because it deleted that information.
Should the server treat it as a new note? Or should it look to see if there’s an exact duplicate? (That’s a crazy amount of work the server would have to do every time it received a note without a server ID.)
It might not find an exact duplicate because the note has been edited. So now it’s two separate notes when it should be just one.
Storing different unique IDs per account
One solution — that I don’t like — is that the client could store multiple unique IDs per note, a different unique ID for each account.
This way it wouldn’t have to delete unique IDs on signing out. When talking to the server it would reference the correct ID based on the signed-in account.
When I imagine implementing this I just don’t like it. (I’m going to stuff a dictionary in a uniqueID property? Ugh.)
The client could generate a unique ID and the server could generate a unique ID.
Almost all of the time the server’s unique ID would be thing actually used. The client ID would be a backup ID to be used in the case when a note may exist on the server but the client doesn’t know that. (Because you’ve signed out and then signed in.)
On receiving a note without a server ID, the server would check see if a note with that client ID exists for that account. If so, then it would return the server ID for that note. If there is no note with that client ID for that account, then it’s a new note and it generates a new server ID.
That client ID would have to be a UUID, while the server ID would be a 64-bit integer.
I don’t love this approach, because it seems a little weird to have to give everything two IDs. I’ve now talked myself into 192 bits for each row on the server to store unique IDs.
The code is less weird than the unique-ID-as-dictionary approach. So I think that’s how I’ll do it.
But if you have a better idea, you can find me on Twitter.
P.S. It occurs to me later that a client ID could just be its creationDate timestamp. With millisecond precision. Since the client ID only has to be unique for a given person (across one person’s accounts) that might be okay. And since I’m already storing creationDate, that means I wouldn’t have to generate and store a UUID. Would any single person run into timestamp collisions? It’s hard to imagine.
My previous post led to some Twitter traffic, and it led Dad (he goes by Dad) to do some investigating.
The documentation for setPropertiesToFetch says one thing and the .h file says another. Contrary to the documentation — but per the .h file — you can use this with managed objects. From the .h file: “the results are managed object faults partially pre-populated with the named properties.”
This almost what I want.
What I’d like even more is to get objects back as faults, but have only those specified properties fetched when the objects are un-faulted.
Other properties would get fetched only when one of them is accessed. Which wouldn’t happen in the case of Vesper’s timeline, but would happen when a note’s detail view is displayed.
We had 50 people at Xcoders last night to see Paul Goracke talk about Core Data. He did a great job, and people were very eager to learn about this particular topic. By show of hands it appeared the room was full of Core Data users. (Enthusiasts, even.)
(I think it was recorded and will appear on the web eventually. Here are Paul’s slides.)
* * *
I left the meeting feeling a little spooked. Not Paul’s fault — it’s more that I’m eternally on the fence about Core Data.
It’s not just that there are certain things that are less efficient in Core Data (one specific: mark-all-as-read in an RSS reader, which means flipping a boolean in a large number of rows, which ought to be just one SQL call), it’s more that there’s a large-ish layer of code and APIs between my code and the database.
Core Data provides a bunch of seriously nice conveniences. I especially like how relationships are handled, which is a pain to do by hand. NSFetchedResultsController is very handy. Etc. So much good stuff.
And yet there’s this layer of glass between me and my data.
And maybe I’m just being a punk for being bothered by it.
* * *
Here’s a thing I’m looking at right now.
Vesper’s notes UITableView — we actually call it a timeline, since notes are organized by sort date — does not need to be populated by VSNote objects.
A VSNote has more than a dozen properties. But the timeline needs only a few of those, and there would be performance and memory-use gains by using a smaller object. I could conceivably create a VSTimelineNote object with just those few properties. (Truncated text, sort date, unique ID, attachment thumbnail.)
Using FMDB I’d query the notes table with something like this:
select id, truncatedText, sortDate, attachments from notes where something. VSTimelineNote and VSNote would come from the same database table.
To get a similar effect in Core Data I could create two entities: VSTimelineNote and VSExtendedNote. VSTimelineNote would have a to-one relationship to VSExtendedNote.
That’s fine. That would work.
But it’s not great.
It means the data isn’t stored using the most natural representation. The data model would be based on the needs of the UI, which is not a good idea. (But note that I mentioned a truncated text property, which is certainly based on the needs of the UI, so it’s not like I’m against that all the time.)
It also means that some accessors would be weird — to get the creation date, for instance, I’d have to type something like
* * *
There are other possible Core Data solutions to the problem. For instance, I could do a request that returns an array of dictionaries, use
setPropertiesToFetch:, and create VSTimelineNote objects (non-NSManagedObject-objects) from that array.
I don’t like that because it essentially means I’m using Core Data as a database rather than as object persistence layer. If I’m doing that, why not just skip the Core Data layer?
* * *
My listing these options has a point. It’s not to show that Core Data is unflexible or lacks power — it is flexible and powerful.
And it’s not to prove that abstractions are leaky. (Though one-entity/one-table is an obvious example.)
Rather, it’s to show what must be considered a flaw in my developer’s psychology: that I know there’s a SQLite database under the hood, and when I think about the Core Data options I can’t help but think how it’s such an easy and straightforward problem to solve were my code closer to the database.
I do not recommend that other people adopt this flaw.
Update 4:15 pm: A few people have suggested Core Data’s parent/child entities as a solution to this problem. That might be the way to go. But my point is not that there isn’t a solution, it’s that all of the solutions are a layer on top of what is an extremely simple problem in SQL. (Which is not fair, because Core Data does so much more than just create SQL queries. Nevertheless, it’s hard for me to look past that. That’s my flaw.)
David Smith investigates the claim (which I’ve made) that people who don’t upgrade their OS are, in general, the kind of people who don’t buy apps.
It seems clear that the distribution of people who are purchasing your apps follows closely the overall adoption of users. There doesn’t seem to be anything about their speed of update that impacts their purchasing habits.
objc.io issue #9 is a great reminder of how incredibly awesome NSString is.
I’m not saying we never have to think about Unicode, but it’s pretty darn close.
Or consider Go strings (Go is an interesting language worth learning about):
Strings are built from bytes so indexing them yields bytes, not characters. A string might not even hold characters. In fact, the definition of “character” is ambiguous and it would be a mistake to try to resolve the ambiguity by defining that strings are made of characters.
I’m sure there are good reasons for Go’s string design, but I strongly prefer NSString’s collection of characters rather than bytes.
Update 3:04 pm: Or maybe I’m wrong about all this! NSString also returns 2 for [@"💩" length].
Well, I stand by NSString being great. But, darn, I thought it had this stuff a little bit more nailed than it apparently does.
Tonight at Xcoders Paul Goracke presents a Core Data potpourri, “a mix of practices, some advanced techniques, and a few performance tips.”
I’m going to learn things.
Afterwards we’ll go to Cyclops as normal.
I rediscovered yesterday that home.mcom.com has been preserved. I love that so much, and I wish more of the early web had been preserved.
There’s a story behind it, of course. From 2008: Happy Run Some Old Web Browsers Day!
Apple has been making strides in Objective-C development. They deprecated GCC in favour of LLVM. They added blocks. They added collections literals. Yeah, and that’s all great, but those are all incremental improvements. At some point, there’s going to be a gulf that can only be bridged by a radical change in the language.
That’s what it should take to inspire a radical change in developer tools - improvements on an order of magnitude in building software, making it easier to solve hard problems, and fixing issues in common coding standards that have arisen through heavy use. This goes beyond just a programming language; it will require new frameworks and design patterns to really bring about the benefit.
I’ve been in the camp that says Objective-C’s incremental improvements will get it there, that we don’t need a revolution. We can have great performance and a great language and frameworks at the same time.
But the one thing that’s made me consider changing my mind is how fast devices are getting. Performance changes the calculations.
In other words: the next language and its frameworks could be slower than Objective-C and Cocoa, but because devices are so much faster, that would be fine — as it once was fine that Objective-C was slower than C, because it allowed us to create better apps more easily.
Everything in Vesper has been animated beautifully.
When I read a review like this, I’m glad — and my mind goes straight to all the hours spent making those things the reviewer called out. Those animations represent a real chunk of my life. A chunk of my life spent learning, iterating, and cursing.
So. Much. Cursing.
I’ve never made anything where it was a breeze. Maybe some developers are like that, but not me. My one gift isn’t intelligence or talent — it’s just that I keep my eye on the end zone and keep going, one strenuous yard at a time.
Whatever glamour is, this is its opposite.
I’m the guest on the very first Thank You for Calling!. I talk about that crazy Network Solutions thing and about doing support as an indie developer.
Here’s the scoop: feline Casanova Moisés Chiullan just launched the Electric Shadow Network. I subscribed to the master feed — I want to listen to all of them.
My storyboard has five view controllers that each have a static UITableView and at least one other button.
I really like how easy it is to create static tables and cells using storyboards. Or, I did like it until I learned this: those table views won’t appear unless I also create a UITableViewController for them in IB.
I think this means that I have to embed a container view controller five times and create five separate UITableViewControllers and hook those up. This means my storyboard will have 12 boxes instead of just seven.
Correct? Or is there another way to do this?
The point of the storyboard is, I think, to have a single place to define a bunch of screens that go together, and to get closer to what-you-see-is-what-you-get.
As nice as it is to be able to define static table cells, having to break those off into separate contained UITableViewControllers detracts from the effect a bit.
At this point I realize that all of this would have been easier — for me — in code. But that really just doesn’t seem right.
This issue came up in private correspondence. Should you always use
IBAction for actions? Is there some advantage to using
void instead? Or the other way around?
Here’s my thinking:
IBAction for the sole case where the action is directly wired up in Interface Builder. It’s a reminder that there’s a xib or storyboard and this action is called directly with this target.
Otherwise I use
In both cases I put the actions in a
#pragma mark - Actions section of the code. And I always use the form
- (void)someAction:(id)sender. (That is, I always include
sender, which is a further clue that it’s an action method.)
Also, I almost never wire up actions to an explicit target in Interface Builder. Instead I use the responder chain, even on iOS apps — meaning that I wire actions to First Responder. (If you’re an iOS developer and you’re not up on the responder chain, you’re missing out. It’s important.)
This has the effect of making
IBAction pretty rare in my code. It doesn’t appear at all in the current shipping version of Vesper.
Update 4 p.m.
Some unstated things, and a good reason to use IBAction:
I don’t use IB that much (though I’m trying to fix that, when possible). But when I do, I add a method to First Responder, then wire up the action to First Responder.
However, if I already had an action method with the
IBAction macro, then I could wire it up to First Responder without having to add the method in IB. Totally true. (And that’s a good reason to disagree with me.)
Here’s why I do it this way: I usually do IB first, code second. There may not be an action method or even a view controller when I’m in IB. Since I usually do things this way (when I do use IB), it makes sense to me to always do it the same way.
Another reason is that because I don’t use IB that often, most of the time when I’m setting up actions I’m doing so programatically, with a nil target and a selector. There’s absolutely no need for
IBAction when doing it this way, and I’m far out of the habit of using it.
In the end, my main reason is still the important one: I like to use
IBAction as an indicator that an action is explicitly wired to this specific target. It doesn’t have to mean that, but I like it to mean that.
But you may agree with Wil Shipley:
Seems like a lot of work to avoid IBAction.
There are people who thought that Azure had to be called Windows Azure when it launched to bypass people’s bullshit filter, when what it did overwhelmingly was up the bar for acceptance and put people off their lunch. One of them, Ballmer, took 14 years and a business landscape slowly crumbling to figure out that he was standing in the way of progress and he still doesn’t know exactly how. Want to guess how many of the more than 100,000 employees have a similar outlook?
Justin Williams wrote about a hanging bug in Glassboard that could happen when pulling links from a string using a regular expression.
We’re using a newer regular expression in Vesper that fixes this bug.
Our tests include Justin’s tests. It passes.
I heard from an internet pal who told me they had numbers for their app like this:
5% on 10.6
5% on 10.7
10% on 10.8
80% on 10.9
The question is this: is this an exception? Should they continue to support at least 10.8?
(There may be an exception for niche apps — education and enterprise apps, for instance. My “No” applies to the majority of consumer apps.)
Here’s the thing about those numbers: they’re a snapshot. The percentage of people on older OSes get smaller every day, as people do eventually upgrade their OS or buy a new computer.
In three months there might be just 8% on 10.8 — but you’ve committed to coding and support for that dwindling share.
And the big thing looms: 10.10. It will come out later this year. At that point you’re supporting three OS releases, which is unreasonable.
When making decisions like this, I don’t think about what conditions are were I to ship today — I think about what conditions will be like when I actually do ship, and I think about conditions six months out as we do support, testing, and maintenance releases.
Given that, anyone working on a Mac app that they don’t plan to ship until Summer might even consider dropping 10.9 — go straight to 10.10. (I know we don’t know anything about it, but you can still plan for this.)
On the Black Pixel blog, Rick Fillion notes that they’re dropping OS X 10.7 support from NetNewsWire.
I don’t disagree with it, but it’s not quite the right call.
The right call would have been to drop 10.8 support also. Here’s why:
There’s almost no barrier to OS updates these days. They’re free and easy to install.
People who don’t upgrade their OS are also the kind of people who don’t buy apps.
An app succeeds based on quality, not breadth of OS support. You can make a better app by using newer APIs. You can make a better app by not having to spend coding and testing resources supporting older versions of the OS.
Yes, you will leave some small number of people behind. It’s worth the trade-off, though, because it’s your job to make the very best app you can make.
(These remarks apply to iOS also. No sense in supporting iOS 6. Once iOS 8 comes out, you should drop support for iOS 7.)
In one of my projects I have a great case for storyboards: a collection of screens that appear in a navigation controller. Each screen has some buttons plus a UITableView with static cells.
I don’t tend to use Interface Builder very much, and this is my first go with Storyboards.
What I Like So Far
On my big display I can see six screens all at once. (There are seven in this storyboard.)
Creating static cells is easy.
Storyboards understand view controllers and have a bunch of configuration options — other xib files don’t. (File’s Owner could be anything.)
What I Don’t Understand Yet
I’m still learning. Some things are not obvious yet.
Here’s my case: UIView with embedded UITableView and with other buttons.
How will the UITableView resize? Here are some of the options under View Controller in the inspector:
Adjust Scroll View Insets
Resize View From Nib
Extend Edges Under Top Bars
Extend Edges Under Bottom Bars
Extend Edges Under Opaque Bars
I have no idea what combination of these will work. And I don’t know how any of these interact with Autolayout.
For instance, “Adjust Scroll View Insets” corresponds to the
automaticallyAdjustsScrollViewInsets property, “which allows the view controller to adjust its scroll view insets in response to the screen areas consumed by the status bar, navigation bar, and toolbar or tab bar.”
But what if the scroll view is a subview of the top-level view? Will this do what it sounds like, or not?
There’s an answer, but I don’t know it. (I’m not asking what it is — I'll find out.)
What Frustrates Me
I can’t make the screens match the design.
There are a half-dozen or so things I want do — create custom header and footer views for table sections, set the color of the navigation bar title, use custom fonts, etc. — that I can’t figure out or are not possible.
This isn’t really a new thing with Interface Builder — we’ve been dealing with this all along — but it’s weirdly more frustrating the closer we get. I want to give this to my designers and let them make it perfect without even having to run the app, but I can’t. Yet.
What Worries Me
My designers will dig into this, and then we’ll have to merge.
What Surprises Me
One of my screens is a blank view controller — it’s a container for two other view controllers. (One shows or the other shows.) We have plenty of code support for this kind of thing, but it appears there’s nothing in Interface Builder.
Update a few minutes later: I was wrong — there totally is a thing in Interface Builder for this. You can drop in a container view and set an Embed relationship to other view controllers. This is cool. I like this. (This info comes from Doug Russell.)
What I Think of Autolayout
The improvements to Autolayout in Interface Builder in Xcode 5 are good and quite welcome — but I’m still going to go with Autolayout in code instead.
I tried it in IB, but I found it went like this: select the thing I’m working on. Click on a constraint all the way on the left. Click in the inspector all the way on the right. Click on the layout toolbar. Repeat in random combinations. And then struggle to resolve ambiguities.
It’s a bunch of moving around. Related things aren’t near each other. It doesn’t compare to reading code (even Autolayout code, which can be verbose but is linear and contained).
(I’ll have my designers write Autolayout in ASCII in DB5.)
What I’m Going to Do
I’m going to keep using Storyboards when I can, when it’s appropriate. The things I like I really, really like. The things I don’t understand I will come to understand.
It’s clear that Storyboards are the future, and only an unwise programmer refuses to keep up.
And the things that frustrate or worry me — well, I expect those to improve over time. (I’ll file radars.)
In the meantime, I can’t help but think how awesome it would be if my designers really could build the UI in Interface Builder.
It’s been 10 years this month since “upgrade from tcsh to bash” was placed on my to-do list. It’s starting to look hopeless.
I have some experience with Azure — with Mobile Services, specifically. Yes, they paid me to create some videos, and they’ve sponsored two of my podcasts. Which I agreed to only because I like what they’re doing.
There’s still a lot of the old Microsoft there, the Windows, Office, Exchange, and Sharepoint (WOES) company. It’s most of the company by far, surely. (I just made up the acronym WOES. It fits.)
But in the Azure group, at least, there’s recognition that Microsoft can’t survive on lock-in, that those days are in the past.
Even if you don’t choose to use Microsoft’s cloud services, I hope you can agree on two things: that competition is good, and that Azure’s support-everything policy is the best direction for the future of the company.
My worry about the future is an Amazon monopoly on cloud services. Amazon’s services are fantastic, and they’ve changed how people make web apps. But they should not be the only game in town.
I’m sorry for being a snarky jerk the other day on Twitter.
“Xcode cannot handle our scale” is the new day and night phones.
The “day and night phones” part refers to Dave Morin’s infamous interview. If you’ve never read it, you should. It’s funny.
The “Xcode cannot handle our scale” part refers to the recent Q&A on Quora about the new Facebook app Paper.
My comment was undeserved, and it was beneath me, and I apologize to the Paper team without reservation.
* * *
Though I haven’t used Paper myself (I don’t have a Facebook account), I’ve heard from people whose taste I respect that it’s a remarkable achievement and an excellent app. So not only do I apologize, I also congratulate the team.
* * *
Nevertheless, I’m still interested in the details here. Why would an iOS app with a complexity presumably much less than GarageBand (for instance) be of a scale hard-to-handle in Xcode?
There are apps that Xcode can’t handle, or can’t handle well, I’d bet, though I’ve not seen this with my own eyes. It’s easy to imagine that Photoshop, Microsoft Office, and Chromium are very difficult to handle in Xcode. I don’t know this for a fact, but I’ll stipulate it.
Facebook iOS developer Grant Paul tweeted:
@brentsimmons @alanzeino @ashfurrow For example, Paper has ~100 projects in the workspace. Takes 45+ seconds to open on a high-end rMBP.
That sure sounds like Xcode can’t handle Paper’s scale: 45 seconds is ridiculous. I imagine that other operations are similarly slow.
I would have started breaking Paper into separate workspaces when it hit about six projects. Or fewer. I’ll call that a matter of personal style, though, and not claim it as a best practice.
But the question in mind is this: why are there about 100 projects in the workspace?
* * *
I don’t know why. I have some theories, which may be true in some combination. Or may not be at all true.
Perhaps they have every line of iOS code they’ve ever written all in those projects. The Quora page suggests as much: Paper “lives in the codebase of the other mobile apps at Facebook that has hundreds of regular contributors.”
Another possibility: they’re re-implementing UIKit, or parts of it. From the Quora page:
The engineering complexity here is finding a way to fully utilize the multicore architecture of newer iPhones on top of the UIKit framework which has no support for multithreading. Significant work went into creating a framework for doing rendering work on multiple threads, and we spent a long time finding the balance between performance and complexity. Turning synchronous operations into asynchronous ones adds a lot of cognitive overhead, and this was one of the biggest challenges of the project.
I don’t know why this is so complex or challenging. Between operation queues and blocks, doing rendering work on multiple threads is just not that hard these days. I’m far from the only developer who’s been doing this long enough to be extremely comfortable with it.
But I don’t know the details, and that makes all the difference.
It’s also possible that they’re using a ton of external libraries.
@alanzeino @dannolan php, chromium, webkit, zlib, libjpg, JSONKit, yaml, yasm; all licenses cited in the Paper license file. Why on earth.
If it’s true that the workspace includes the code from a bunch of external libraries — including some huge ones — then I don’t know what to think. Why not, for instance, use the system-supplied JSON parser?
Well, the answer might be that there are bugs in Apple’s code. (There certainly are.) Or it could be that they need more than UIWebView provides, so they have a private build of WebKit. I don’t know. I’m speculating.
* * *
Here’s the thing: even if there’s a good reason for every one of those 100 projects, there’s no good reason to need 100 projects to build almost any iOS app. (Maybe GarageBand.)
“Xcode can’t handle our scale” should almost always be considered a signal from Xcode that the workspace needs major surgery, because at that point you have a workspace that an individual developer can’t handle either. (Particularly for iOS apps. MacBU and Adobe can ignore me on this point.)
But I don’t know the details — I’m totally guessing when it comes to the specific case of Facebook Paper. And so I can’t definitely say that this workspace is a sign of a problem with the code or the process.
Still, though. 100 projects. I’m massively curious. How did it happen, and what will they do next to deal with it?
The first play from scrimmage in the championship game was embarrassing. The ball got away from the quarterback, and it quickly turned into a few points for the other team.
Please don’t let this set the tone of the game, I thought. Please.
And it didn’t. Despite Russell Wilson’s first-play fumble and the subsequent field goal, the Seahawks beat the 49ers 23-17 to become NFC champions.
All throughout the post-season I had been hoping somebody would beat the 49ers so we didn’t have to face them. A Seahawks-49ers matchup would mean the two best teams in football had to play each other, and on any given day the better team (Seahawks) might not actually win.
The NFC championship was the real championship game this year, and we knew it at the time.
* * *
I watched almost every Seahawks game but hadn’t seen the Broncos until their AFC championship game against the Patriots. That game surprised me: it didn’t look at all like the football I was used to.
It looked like the civilized, genteel sport of football, where each beloved quarterback gets to take his turn, and everybody’s careful not to to interfere with Manning’s and Brady’s greatness. It was a quiet and mildly interesting tennis match.
The 49ers would have beaten the Broncos too.
When people say there are differences in style between the conferences, I don’t know what to think. I don’t watch enough games to know. But if the Broncos are representative of an AFC style, then I don’t see how that style can stand up to the NFC’s style of hitting and scrambling, of communication and speed, of contesting every single part of every play.
It’s not fancy. There’s little disguise. The strategy is to play better than the other team.
* * *
Before the game, analysts were predicting a close game, with one team or the other winning by a field goal. (Which sounds like a safe bet for any Super Bowl: no shit the teams ought to be pretty evenly-matched.)
After the game, some analysts predicted a Seahawks dynasty, since they’re such a young team. And they predicted that lots of teams would adopt Pete Carroll’s coaching style and the Seahawks’ defensive style.
As if one big win can change the league. I’m skeptical.
As if the 49ers — and Eagles, Saints, Panthers, and Packers — won’t be making some solid moves to knock off Seattle next year.
Remember that the Seahawks nearly lost to the then-winless Buccaneers last November. Anything can happen. Had we lost that game, the 49ers could have won the division and home-field advantage, and likely would have gone to the Super Bowl and beaten the Broncos (35-17 in this alternate world). (But we came back from 21 points down to beat the Bucs in overtime. Because we’re that good.)
* * *
The four major sports leagues are the NFL, MLB, NBA, and NHL. Seattle has a previous championship in these leagues: in 1979 the SuperSonics beat the Washington Bullets four games to one. (Otherwise we go back to 1917 when the Metropolitans beat the Canadiens to become the first U.S. team to win the Stanley Cup.)
To make matters worse, the SuperSonics moved. We don’t have an NHL team. And our baseball team is, well, it’s the Mariners. (Hipsters love them because you’ve never heard of them.)
To say that we were hungry is an understatement.
I don’t know what it means to the city. Something about confidence? Something about community? Something about joy? All that, but the details will take a while to understand.
* * *
I was so totally looking forward to the Super Bowl game. Imagine. I expected it to be close, but I expected to win. My reasoning: 1) a great defense beats a great offense, and 2) our offense is way better than their defense.
Of the individual players I was most excited to see what Percy Harvin would do. Due to injury he’d played only parts of a couple games, but when he was on the field he played beautifully. It’s not that he’s better than everyone else, it’s that he’s so much better than everyone else. So much faster.
When Harvin returned the second-half kickoff for a touchdown, I laughed.
The Broncos defense did do a good job of shutting down Marshawn Lynch. Except, that is, for when it counted, when he was on the one yard line.
And our defense? Well. They’re the most effective and thrilling team in all of sports. (Says me.)
* * *
My friend @rands:
Biggest disappointment of flying during the Super Bowl. Can’t hang out on Twitter and make fun of the Seahawk’s loss.
* * *
I am ridiculously happy — not just a win but a knock-out. Decisive. Unambiguous. Satisfying. Fun.