Responding to App Store Reviews

When developers talk about wanting to respond to reviews, many of them haven’t thought through the social implications of what that means. Matt Gemmell has. As Marco Arment points out, replying publicly also leaves iTunes (more) open for abuse by unscrupulous or uninformed developers.

One idea I’ve had is giving developers the ability to add a support link to a review. This helps both the developer and customer in several ways:

  • The customer who reported the problem could be notified that a support link was added to their review and would be directed to a site which is designed to help them out. This could also lead to direct contact if there are other issues to be resolved.
  • Potential customers that are reading reviews can see how a developer responds to problems. If you come across a product with lots of support links, you know that’s a developer who cares about his customers.
  • Putting customer service front and center in iTunes makes it desirable for developers to create and maintain sites that provide helpful information. There are far too many products where the customer support link just goes to a product page that’s unhelpful.

Of course, restrictions would be needed to prevent abuse of these external links. For example, Apple could decide to only allow links to a developer’s support domain. There could also be limits on the number of support links a developer has at their disposal (like promotion codes, we would then use them judiciously.)

Finally, these thoughts only cover the information we exchange with the customers publicly. I still think there are cases where private contact via email is vital.

Debugging Core Data Objects

If you’re working on an app that uses Core Data, it’s inevitable that you’ll end up in the debugger and need to dig around in the object graph. You’ll also quickly realize that Core Data’s -description of an object isn’t terribly helpful:

(lldb) po myListObj
(List *) $19 = 0x08054ac0 <List: 0x8054ac0> (entity: List; id: 0x8074280 <x-coredata://29B10357-0723-4950-9EB6-E6D7AD6269B9/List/p175> ; data: <fault>)

Core Data’s documentation is excellent, but surprisingly doesn’t cover some of the tricks you can use to examine managed objects in the debugger.

The first trick is to fire a fault on the object using -willAccessValueForKey. After that, you can see what’s really there:

(lldb) po [myListObj willAccessValueForKey:nil]
(id) $20 = 0x08054ac0 <List: 0x8054ac0> (entity: List; id: 0x8074280 <x-coredata://29B10357-0723-4950-9EB6-E6D7AD6269B9/List/p175> ; data: {
    containerId = "8E4652D3-5516-4186-B1C9-DDBE41E108CF";
    createdAt = "2009-10-08 15:17:53 +0000";
    itemContainers = "<relationship fault: 0x8020850 'itemContainers'>";
})

You might also be surprised when you try to access one of the properties of the object:

(lldb) po myListObj.containerId
error: property 'containerId' not found on object of type 'List *'
error: 1 errors parsing expression

Remember that these properties are defined as @dynamic and there’s a lot of work done by Core Data at runtime to provide the implementation. The solution here is to use the KVC accessor -valueForKey: to get the object’s value:

(lldb) po [myListObj valueForKey:@"containerId"]
(id) $26 = 0x08069b60 8E4652D3-5516-4186-B1C9-DDBE41E108CF

Often, you’ll want to examine the relationships between objects. As you can see in the output above, the attribute itemContainers, which is a to-many relationship, is a fault. To fire the fault, get all the objects from the set:

(lldb) po [[myListObj valueForKey:@"itemContainers"] allObjects]
(id) $23 = 0x0d0667b0 <__NSArrayI 0xd0667b0>(
<Container: 0x807a180> (entity: Container; id: 0x801bc50 <x-coredata://29B10357-0723-4950-9EB6-E6D7AD6269B9/Container/p1> ; data: {
    containerId = TestContainer;
    items = "<relationship fault: 0x805b100 'items'>";
    state = "(...not nil..)";
    type = 4;
})
)

Finally, you may be using Transformable attribute types. The state attribute above is an example. If you’d like more information than (...not nil...), use the KVC getter and you’ll see that it’s an empty NSDictionary:

(lldb) po [[[[myListObj valueForKey:@"itemContainers"] allObjects] lastObject] valueForKey:@"state"]
(id) $29 = 0x0807dd30 {
}

(lldb) po [[[[[myListObj valueForKey:@"itemContainers"] allObjects] lastObject] valueForKey:@"state"] class]
(id) $30 = 0x01978e0c __NSCFDictionary

It’s taken me months to learn these tricks, so hopefully this short essay helps you come up to speed more quickly than I did!