Navigation
Wednesday
Aug212013

Work can be frustrating sometimes

Wednesday
Oct242012

Location-aware passcode settings

Are you on the fence what the passcode setting should be on your iOS device? Are you worried that anyone could access all your data if you left it somewhere yet at the same time annoyed by having to type in your passcode all the time?

I know I am. I use my phone so frequently that a five minute passcode setting actually rarely kicks in. My iPad on the other hand I don’t pick up as often and therefore I would have to enter the passcode pretty much every time. Which is why I don’t use one.

This came back to bite me a few weeks ago when I left my iPad on the cross-trainer in the gym. When I noticed my iPad was missing I wished I had at least set a passcode to protect its contents. Thinking about why I hadn’t, it occurred to me how I could have had the best of both worlds: no passcode while at home or at other places I consider “safe” and a passcode everywhere else.

iOS should simply allow you to define location specific passcode settings. You should be able to define a default which applies everywhere and then one or more regions that are exceptions. This would allow you to define a basic safe setting with a passcode and then other areas where you’re more liberal. You could even have passcode settings of different complexity depending on where you are. No passcode at your home, a complex one abroad, and a simple one everywhere else.

Below you‘ll find an illustration of what the setup for this could look like.

Sunday
Aug122012

Unit testing asynchronous code

Grand Central Dispatch and blocks have made it very easy to send blocks of code off to the background for execution and since it is so much easier, asynchronous code is much more common.

All this is a blessing – except when it comes to unit testing. To test the result of an asynchronous task, you need to force it back to “synchronicity”, so to speak. Unfortunately, Xcode’s built-in testing framework SenTestingKit does not provide any help in this regard. All its test macros like STAssertEquals assume values to be returned synchronously, leaving it up to you to provide them from asynchronous tasks.

GHUnit, on the other hand, does have a mechanism to test asynchronous tasks. However, its setup is a little more complicated than simply adding the built-in SenTestingKit. Unless you need GHUnit for some of its other features, STK is therefore usually the best way to get started with unit testing in Xcode.

To allow for asynchronous testing with SenTestKit, I’ve added a category to SenTestCase that is based on GHUnit’s asynchronous test. It is available as part of a sample project on github.

A test of an asynchronous task using the added method waitWithTimeout:forSuccessInBlock: looks like this:

- (void)test_completion
{
  Downloader *dl = [[Downloader alloc] init];
  
  __block BOOL received = NO;
  [dl startDownloadWithCompletion:^{
    received = YES;
  }];
  
  [self waitWithTimeout:1.1 forSuccessInBlock:^BOOL{
    return received;
  }];
  STAssertTrue(received, nil);
}

This code tests the completion handler of an asynchronous call of a Downloader class (see the example project for details) by waiting for a block to return YES within a given time limit.

What I prefer about this category over the one in GHUnit is that the test is fully contained within the body of the test method. There is no need to implement any other callback method and reference it from this test. This makes copy and paste of tests for re-use much simpler, for example.

Tuesday
Jun052012

Safari: Keyboard shortcut for opening current page in Google Chrome

Currently shipping Macs come without Adobe Flash preinstalled and I’ve been running that same setup without Flash for quite a while now myself. More and more webpages work fine without Flash and only the occasional video requires it. When that is the case, I simply go to the ‘Develop’ menu (enable it in the ‘Advanced’ section of Safari’s preferences if you don’t have it) and select ‘Open Page With’ ➡ ‘Google Chrome.app (20.0.1132.21)’. Since Google Chrome ships with integrated Flash, this is a simple way to switch to a Flash-enabled browser.

Now, rather than having to choose Chrome from the menu it would be nice to be able to assign a keyboard shortcut for this menu item. This is actually quite simple: Open the keyboard preference pane in System Preferences, select ‘Application Shortcuts’ and add a shortcut for the ‘Google Chrome.app (20.0.1132.21)’ menu item to Safari. However, the problem here is that the menu item contains the version number of Chrome and since Chrome updates frequently (and in the background), you’ll find yourself with a broken shortcut very soon.

