iTunes Manglement

I think we can all agree that iTunes is in need of a major overhaul. So why isn’t it happening?

Apple has shown no fear of rethinking and innovating with the user interfaces that manage our own personal data. Both the iMovie and iPhoto apps are great examples of this: they’ve gotten much simpler to use over the years (especially as they’ve moved to the iOS platform.)

This makes me think that there may be another factor that’s holding back iTunes; and I fear that it’s contractual.

Much of iTunes functionality is based around content that Apple or the user doesn’t own. And as we all know, the media companies that own the content are particularly paranoid about how digital assets are managed. In the 10+ years that iTunes has been in existence, I’m sure there’s a tangled web of legal obligations that makes improvements a huge technical headache.

To give you an idea of how painful this must be, imagine being a developer at Apple and having to consult this before implementing or improving a feature in iTunes. And when you’re done wrapping your head around those conditions, make sure you have thought about restrictions in other parts of the world. Having fun yet?

Core Data without Fetch Requests

If you follow me on Twitter, you’re probably aware of a really nasty problem I encountered with versioned Core Data models. Suffice it to say that was two days of hell caused by a single Fetch Request in a .xcdatamodeld file.

The irony of it all is that I no longer use Fetch Requests this way: the queries defined in the model file weren’t being used so I was happy to delete them all.

In my opinion, fetch requests that are stored as a part of the managed object model have some downsides:

  • You need to carry around an instance of NSManagedObjectModel in order to access the requests.
  • A text-based search (such as Find in Workspace from Xcode’s Search Navigator) doesn’t find the requests.

Instead, I use a category for NSManagedObjectContext that adds some nice functionality for creating fetches and executing them purely with code. The original idea and implementation comes from Matt Gallagher.

A picture’s worth a thousand words, so let’s look at how this works in code. Instead of creating an “existingPartner” fetch configured as:

Fetch all: Partner objects where:
Expression: name == $NAME

I write this code:

NSArray *objects = [managedObjectContext fetchObjectArrayForEntityName:@"Partner" withPredicateFormat:@"name == %@", name];

If I need the managed objects in sorted order, there’s this variant:

NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];

NSArray *objects = [managedObjectContext fetchObjectArrayForEntityName:@"Partner" usingSortDescriptors:sortDescriptors withPredicateFormat:@"name LIKE %@", pattern];

Getting count entity objects is just as easy:

NSNumber *salaryLevel = [NSNumber numberWithInteger:1000000];
NSUInteger count = [managedObjectContext countForEntityName:@"Employee" withPredicateFormat:@"salary > %@", salaryLevel];

After reading about how NSExpression can be used to significantly speed up fetches against attribute values, I extended Matt’s NSManagedObjectContext+FetchAdditions category. This lets you do things like this:

NSNumber *marketingPayroll = [managedObjectContext fetchObjectArrayForEntityName:@"Employee" usingAttribute:@"salary" andFunction:@"sum:" withPredicateFormat:@"department == %@", marketing];

One thing to keep in mind when you’re using NSExpression against a SQLite store: it only works on data that has been persisted. Since the underlying implementation relies on SQL functions such as min(), max() and sum(), any managed objects that you have in memory (because the context hasn’t received a -save) won’t be included in the results.

One final trick I use is to keep the code for these fetches as close to the objects and attributes as possible. I’ve found it very handy to create class methods in the NSManagedObject subclasses. For example, using our partner name example from above, I’d implement the following class method for the Partner object:

+ (Partner *)fetchInManagedObjectContext:managedObjectContext withName:(NSString *)name
{
	Partner *result = nil;
	
	NSArray *results = [managedObjectContext fetchObjectArrayForEntityName:@"Partner" withPredicateFormat:@"name == %@", name];
	if ([results count] > 0) {
		result = [results lastObject];
	}
	
	return result;
}

Then when I need a Partner with the given name, I use:

Partner *partner = [Partner fetchInManagedObjectContext:managedObjectContext withName:@"CHOCK GLOBAL INDUSTRYS INK"];

I hope this category cleans up your code as much as it has mine. Feel free to use this code in any way you see fit.

VMware for developers

Many of us rely on VMware Fusion for testing our products both on older and newer versions of Mac OS X. Your development machine may be running Lion, but it’s incredibly handy to run both Snow Leopard and Mountain Lion on the same machine.

With the recent release of Mountain Lion DP2 some problems cropped up with Fusion 4.1 (you’ll need a developer account to view that link.) Luckily the new VMware Fusion Technology Preview 2012 makes it possible to run the latest Mountain Lion release without any problems. This preview release also allows you to keep Fusion 4.1 installed if you encounter any problems in other virtual machines: the only requirement is that you can only run one version at a time.

