If you’ve experienced random hangs, the never ending spinner of death, or simply have no idea why your Apple Watch app isn’t working at random times, this post is for you!

4 Tips To Better WatchKit Development

Jessica Lam
3 min readApr 22, 2015

At LoungeBuddy we spent some time crafting our WatchKit app to perfection and learned a lot along the way. Hopefully these lessons are as helpful to you as they would have been to our past selves.

1. You’re not attaching to the parent process.

If you developed with WatchKit for any meaningful amount of time, you’ve seen this dreaded error message:

Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]"

That’s it. Gone. Just like that, the parent was never heard from again. What a familiar experience, alas I digress. Hey, at least you got a log message!

Chances are the parent app crashed, now if only you had your exception breakpoints on that process, you would’ve known!

Xcode -> Debug -> Attach To Process -> [whatever your app’s process is]

Your process will only show up on the list after the first call to
+ openParentApplication:reply:. Now you’re back to all your familiar debugging environment.

“But wait!”, says the app that does a lot of things, “what if it ACTUALLY takes awhile to do this really complicated task?”

…. besides wondering what can possibly be so complicated and why it’s even that complicated to start with…maybe it’s because….

2. You’re not replying quick enough

Unlike some really bad dating advice, you actually DO want to reply as soon as you get a ping.

In order to do that in - application:handleWatchKitExtensionRequest:reply: you have to do two things:

  1. create a background task
  2. create a way to message the watch extension back without the reply block

The official UIApplicationDelegate documentation (which is amazing and wonderful) will tell you how to make the background task for step one.

Step two’s messaging back is more interesting. I recommend using:

CFNotificationCenterGetDarwinNotifyCenter()

If you would rather not deal with C syntax or if you’re one of those young’uns who’ve never experienced the pointer soupy C — wormhole is a good wrapper to use.

“But what about my beautiful classes?”, says one who cares about not programming in just dictionaries, “I can’t stuff them in there!?”

3. You Forgot about NSCoding

To preserve your Objective-C classes you can use the wonderful NSCoding protocol by implementing initWithCoder: and encodeWithCoder:. Here’s a basic example:

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:@”name”];
self.rating = [aDecoder decodeFloatForKey:@”rating”];
self.bookingId = [aDecoder decodeObjectForKey:@”bookingId”];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name?:@"" forKey:@"name"];
[aCoder encodeFloat:self.rating forKey:@"rating"];
[aCoder encodeObject:self.bookingId?:@"" forKey:@"bookingId"];
}

4. Use the NSURLSessionDownloadTask, Luke

A picture is worth 1000 words but no one is going to wait 1000 seconds. Since there’s not enough space on the watch for those 1000 words anyways, we really should just download some pictures.

This is a worthy endeavor. However, your background task might expire waiting for a resource to download so we want to use NSURLSessionDownloadTask with a splash of AFNetworking convenience.

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:backgroundTaskName];AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];NSURL *URL = [NSURL URLWithString:userInfo[@”data”]];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[manager setDownloadTaskDidFinishDownloadingBlock:^NSURL *(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) {
// do something with the downloaded data at location
return realLocation;
}];NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) { NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {if (error) { // ...do something with the error} else { NSLog(@”File downloaded to: %@”, filePath);
// got the file!
}
}];
[downloadTask resume];

Now you don’t have to worry about caching extra aggressively, background process timing out, or mysterious parent disappearances.

Have fun, make beautiful things and buy some lounge access!

So there you have it! Hopefully this makes your WatchKit development process more enjoyable and predictable. Here’s a demo of the fruits of our labor, let us know about yours too!

Extra Bonus Feature use the credit code watchkit and get $10 off ☺

Disclaimer The code formatting in this post is in no way representative of what me, or my employer, or any sane person’s actual preference. Dear @medium, please fix code block, kthnxbai. ☺

Credits Thanks also to our friends at Hype for making a fantastic tool for our animation needs and Jonathan for editing help.
Also a big thank to Zac, Brent, and Eric for making sure that the cynicism is properly calibrated on this post. Last but not least, thanks to Apple DTS team for awesome documentation and Apple engineers for making yet another wonderful product.

--

--

Jessica Lam

Engineer mind, artist soul, nomad spirit, entrepreneur drive. Serial entrepreneur, technical advisor, multiple exits.