The fix for this is a little Apple Script OpenURLInNewChromeWindow.app by Mike Hardy which basically tells Google Chrome to open the URL via an Apple Script command. If you run this script once, it will register itself as a application that can handle URLs and will therefore also appear in the list of browsers under ‘Open Page With’. Opening a page with this script will open the current page in Chrome just like before but the point is that the menu command will stay the same no matter what version of Chrome you have installed. Therefore you simply assign the shortcut to this ‘browser’ instead of the ever changing Chrome one.

An added benefit (and actually the reason Mike Hardy wrote the script in the first place) is that the page opens in a new window and not in a new tab (which can be quite annoying when using virtual screens). See Mike’s blog post on more details how to use his script in that context.

Monday
May142012

Peer to peer synching with TouchDB

Updated 2012-06-05: Incorporated Jens’s suggestions and corrections.

TouchDB is a lean CouchDB-compatible database framework that can be embedded in iOS applications (or more generally, mobile or desktop applictions but this post is about iOS). Jens Alfke, its author, describes it this way: “If CouchDB is MySQL, then TouchDB is SQLite.” The project is available on github.

TouchDB is CouchDB-compatible with respect to its replication API when initiated on the device against another ‘regular’ CouchDB. You can create push and pull replication tasks on TouchDB. However, out of the box, TouchDB does not offer an HTTP interface for other TouchDB (or CouchDB) instances to connect to. This means that initially, you are limited to a “star” topology with a regular CouchDB at its center and iOS devices with TouchDB connecting to it as a synchronization hub.

However, with a little extra work, it is quite easy to turn this into a peer to peer setup, thanks to the Listener framework Jens has included in TouchDB.

In order to get this to work, you first need to build the listener framework. To do so, clone the git repository, pull the submodules and build the “Listener iOS Framework” target as follows:

git clone https://github.com/couchbaselabs/TouchDB-iOS
cd TouchDB-iOS
git submodule init
git submodule update
xcodebuild -target "Listener iOS Framework"
open build/Release-ios-universal

The open command will open a Finder window with the framework, which you need to add to your existing project.

After you have done that, you need to start the listener. One place where you might want to do that could be application:didFinishLaunchingWithOptions:. Add the following code to start the listener:

CouchTouchDBServer *server = [CouchTouchDBServer sharedInstance];
[server tellTDServer:^(TDServer *tdServer) {
  NSLog(@"Starting listener");
  _listener = [[TDListener alloc] initWithTDServer:tdServer port:59840];
  [_listener start];
}];

NB: Make sure _listener is retained outside the block and lives on, otherwise your listener goes out of scope and stops listening immediately. And as you can tell from the unbalanced alloc message: these code snippets are assuming ARC.

This is basically all you need to do to connect to your TouchDB instance via HTTP. For example, you could use curl to query it for documents. However, peer to peer benefits from advertising and discovering your service via Bonjour and the rest of this article briefly describes how to achieve this.

First off the advertising part. Add the following to a startup section of your application, for example right after creating the listener:

UIDevice *device = [UIDevice currentDevice];
self.netService = [[NSNetService alloc] initWithDomain:@"local" type:@"_myapp._tcp" name:device.name port:59840];
NSData *data = [NSNetService dataFromTXTRecordDictionary:[NSDictionary dictionaryWithObject:conf.localDbname forKey:@"path"]];
[self.netService setTXTRecordData:data];
[self.netService publish];

Replace “myapp” and 59840 with values of your choosing and note that it is advisable to choose a better service name than simply the device name as I have done in this example.

For discovery, you create an NSNetServiceBrowser and search for hosts of your service type:

self.browser = [[NSNetServiceBrowser alloc] init];
self.browser.delegate = self;
[self.browser searchForServicesOfType:@"_myapp._tcp" inDomain:@"local"];

You will be notified of any matches by implementing the following NSNetServiceBrowserDelegate protocol callback:

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didFindService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing
{
  [self.services addObject:service];
  if (! moreServiceComing) {
    [self.tableView reloadData];
  }
}

