Detective update

Twitter recently changed some of their rules for app authorization using OAuth, so as a result, when a new user tries to start Detective, they will get an error when authorizing Twitter. However, if you’re already using Detective it will continue to work as long as you don’t log out of Twitter.

Detective uses MGTwitterEngine, which no longer seems to be actively maintained and which still uses Twitter’s deprecated 1.0 API. As a result, at some point it would stop working when Twitter shuts off that API. I’ve looked into a few options and I’ve determined that the best way to move forward is to use Mac OS X’s built-in social framework. Rather than asking you to log in to Twitter, it will now use your Mac’s twitter accounts. However this means we will no longer be able to support any Mac OS X version earlier than 10.8. I hope to have an update available soon.

Picslide is now open source

I’ve decided to open source PicSlide, which was one of my first iOS games. It isn’t selling enough to make it worthwhile updating for the iPhone 5. When I looked at the code I didn’t find it too embarrassing and it didn’t make me want to vomit, despite being written in 2009, so I decided to share it. Some pieces like scaling & slicing images and doing Quartz drawing may be helpful as sample code.

The source code is now available on Github at https://github.com/mike3k/picslide

Sugar Rush 1.4 Update

I’ve submitted a minor update to Sugar Rush which changes the FaceBook SDK to use the latest API so sharing will continue to work. It also adds support for built-in Twitter accounts in iOS 5 or later and updates the Kiip SDK to the latest version.

Sugar Rush Free gets a much bigger update. In addition to those SDK changes, I’ve renamed it to Sugar Rush Lite and added in-app purchase to get rid of the ads & height limit. You can also buy Donuts, which will make it functionally equivalent to Sugar Rush Pro. Both Sugar Rush Lite & Sugar Rush Pro now share the same Game Center leader board & achievements.

Sandboxing revisited

When I submitted an update to Detective, I discovered a few tricky things related to sandboxing and embedded helper apps.

In order to support ‘start at login’ in a sandboxed app, you need to embed a helper app that launches the main app (the entire process is described here). What I didn’t realize is that the helper app also has to be signed, or it will fail to let you start it at login. However, when you sign the helper app, it will include its own embedded provisioning profile, so when you try to submit your app, it will be rejected with the following message:

Invalid Provisioning Profile Location – The provisioning profile for your Mac OS X app must be located in the Contents directory of the main app bundle. A provisioning profile is optional, but you cannot submit more than one.

One of the suggestions in Apple’s developer forum is to remove the embedded profile from the helper app. Note that deleting the embedded profile doesn’t affect the actual code signing. After some experimentation, I found that the easiest way to do it is to add a Run Script build phase to the main application that deletes the profile from the helper app:

#!/bin/sh
rm ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Library/LoginItems/DetectiveLoginHelper.app/Contents/embedded.provisionprofile

After doing this, I was able to submit the app successfully.

Login items in the sandbox

Apple will soon require all Mac apps submitted to the app store to be sandboxed for heightened security, which means it needs to request permission for doing even basic things like accessing files or connecting to the internet. A lot of things like disk burning aren’t allowed at all in the sandbox, and some things like sending AppleEvents require temporary exemptions which will be phased out.

One common task that’s complicated by the sandbox is adding a login item. A good tutorial on creating login items is available at delite studio, although some changes to the code are needed. That code follows Apple’s earlier guideline of using LSRegisterURL to register your helper app. However, I found that it always fails with error -10819. According to an Apple engineer in Apple’s developer forum, you should not call LSRegisterURL in a sandboxed app.

Note that your application can’t add itself as a login item. You need to write a simple helper app that launches your main app and it must be included in the main application bundle in the relative path /Contents/Library/LoginItems/.

The method for adding & removing a login item turned out to be very simple:

- (void)addLoginItem {
    NSString *ref = @"com.madebynotion.myLoginHelper";
	if (!SMLoginItemSetEnabled((CFStringRef)ref, true)) {
		NSLog(@"SMLoginItemSetEnabled failed.");
	}
}

- (void)removeLoginItem {
    NSString *ref = @"com.madebynotion.myLoginHelper";
	if (!SMLoginItemSetEnabled((CFStringRef)ref, false)) {
		NSLog(@"SMLoginItemSetEnabled failed.");
	}
}

If you need to find out whether your login item is enabled, here’s a way to do it:

-(BOOL)appIsPresentInLoginItems
{
    NSString *bundleID = @"com.madebynotion.myLoginHelper";
    NSArray * jobDicts = nil;
    jobDicts = (NSArray *)SMCopyAllJobDictionaries( kSMDomainUserLaunchd );
    // Note: Sandbox issue when using SMJobCopyDictionary()

    if ( (jobDicts != nil) && [jobDicts count] > 0 ) {

        BOOL bOnDemand = NO;

        for ( NSDictionary * job in jobDicts ) {

            if ( [bundleID isEqualToString:[job objectForKey:@"Label"]] ) {
                bOnDemand = [[job objectForKey:@"OnDemand"] boolValue];
                break;
            }
        }

        CFRelease((CFDictionaryRef)jobDicts); jobDicts = nil;
        return bOnDemand;

    }
    return NO;
}

Although this method works and seems to be the preferred way to do it in the sandbox, it’s less optimal than the old non-sandbox method, since your login item won’t appear in the users & groups preference panel’s login items list and can only be turned on & off from your own application.

Core Data debugging fun

A few days ago I spent a lot of time tracking down a data validation error in Core Data which turned out to have a very simple cause.

The application I’m working on has two entities for customer and order. Each order must be linked to a single customer.

When a new order is saved, I call this function to save changes:


- (void)saveContext {

    NSError *error = nil;
	NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        }
    }
}

The first time I created a new order, I was able to save it successfully. The next time I tried to save an order, I got a validation error. When I inspect the new order I just created, it looks correct, with a proper customer relationship.

Looking at the NSManagedObjectContext in the debugger offers a few clues. You’ll see that it has sets of inserted & changed items, which you can inspect by typing ‘po managedObjectContext->_insertedObjects‘ or ‘po managedObjectContext->_changedObjects‘.

Screen Shot 2011-10-13 at 8.20.37 PM.png

The set of inserted object contained exactly what I expected, but the set of changed items contained some unexpected items, including the previous order, which now had its customer relationship set to nil. Since the customer is required, the data validation failure was not on our newly inserted item, but on the previous order which used to be valid.

How could I be changing that previous order? I couldn’t find any place in my code which could have been doing it. Instead it turned out to be a simple error in my data model.

When you create a relationship, it can be either required or optional, and it can be to a single entity or to many entities. Every relationship also needs to have an inverse relationship.

In this case, my order had a required to-one relationship with a customer, which meant customer also had to have an inverse relationship to orders, which was optional. However, I neglected to set my customer to order relationship as to-many.

Therefore, although I never touched the customer’s relationship to orders, when I created a new order & set the customer, Core Data also set the customer’s order relationship. Since I didn’t make it to-many, doing so wiped out the relationship to the previous order, which also wiped out that order’s required relationship to a customer.

Moral of this story: check your relationships carefully and make sure you set the proper attributes for the inverse relationship. In this case, I spent many hours debugging something caused by setting a checkbox incorrectly in the model editor.

Easy Twitter & FaceBook sharing with ShareKit

I’ve had a few questions about how to implement Twitter & FaceBook sharing, so here’s how I did it.

ShareKit provides an easy drop-in solution for posting to Twitter, FaceBook, Instapaper, Tumbler, and many other services. However, in many cases you don’t need all of the features and simply want to post a status to FaceBook or Twitter.

It takes only two lines of code to simply share an item to Twitter, FaceBook, or most other supported services:

  1. Create a SHKItem with the text you want to post
  2. Call the shareItem: method of the appropriate subclass.

These are the specific methods for Twitter & FaceBook:


- (IBAction)tweet:(id)sender
{
    SHKItem *aTweet = [SHKItem text: @"Share this text"];
    [SHKTwitter shareItem:aTweet];
}

- (IBAction)facebook:(id)sender
{
    SHKItem *post = [SHKItem text: @"Share this text"];
    [SHKFacebook shareItem:post];
}

When you call shareItem: it will display the login & authentication prompt and show the item to be shared.

The most difficult part is setting up your application on Twitter or FaceBook with the correct permissions and obtaining the proper API keys. Read the instructions in SHKConfig.h carefully for details on how to set up your application and enter the API keys there.

I have my own branch of ShareKit at https://github.com/mike3k/ShareKit which fixes a problem related to iOS 5 and gets rid of most of the compiler warnings.

Distributed Objects are easier than you think

For my current project, I had to implement interprocess communication between a background process and an application. In the past, I would probably use AppleEvents or even CFMessage, but this tech note advises against using those techniques.

After some investigation, I found that Distributed Objects is the easiest & cleanest way to do it.

Distributed Objects works its magic with a set of proxy classes that pose as the actual object which exists in the other application. Here’s how to use it.

