2014-12-18

Super Foul Egg iOS Retires

Good news, everyone! Super Foul Egg for iOS is now unavailable. I’ve been considering pulling it down for a while, mainly because I have no time to maintain it or fix any of the bugs in the game. They are only minor - the app icons are wrong and one of the menu screen bitmaps is the wrong size on retina iPads - but they irk me every time I pick up my iPad. That this is only my eighth post this year should be a good indicator of how much free time I have. In the 18 months that the iOS version was available, at the wallet-busting price of “free”, it managed to attract a grand total of 370 punters. Clearly very few people will be inconvenienced by the game’s retirement.

What finally prompted me to retire the game was a very friendly email from the original game’s author asking me to pull my version from the store to make way for an upcoming release of his own. This is fantastic, partly because I no longer have to feel guilty for not releasing updates, but mostly because I am terribly excited to play an official remake. I’ll post a link up here as soon as it appears in the app store.

The 370 folks who enjoyed Super Foul Egg can still find the OSX version at superfoulegg.com, and the source code for the OSX and iOS versions is hosted on GitHub:

2013-02-21

Encoding HTML Entities with libXML

iOS doesn’t have a standard, easy-to-use way of encoding UTF-8 strings into HTML entities (ie. from “>” into “>“). One library that can be used to achieve this is libXML, but its API is particularly unpleasant:

int htmlEncodeEntities (unsigned char * out, 
                        int * outlen, 
                        const unsigned char * in, 
                        int * inlen, 
                        int quoteChar)