In this example, I’ve added the service to an array. This could be an array that is driving a UITableView for example. (There’s a complete bonjour browser example available on the iOS Dev Center that includes a browsing UI and discovery and resolution for bonjour services that these code examples are based on.)

As Jens Alfke correctly points out in the comments, it is important to implement the companion method netServiceBrowser:didRemoveService:moreComing: as well in order to remove a service from the list when it disappears:

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didRemoveService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing
{
  [self.service removeObject:service];
  if (! moreServiceComing) {
    [self.tableView reloadData];
  }
}

Once a service is selected in this table view, we try to resolve it:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  NSNetService *service = [self.services objectAtIndex:indexPath.row];
  [service setDelegate:self];
  [service resolveWithTimeout:0.0];
}

Finally, we implement the relevant part of the NSNetServiceDelegate protocol to handle the resolved address. This is where we would then update the sync settings for our app, which is encapsulated in the [self updateSync:url] in this example. This would be the same updateSync: present in the TouchDB example apps.

- (void)netServiceDidResolveAddress:(NSNetService *)sender {
  // Construct the URL including the port number
  // Also use the path, username and password fields that can be in the TXT record
  NSDictionary* dict = [NSNetService dictionaryFromTXTRecordData:[service TXTRecordData]];
  NSString *host = [service hostName];
  NSString* user = [self copyStringFromTXTDict:dict which:@"u"];
  NSString* pass = [self copyStringFromTXTDict:dict which:@"p"];
  NSString* portStr = @"";
	
  // Note that [NSNetService port:] returns an NSInteger in host byte order
  NSInteger port = [service port];
  if (port != 0 && port != 80) {
    portStr = [[NSString alloc] initWithFormat:@":%d",port];
  }

  NSString* path = [self copyStringFromTXTDict:dict which:@"path"];
  if (!path || [path length]==0) {
    path = [[NSString alloc] initWithString:@"/"];
  } else if (![[path substringToIndex:1] isEqual:@"/"]) {
    NSString *tempPath = [[NSString alloc] initWithFormat:@"/%@",path];
    path = tempPath;
  }
	
  NSString *ipAddress = nil;
  for (NSData* data in [service addresses]) {
    char addressBuffer[100];
    struct sockaddr_in* socketAddress = (struct sockaddr_in*) [data bytes];
    int sockFamily = socketAddress->sin_family;
    if (sockFamily == AF_INET /* || sockFamily == AF_INET6 */) {
      const char* addressStr = inet_ntop(sockFamily,
                                         &(socketAddress->sin_addr), addressBuffer,
                                         sizeof(addressBuffer));
      int port = ntohs(socketAddress->sin_port);
      if (addressStr && port) {
        NSLog(@"Found service at %s:%d", addressStr, port);
        ipAddress = [NSString stringWithCString:addressStr encoding:NSASCIIStringEncoding];
      }
    }
  }

  NSString* url = [[NSString alloc] initWithFormat:@"http://%@%@%@%@%@%@%@",
                   user?user:@"",
                   pass?@":":@"",
                   pass?pass:@"",
                   (user||pass)?@"@":@"",
                   ipAddress?ipAddress:host,
                   portStr,
                   path];
	
  NSLog(@"service: %@", service);
  NSLog(@"url: %@", url);
  [self updateSyncURL:url];
}

The method above references one simple helper method to access bonjour data from the serivce:

- (NSString *)copyStringFromTXTDict:(NSDictionary *)dict which:(NSString*)which {
  // Helper for getting information from the TXT data
  NSData* data = [dict objectForKey:which];
  NSString *resultString = nil;
  if (data) {
    resultString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  }
  return resultString;
}

As mentioned above, this bonjour code is mostly from the Apple example code of BonjourWeb but it required some minor changes. I’ve added the path component to broadcast which database to replicate with. I’ve also commented out the AF_INET6 socket family part, because it did not work with the replication and for the same reason I’m using the IP address for the URL rather than the clear name, because this also did not yield a working connection.

Hopefully this post will help people getting started with TouchDB peer-to-peer replication!