The object you want to share can be any ordinary object, like this one:

@interface MyObject: NSObject
- (int)myMethod;
@end

@implementation MyObject
- (int)myMethod {
	return 42;
}
@end

On the server side, you create a connection and vend that object using NSConnection’s setRootObject: method.

#define SOCK_NAME "/var/tmp/com.example.socketname"

   // make sure socket file doesn't exist, or we'll fail
    unlink(SOCK_NAME);

    // create an AF_UNIX socket address
    struct sockaddr_un socketAddress;
    bzero(&socketAddress,sizeof(socketAddress));
    socketAddress.sun_family = AF_UNIX;
    strcpy(socketAddress.sun_path,SOCK_NAME);
    socketAddress.sun_len = SUN_LEN(&socketAddress);
    NSData* socketAddressData = [NSData dataWithBytes:&socketAddress length: sizeof(socketAddress)];

    NSPort * theMessagePort = [[NSSocketPort alloc] initWithProtocolFamily: AF_UNIX
                                                     socketType: SOCK_STREAM
                                                       protocol: 0
                                                        address: socketAddressData];

    NSConnection * theConnection = [[NSConnection alloc] initWithReceivePort: theMessagePort  sendPort: nil];

    // create our object to be shared
    theObject = [[MyObject alloc] init];
    [theConnection setRootObject: theObject];

In the client application, you create a remote connection and ask for a proxy object. You then use that proxy object as if it was the actual object.

    // create an AF_UNIX socket address
    struct sockaddr_un socketAddress;
    bzero(&socketAddress,sizeof(socketAddress));
    socketAddress.sun_family = AF_UNIX;
    strcpy(socketAddress.sun_path,SOCK_NAME);
    socketAddress.sun_len = SUN_LEN(&socketAddress);
    NSData* socketAddressData = [NSData dataWithBytes:&socketAddress length: sizeof(socketAddress)];

    // Create a message handler socket and add it to our runloop
    NSPort * theMessagePort = [[NSSocketPort alloc] initRemoteWithProtocolFamily: AF_UNIX
                                                           socketType: SOCK_STREAM
                                                             protocol: 0
                                                              address: socketAddressData];

    NSConnection * theConnection = [[NSConnection alloc] initWithReceivePort: nil sendPort: theMessagePort];

    // this is actually a NSDistantObject which acts as a proxy to our actual object
    MyObject *anObject = (MyObject*)[theConnection rootProxy];

    // call a method on the remote object
    int result = [anObject myMethod];

What really happens when you call the method in the client is that it gets packaged as a NSInvocation, which gets sent over the connection and is executed by the server process.

Sugar Rush, Removr, and iOS 5

I’ve been able to test my apps in iOS 5 and the only one that has an issue is Removr. Sugar Rush is 100% compatible with iOS 5 and doesn’t require an update.

Removr immediately returns to the menu when you tap play or try to go to a level. I’ve already fixed it and will submit the update after some further testing as soon as Apple accepts iOS 5 application updates.

There seems to be a change in SQLite’s behavior under iOS 5 which affected Removr. I’m storing the level maps as a SQLite database in the application bundle. In the current version, I’m opening the database as follows:


int err = sqlite3_open([self.dbpath UTF8String], &db);

Under iOS 5, that fails unless readonly mode is specified when opening the database. The simple fix was merely to specify read only mode as follows:


int err = sqlite3_open_v2([self.dbpath UTF8String], &db,SQLITE_OPEN_READONLY,NULL);

I will release a Removr beta in the next few days to anyone whose device ID I have (I can’t add any new devices until July 16).

A big piracy problem

Our early sales for Sugar Rush were very low, despite a big PR push.  We had a problem on the first day when the screen shots disappeared for a few hours after I tried to update the description & screen shots. Second day sales were even worse.

However, I noticed something very interesting: the number of users in Game Center and the number of users reported by Flurry Analytics were at least 3 times the total number of sales reported in iTunes Connect. At first I thought the iTunes reports were delayed, but a google search revealed that there are lots of pirated copies available. If the numbers are accurate, this means there are at least 3 or 4 times as many pirated downloads as we had legal sales.

I’m amazed and disappointed that an app can be that widely pirated after only two days of sales, especially when the legal sales were lackluster.

As a result I’m very unlikely to develop any future iOS apps, since it looks like I can’t make a living on app sales.

UPDATE: according to a source, the illegal copy got 2000 downloads, which is a lot more than 4x the number of legal downloads.