out: a pointer to an array of bytes to store the result
outlen: the length of @out
in: a pointer to an array of UTF-8 chars
inlen: the length of @in
quoteChar: the quote character to escape (' or ") or zero.

Returns: 0 if success, -2 if the transcoding fails, or -1 otherwise
The value of @inlen after return is the number of octets consumed as
the return value is positive, else unpredictable. The value of
@outlen after return is the number of octets consumed.

What’s wrong with that function signature, you ask? Other than the quality of the documentation, that is? Consider this situation. You need to encode this string:

<p>

That will encode into this string:

&lt;p&gt;

Now consider this braindead catch-22 situation: You must allocate the memory for the “out” parameter before you call the function, but until you call the function you won’t know how much memory you need to allocate.

The raw string in the example above is 4 bytes long; the encoded version is 10 bytes (don’t forget that this is C and all strings are NULL-terminated). If you follow the advice from Stack Overflow and double the initial size you’ll end up with 7 bytes (1 terminator + (3 characters * 2)), which is still too short. Another alternative is to figure out the maximum amount of memory that an encoded string could possibly consume (9 bytes per character) and use that, but then you’ll potentially be wasting massive amounts of memory.

If I were writing the function I’d probably return a pointer to a block of memory allocated inside the function itself. It could re-allocate the memory as needed and users of the function wouldn’t need to guesstimate the buffer size. That would raise my favourite C question, though: Who owns this memory?

I couldn’t find any examples of how to use the httpEncodeEntities() method, so I came up with my own. This solution uses a loop and encodes the string in chunks. It uses an encoded buffer twice the size of the initial string, but will resize it if it finds that the buffer isn’t large enough for a single encoded character. It’s implemented as a category method on NSString.


#import <libxml/htmlparser.h>

@implementation NSString (SZHTMLEncoding)

- (NSString *)stringByEncodingHTMLEntities {

    if (self.length == 0) return;
    
    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
    
    int remainingBytes = data.length;
    int bufferSize = (data.length * 2) + 1;
    const unsigned char *bytes = (const unsigned char *)[data bytes];
    
    // We have to add an extra byte on the end of the encoded string to enable
    // us to add a terminator character.
    unsigned char *buffer = malloc(bufferSize);
    buffer[bufferSize - 1] = '\0';
    
    NSMutableString *output = [NSMutableString stringWithCapacity:remainingBytes];
    
    do {
        
        int outLen = bufferSize - 1;
        int inLen = remainingBytes;
    
        int result = htmlEncodeEntities(buffer, &outLen, bytes, &inLen, '"');
        
        // libXML doesn't append a terminator to the string - presumably because
        // NSString doesn't include one - so we'll have to take care of that in
        // order to convert back to an NSString.  We only add this if we haven't
        // completely filled the buffer.  If we've filled it, we've already
        // added the terminator character.
        if ((NSUInteger)outLen < bufferSize - 1) {
            buffer[outLen] = '\0';
        }
        
        if (result == 0) {
            
            NSString *string = [NSString stringWithCString:(const char *)buffer encoding:NSUTF8StringEncoding];
            
            [output appendString:string];
            
            remainingBytes -= inLen;
            
            if (remainingBytes > 0 && inLen == 0) {
                
                // Oh no!  We've got characters left to encode but they aren't
                // encoding.  This happens if our buffer isn't big enough, so
                // we'll resize it.
                free(buffer);
                bufferSize = ((bufferSize - 1) * 2) + 1;
                buffer = malloc(bufferSize);
                buffer[bufferSize - 1] = '\0';
            }
        } else {
            
            // Something bad happened
            break;
        }

        bytes += inLen;
        
    } while(remainingBytes > 0);

    free(buffer);
        
    return output;
}

@end

2012-11-20

Super Foul Egg iPad Progress

Hardware test successful! SFE runs on both the standard and retina iPads. Now that I’ve switched it to using standard UIGestureRecognizers instead of trying to roll my own in the Cocos2D touches callbacks, the control system works flawlessly.

Once I’d succeeding in getting the controls working, I was very concerned about how usable and intuitive they’d be. My first few games saw me lose miserably to the easiest AI setting, but after a dozen games I progressed to the point where I can occasionally beat the “hard” setting. I’m nowhere near as good with the touchscreen as I am with the cursors (where I can sometimes beat the “insane” AI), but it proves that the touchscreen can work as an input device for this kind of game without resorting to an onscreen joypad.

Needless to say, I’m planning to buy an iCade 8-bitty joypad and get it working with that.

2012-11-17

Super Foul Egg - iPad

Soon:

Here’s what’s left to do:

  • Pause/exit game;
  • Replace any bitmaps that mention keys with bitmaps that mention touches;
  • Test it on real hardware.

I don’t currently have an iPad to test on. I had a quick try on the office iPad and discovered that the initial control system I’d written was unresponsive and unusable, so I’ve just finished a re-write of that.

I had two options for the control system. The obvious solution was to use an on-screen joypad. However, I detest iOS games that use an on-screen joypad to such a degree that I don’t even bother trying them. A more interesting solution was to try and work with the touchscreen instead of working against it and use gestures.

This is what I’ve implemented:

  • Tap with one finger to rotate the current shape clockwise;
  • Tap with two fingers to rotate the current shape anticlockwise;
  • Swipe downwards with two fingers to auto-drop the current shape (I had to concede that the hated auto-drop makes sense in the context of an iPad);
  • Touch and drag with one finger to move the current shape left and right (it follows the movements of the finger).

These gestures can be performed anywhere on the screen, which means the player’s fingers won’t get in the way of the action.

Unfortunately, I had to remove the two-player mode in order to accommodate this control system. I’m hoping that I’ll get around to using GameKit to allow multiplayer on several devices. Perhaps I’ll get an 8-player mode implemented.

Getting SFE working on a new platform has allowed me to look over the code again. Back in August 2011 I said this about the codebase:

My code probably looks hideous to experienced Objective-C coders as I’ve trampled conventions all over the place.

Just over a year later, I’m pleased to say that I agree with myself. The SFE code is pretty nasty in places. Here are a few mistakes I’ve spotted in the last few minutes:

  • Using #define instead of constants.

C guys use #define. C++ guys use consts. I went with #define because objc is clearly C-with-classes, but consts are better style.

  • Using blocks instead of protocols and delegates.

Blocks are cool! They’re also finicky when doing manual memory management and are an exceptionally good way to end up with cyclic references. For the most part I used blocks to separate out the game engine from the I/O code, which made the code vaguely MVC-like. Great idea (and wonderful for getting the game working on the iPad) but using delegates and protocols would have simplified things immensely and been more conventional.

  • Not cleaning up references after releasing objects.

Try doing something to the live block references in a Grid object when it doesn’t have live blocks. Ooops: crash.

  • Not following standard Objective-C naming conventions.

Pretty much everything should have been prefixed with “SZ” but wasn’t.

  • Following standard Objective-C naming conventions.

The enum names follow Apple’s official enum naming style (aside from the missing two character prefix), but then Apple have two styles and the official style sucks. It’s impossible to use autocompletion to determine the contents of an enum if the values are named “NSUTF16BigEndianStringEncoding” instead of “NSStringEncodingUTF16BigEndian”.

  • Using “SomeType* variable” instead of “SomeType *variable”.

Nitpicking, I know. I think the former makes the most sense semantically - the full type definition is “a pointer to SomeType”, so the asterisk is grouped with the type name - and is what I use in C/C++. However, I’ve been following Apple guidelines and now use the latter in objc. Apple don’t always follow this themselves, though. The fact that their templates don’t consistently place the opening brackets for a code block in the same place irritates me, too.

  • Protocol names.

“ControllerProtocol”: It’s like backwards Hungarian notation.

  • Fear of singletons.

It’s widely acknowledged that singletons are a Bad Pattern. They can’t be tested! They can’t be mocked! Every time you use a singleton a child dies! Look through most of my code and I’m pretty sure you’ll find that I try to avoid singletons wherever possible. Part of this was a response to the irrational level of hate levelled at the pattern, and part of it was due to the number of issues that singletons cause when working on web applications in .NET (hint: static variables have an application-level scope, not a client-level scope).

On the other hand, singletons are used extensively in Apple’s objc frameworks, and I now use them everywhere too. TDD crowd be damned. The “BlockFactory” could easily be a singleton, accessed via [SZBlockFactory sharedFactory], instead of being injected into every class that needs it like I’m writing some kind of enterprisey abomination.

  • “SZPoint”.

I’m hoping that this was a holdovee from the C++ version, because I can’t think of a valid reason why I’d create a class with identical functionality to CGPoint.

  • Reference counting.

I haven’t seen any examples yet, but this sentence from the August blogpost sounds extremely dumb:

For the most part, I’ve used what Objective-C calls “weak references” - I’ve got pointers to objects without retaining them, the same as I would in C++.

I might try to tidy some of this mess up. On the other hand, it ain’t broken…

2012-11-08

Barcodes in iOS

Recently I’ve been investigating methods for getting iOS apps to read one and two dimensional barcodes. There’s a huge variety of formats, but the three I’m mainly interested in are UPCA and EAN-13 (linear barcodes you’ll find on boxes in shops) and PDF417 (matrix barcodes you’ll find on some US driving licences).

There appear to be three main competitors for your attention if you’re trying to read two-dimensional barcodes in an iOS app:

ZXing is an open-source offering that supports a smorgasbord of formats for a variety of platforms and languages, including iOS, Android, Java, C#, ActionScript and others. Unfortunately, its PDF417 support is listed as “alpha quality” and the iOS port only supports QR codes (another matrix format). The iOS port hasn’t been updated in about 3 years, so ZXing is out.

Manatee Works is a closed-source library that claims to be smaller and more efficient than ZXing. It supports Windows, Android and iOS, and supports all of the bar codes I’m interested in. Looks good! How much does it cost? And can I see some code so I get an idea of how well the API was put together? No. No I can’t.

Manatee Works appears to be one of those companies that believes ardently that keeping its prices secret will encourage people to contact its sales team so they can presumably engage in the hard sell. Either that, or it’s like one of those restaurants that don’t put prices on the menus because, if you have to ask how much it costs, you probably can’t afford it. In any case, their product does indeed cost more than I’d pay.

Lastly, there’s SD-Toolkit. Again, they support multiple platforms: Windows, Android, iOS, Mac OSX and Linux, and again it supports all of the barcodes I need. Their relatively reasonable prices are published on their website and they even provide a trial SDK. My only issue with SD-Toolkit was that their trial SDK doesn’t yet support the armv7s architecture used in the iPhone 5.

If you’re trying to include a barcode reader in your iOS app, these are your options:

  • An extremely limited open-source effort that’s been abandoned;
  • An extremely expensive closed-source library that you can’t see until you talk to the sales team;
  • A more reasonably-priced closed-source library, with a trial SDK, that’s a little behind the times.

At this point I came up with an alternative solution: don’t try to read barcodes on an iOS device at all. Instead:

  • Grab a photo of the barcode using the phone;
  • Rotate, scale, crop and compress the image down to ~40K;
  • Post the image to a web server running a barcode reader SDK behind a RESTful web service;
  • Perform all of the barcode parsing on the web server;
  • Return the parsed data as JSON objects to the iOS device.

The downsides to this are obvious. Barcode parsing will only work on an iOS device that has an internet connection, and parsing times will include data transmission time. However, the advantages are compelling. The average web server is so much faster than an iPhone that the time taken to transmit in both directions and decode via a web service appears to be no longer than the time to parse directly on an iPhone. ZXing becomes an option again if you’re a Java shop. If you can’t use ZXing, you should be able to find an SDK for your prefered language for - at most - the cost of Manatee Works’ library, but it will work on all devices. Yep, a web service will work with Android, iOS, Windows, Linux, BSD or anything else with an internet connection.

That’s the option I’ve plumped for. It’s working nicely so far. The most troublesome part was rotating, scaling and cropping the images from the iOS camera correctly on all iOS devices.

One final note: Don’t bother trying to read barcodes on an iOS device that doesn’t have an autofocusing camera. It just doesn’t work reliably. Stick to the iPhone 3GS+, iPod Touch 5g+ or the iPad 3+.

2012-08-27

Apple Vs Samsung

Apple describe iOS as having an “Elegant and intuitive interface” that “You know how to use…from the first time you pick it up.”

It’s intuitive because it’s obvious. Tap/pinch to zoom, rotate gesture, drag to scroll: all obvious gestures. Non-obvious gestures (place phone on the ground and do a handstand to zoom) didn’t make the cut.

Now answer this: if the gestures are successful because they are obvious to a lay user from the first time he picks it up, how can they be patented if the main criteria for these patents is that they are non-obvious to a professional user interface designer?

2012-05-01

Email Address Validation For iOS

If you’ve ever needed to validate an email address, the chances are you used a regex engine and validated the address’ structure against something like this:

^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$

It works well enough for the average case. But what about this email address?

joe+bloggs@example.com

Not so good. You could modify the regex to support the “+” character, but if you read RFC 822 and RFC 5321 you’ll quickly discover that the structure of an email address isn’t just “alphanumeric@alphanumeric.alphanumeric”. Email addresses include a built-in comment system using parentheses, escape characters, quoted sections, “dot-atoms” and more. A regex to correctly parse the entirety of RFC 822 would be huge, and that’s the older, simpler version of an email address.

Once you start really digging into the syntax of an email address, you find that:

  • Domain names (the part after the “@”) can now be in character sets other than Latin, so your primitive [a-z][0-9] regex won’t work;
  • The local part of the address (the part before the “@”) can also now be in other character sets, but as this idea is about 2 months old nothing supports it yet;
  • Trying to figure out if Exchange supports internationalised domain names (IDN) or not is basically guesswork;
  • There are multiple standards for email addresses, most of which disagree with each other and most of which appear to be deprecated. Figuring out which is the current standard is like trying to find a particular Skittle in a bag of Skittles.

Pretty much the only fact you’ll be entirely sure of after your research is that regex is a really bad solution for validating email addresses.

My favourite part of RFC 3696 is where it says these addresses are valid:

Fred\ Bloggs@example.com
"Fred Bloggs"@example.com

This makes sense. The backslash is used to escape a single character, whereas the quotes are used to signify that everything between them should be automatically escaped. One’s really a shortcut for the other. Simple.

However, the errata suggests that this is a mistake, and that this is the correct form:

 "Fred\ Bloggs"@example.com

Now we have a backslash system embedded within a quoting system. The former makes the latter entirely superfluous. I think this amendment is probably why so many developers just stick with regex: The standard is crap.

So, you have two choices:

  1. Ignore the complexity, do what everyone else does, and use regex regardless;
  2. Write an email address parser.

As I’m crazy, here’s an email address parser written in Objective-C. It has some limitations:

  • It does not allow the local part of the address to be internationalised as nothing supports this yet.
  • It does not allow domain name literals (ie. joe@[10.0.1.2]) as Exchange does not support them and I’ve never seen one in the wild.
  • It may not apply all of the backslash escape rules correctly, as this is one area where every document entirely disagrees with every other document.
  • It may not enforce the omission of certain ASCII ranges correctly, as this is another area where the docs suck.

On the plus side:

  • It validates comments and nested comments (comments are apparently deprecated, but I can’t find anywhere that specifically says that this is the case).
  • It correctly enforces at least some of the backslash escape rules.
  • It allows the use of quoted sections (that can include characters like “@”, “(”, etc) and enforces their rules (quotes must be matched; quoted sections must be the only element of the local part or must be delimited by periods).
  • It enforces period rules (periods cannot be the first or last part of the local or domain parts of the address; periods cannot be adjacent except within a quoted section).
  • It enforces the maximum and minimum lengths of each section (up to 64 chars for the local part; up to 254 chars for the entire address; there must be a local and domain part of the address).
  • It allows IDN.
  • Domain names can only contain letters, numbers, periods and hyphens.
  • It passes all of the test cases written by Dominic Sayers except those concerning carriage/line returns and domain name literals, which aren’t relevant to what I’m trying to do.

// .h
@interface SZEmailValidator : NSObject

+ (BOOL)isValid:(NSString *)candidate;

@end

// .m
#import "SZEmailValidator.h"

struct SZEmailParserState {
    BOOL quoted : 1;
    BOOL escaped : 1;
    BOOL domain : 1;
    BOOL dot : 1;
    BOOL followingQuoteBlock : 1;
};

@implementation SZEmailValidator

+ (BOOL)isValid:(NSString *)candidate {

    unsigned int domainPartStart = 0;
    unsigned int commentDepth = 0;
    
    struct SZEmailParserState state;
    
    state.dot = NO;
    state.quoted = NO;
    state.escaped = NO;
    state.followingQuoteBlock = NO;
    state.domain = NO;
    
    for (unsigned int i = 0; i < candidate.length; ++i) {
        unichar character = [candidate characterAtIndex:i];
                
        if (!state.domain) {
            
            // Do not allow characters beyond the ASCII set in the username
            if (character > 126) return NO;
             
            // Do not allow NULL
            if (character == 0) return NO;
            
            // Do not allow LF
            if (character == 10) return NO;
        }
        
        if (i > 253) {
            
            // Do not allow more than 254 characters in the entire address
            return NO;
        }
        
        // The only characters that can follow a quote block are @ and period.
        if (state.followingQuoteBlock) {
            if (character != '@' && character != '.') {
                return NO;
            }
            
            state.followingQuoteBlock = NO;
        }
        
        switch (character) {
            case '@':
                
                if (state.domain) {
                    
                    // @ not allowed in the domain portion of the address
                    return NO;
                    
                } else if (state.quoted) {
                    
                    // Ignore @ signs when quoted
                    
                } else if (state.dot) {
                    
                    // Dots are not allowed as the final character in the local
                    // part
                    return NO;
                    
                } else {
                    
                    // Swapping to the domain portion of the address
                    state.domain = YES;
                    domainPartStart = i + 1;
                    
                    if (i > 64) {
                        
                        // Do not allow more than 63 characters in the local part
                        return NO;
                        
                    }
                }
                
                // No longer in dot/escape mode
                state.dot = NO;
                state.escaped = NO;
                
                break;
                
            case '(':
                
                // Comments only activate when not quoted or escaped
                if (!state.quoted && !state.escaped) {
                    ++commentDepth;
                }
                
                break;
                
            case ')':

                // Comments only activate when not quoted or escaped
                if (!state.quoted && !state.escaped) {
                    
                    if (commentDepth == 0) return NO;
                    
                    --commentDepth;
                }
                
                break;
                
            case '\\':
                
                if (!state.quoted && commentDepth == 0) {
                    
                    // Backslash isn't allowed outside of quote/comment mode
                    return NO;
                }
                    
                // Flip the escape bit to enter/exit escape mode
                state.escaped = !state.escaped;
                
                // No longer in dot mode
                state.dot = NO;
                
                break;
            
            case '"':
                
                if (state.domain && commentDepth == 0) {
                    
                    // quote not allowed in the domain portion of the address
                    // outside of a comment
                    return NO;
                }
                
                if (!state.escaped) {
                    
                    // Quotes are only allowed at the start of the local part,
                    // after a dot or to close an existing quote part
                    if (i == 0 || state.dot || state.quoted) {
                        
                        // Remember that we just left a quote block
                        if (state.quoted) {
                            state.followingQuoteBlock = YES;
                        }
                    
                        // Flip the quote bit to enter/exit quote mode
                        state.quoted = !state.quoted;
                    } else {
                        return NO;
                    }
                }
                
                // No longer in dot/escape mode
                state.dot = NO;
                state.escaped = NO;
                
                break;
            
            case '.':
    
                if (i == 0) {
                    
                    // Dots are not allowed as the first character of the local
                    // part
                    return NO;
                    
                } else if (i == domainPartStart) {
                    
                    // Dots are not allowed as the first character of the domain
                    // part
                    return NO;
                    
                } else if (i == candidate.length - 1) {
                    
                    // Dots are not allowed as the last character of the domain
                    // part
                    return NO;
                }
                
                if (!state.quoted) {
                    
                    if (state.dot) {
            
                        // Cannot allow adjacent dots
                        return NO;
                    } else {
                        
                        // Entering dot mode
                        state.dot = YES;
                    }
                    
                }
                    
                // No longer in escape mode
                state.escaped = NO;

                break;

            case ' ':
            case ',':
            case '[':
            case ']':
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
            case 11:
            case 13:
            case 15:

                // These characters can only appear when quoted
                if (!state.quoted) {
                    return NO;
                }
                
            default:
                
                // No longer in dot/escape mode
                state.dot = NO;
                state.escaped = NO;

                // Do not allow characters outside of unicode, numerals, hyphens
                // and periods in the domain part.  We use letterCharacterSet
                // because we're supporting internationalised domain names.
                // We don't have to do anything special with the name; that's up
                // to the email client/server to handle.
                if (state.domain) {
                    if (![[NSCharacterSet letterCharacterSet] characterIsMember:character] &&
                        ![[NSCharacterSet decimalDigitCharacterSet] characterIsMember:character] &&
                        character != '-') {
                        
                        return NO;
                    }
                }
                
                break;
        }
    }
    
    // Do not allow unclosed comments
    if (commentDepth > 0) return NO;
    
    // If we didn't identify a local and a domain part the address isn't valid
    if (!state.domain) return NO;
    if (candidate.length == domainPartStart) return NO;
    if (domainPartStart == 1) return NO;
    
    // Validate domain name components
    NSArray *components = [[candidate substringFromIndex:domainPartStart] componentsSeparatedByString:@"."];
    
    for (NSString *item in components) {
        
        // We can't allow a hyphen as the first or last char in a domain name
        // component
        if ([item characterAtIndex:0] == '-' || [item characterAtIndex:item.length - 1] == '-') {
            return NO;
        }
        
        // Items must not be longer than 63 chars
        if (item.length > 63) return NO;
    }

    return YES;
}

@end

2011-11-29

Enterprise iOS Apps

Recently I’ve been looking into the potential of iOS devices in the enterprise, which is surprisingly zeitgeisty. Computer users in enterprises typically have to deal with laptops that take 10 minutes to boot because they’re old, running Windows XP and laden with crufty enterprise junk. Users can’t install anything because they don’t have admin rights, and the IT department doesn’t want to spend its time uninstalling the Bing toolbar and Bonzi Buddy from the computers of techno-illiterates who, like magpies, are attracted by anything that blinks, flashes, glows or moves. They have to deal with IE6 and a virus killer that saps their CPU time and their will to live in equal measure.

A few people within enterprises have bought iPads or iPhones and suddenly find themselves with a computer that doesn’t have IE, doesn’t have viruses, allows them to install whatever they want and is always ready to use. They’ve realised that they can do most of their work with nothing more than an iPad and a Bluetooth keyboard. If only there was “an enterprise app for that” they could dump their bottom-of-the-range Dells with their oversized bag and carry around their iPads instead; hence the need for enterprise iOS applications.

The average enterprise application consists of:

  • An enormous, badly-designed database written by guys who didn’t know how to make databases but who once met the brother of a friend of an acquaintance who’d seen a SQL query a few years ago, didn’t really understand it, but thought it was pretty neat;
  • A vile, enterprisey “business logic” layer, with interfaces that describe factory classes that produce factories that produce classes that aren’t used anywhere in the codebase, because all of that clever planning and abstraction was a counterproductive waste of time;
  • A dumb web UI that allows CRUD operations on the database, cunningly designed to be 100% compatible with Internet Explorer 5 and 6 and utterly unusable in anything else, possibly even implemented as an ActiveX control masquerading as a website for marketing purposes.

Enterprise iOS apps will typically replace this last tier of the application stack. At the very least, apps need to be able to interact with the business logic layer to perform CRUD operations.

If you’ve been smart when putting together your existing systems, you’ll have followed Steve Yegge’s advice and built everything as a service. Want to know which user has the network ID “elb”? Point your web browser at your RESTful HR web service and query /employees/elb. The user’s details will be output in easy-to-parse JSON format.

In that case, you can give yourself a congratulatory pat on the back and go grab copies of ASIHTTPRequest and JSONKit. All of your systems are already set up in such a way that you can interact with them from pretty much any device that supports HTTP. It’s even easier if they can perform asynchronous requests and parse JSON.

If you haven’t built a service-oriented architecture, or were misguided enough to use WCF and SOAP - perhaps because you wanted to add a piquant dash of vendor lock-in to an architecture designed for heterogeneity - you could do far worse than take a look at Nancy. This is a micro web framework for C# based on Ruby’s Sinatra that will allow you to create RESTful web services, and indeed full websites, with astonishing ease.

The next issue you’ll have is security. What you really don’t want to do is expose your databases to the internet, particularly if those databases contain sensitive data such as employee details, client data, etc. If your iPhone needs to interact with a web service behind a firewall, what can you do?

You have 3 options:

  • Ignore the security issues and expose your web services to the internet;
  • Only allow the app to work when the iPhone is connected to the corporate network;
  • Connect the iPhone to the corporate network via a VPN.

I like to think of the first option as “the Sony approach”. If you don’t particularly value the privacy of your employees or your clients, don’t mind months of downtime when you try to shoehorn security features into a live system and aren’t embarrassed by publically demonstrating your astounding ineptness, this is a very worthwhile choice. On the other hand, you might recognise it as a security disaster waiting to happen.

The second option introduces some complexities. The whole point of a mobile app is that it’s, well, mobile. A mobile app that can only be used at a single location is obviously not amazingly useful. One possible workaround would be to cache relevant chunks of the database’s data on the iPhone. All CRUD would be performed on the cache. Any changes would get replicated to the master database when the device next connected to the corporate network. Obvious downsides are the need to deal with conflicting edits, either by automatic/manual merging or by asking the user if he wants to overwrite the remotely-edited version with his own changes.

It’s a tidy solution, but it introduces another security risk - your sensitive data is now stored on a highly desirable and easily mislaid phone. You could consider encrypting data stored on the iPhone. More extreme possibilities for securing the data include requiring a username and password to start the app (which would be a massive detriment to usability) or setting up the device in Exchange so that it can be remotely wiped if lost.

The third option is the best tradeoff between usefulness and security. VPN traffic will be encrypted, there’s no need to punch holes in the corporate firewall, and the VPN can be accessed from any location with internet connectivity. The iPhone has an excellent built-in VPN client that appears to offer identical functionality to the client in OSX. This solution obviously relies on the company having an existing VPN infrastructure or being open to the idea of implementing one.

The downside is that a lost device now has access to not only any enterprise apps but the VPN too. The risk associated with this can be mitigated by using a username/password combo to authenticate with the VPN instead of using a certificate. Certificate-based authentication happens automatically, whereas username/password-based authentication requires the user to type in his password every time he tries to connect. This might be the most appropriate approach to adopt anyway, as corporations are fond of implementing expiry policies that would necessitate the creation of new VPN certificates every few months. However, the user would need to manually sign on to the VPN each time they used the app. Worse, the iPhone has a habit of dropping VPN connections whenever it feels like it. How often do you want your users to enter their passwords?

The best approach is probably a mixture of the above:

  • Encrypt all local data
  • Set up all iOS devices in Exchange so that they can be remotely wiped
  • Allow read-only access offline by caching data on the device
  • Require a VPN connection for editing data

The VPN can authenticate via certificate for ease of use or via username/password for heightened security/to appease the password policy patrol.

Our enterprise iOS app architecture now looks like this:

  • MSSQL database used as storage
  • .NET-based business logic layer
  • RESTful Nancy web service exposing CRUD operations of business logic layer
  • VPN connection for encrypted, authenticated communication with the web service
  • iOS UI app that caches data locally for offline browsing and allows the user to interact with the web service via ASIHTTPRequest and JSONKit

Vaguely related to this, I’ve added a simple website/web service for distributing enterprise iOS apps to my BitBucket page:

It presents a list of iOS apps in an iOS-friendly website and allows them to be downloaded straight to the device. It can also be used by apps to determine the latest version number should they want to update themselves automatically.

2011-10-13

Storyboards in the iOS5 SDK

Compared with the old nib style of UI creation, the new storyboard system is awesome. Suddenly the whole UI system makes sense and I’ve been able to replace some nasty bodges in a little program I’ve been working on.

One more time: storyboards are awesome.