Also worth nothing: if you used the bug in VMware Fusion 4.1 to create a virtual machine that uses the client version of Mac OS X 10.6, you’ll be disappointed as soon as you try to restart that VM in the Technology Preview. During the reboot, you’ll see a window pop up with the message:

The guest operating system is not Mac OS X Server. This virtual machine will be powered off.

You’ll need to keep VMware Fusion 4.1 around if you want to apply Software Updates to your test environments.

I wish Apple would relax this restriction with the 10.6 clients: we still have products that rely on older versions of Xcode. Apple themselves even have sample code that can’t be opened in Xcode 4, preventing developers from exploring older, but useful, projects.

Sandboxing

The recent release of xScope 3.0 is our first product to use the new application sandbox that will soon become a requirement for submission to the Mac App Store. I’d like to share some experiences and advice on how to use it in your own products.

First off, Ivan Krstić and the rest of the team at Apple have done a great job in making the whole process easy to implement. Adding entitlements and signing your code will be the least of your worries as you transition to the new sandbox.

Of course there are some applications that have a harder time than others: primarily if those apps require access to all or part of the filesystem (think about syncing data with Transmit, for example.) Apps that make use of AppleScript for inter-app communication will also have a difficult time: this includes our Take Five app. Apple is actively listening to developers who are encountering these types of issues, so if you haven’t filed a Radar yet, quit bitching.

Speaking of Radar, we encountered a fairly nasty problem after launching xScope. Many of our customers are designers and developers who love SSDs. It’s common to use a symlink in your Home folder to put big datasets like Pictures, Music and Movies on a separate hard drive. When you do this, folder access in the application sandbox container breaks. A small number of users who use symlinks are also getting crashes after launching the app that was downloaded from the Mac App Store:

xpchelper reply message validation: sandbox creation failed: 1002
Container object initialization failed: The file couldn’t be opened.

We also encountered a problem when using Sparkle to update an app running in a sandbox: an app can’t update its own binary. Changing Sparkle so that it uses an XPC service is a major architectural change, so we decided to remove the sandbox for the version we distribute on the website.

Besides being the path of least resistance, it also gives us a version of xScope that doesn’t run into the sandbox bugs reported above. I highly recommend that you give yourself this option for any customers that experience sandbox related problems.

All things considered, adding an application sandbox has been a fairly smooth transition. But it’s also clear that we’ve only just begun putting the genie back in the bottle.

Updated January 27th, 2012: The bug reported above is a duplicate of Radar 9865143.

Homebase

A lot of people I know and respect have been commenting on problems associated with the iPhone mute switch:

John Gruber – On the Behavior of the iPhone Mute Switch
Andy Ihnatko – Unmuting on The Mute Question
Marco Arment – Designing “Mute”
Guy English – Mute This

Both sides of the argument have valid points-of-view. This really is a situation with no right answer given the current mechanisms.

That got me thinking that there might be something missing that’s causing this ambiguity. I’ve come to the realization that this is a problem bigger than just alarms going off at inopportune moments. What we really want is for the devices in our pocket to behave differently depending on where they’re physically located.

Let’s imagine a new feature in iOS called “Homebase”. A user would be presented with a simple UI that lets them select a location that’s a “safe” environment. After the setup is complete, your Homebase would be recognized by GPS coordinates and/or available Wi-Fi networks. The important thing here is that the user gets to define where they feel safe with their device.

With that information developers can make smarter decisions:

  • Alarms that go off while the mute switch is on make noise at Homebase and just vibrate elsewhere. There’s no need to worry about alarms going off in public places (such as concert halls) and you won’t oversleep when you go to bed with a mute switch on.
  • The lock screen doesn’t need to display a Passcode lock at Homebase. People who use the Remote app with their Apple TV will no longer be annoyed by an unnecessary security precaution, nor will folks forget to turn their Passcode lock back on when they leave for the local bar (where they’re certain to get a Poopin’ tweet.)
  • Apps, like Find My Friends, could use cached Apple ID credentials at Homebase and avoid asking the user for them over and over and over and over again.

Of course, this feature is needed most by people who don’t even know the Settings app exists. It’s my opinion that if developers are careful with this additional knowledge about the user and device, default behavior can be adjusted appropriately without additional confusion. It’s analogous to the Energy Saver on the Mac: people don’t question why the screen dims when the power cord is removed because it just “makes sense”.

The examples above use Apple’s own apps, but the Homebase status would be useful for third-party developers, too.

If you’d like to see something like Homebase in iOS, please be sure to file a duplicate Radar.