Visual Tests with Xcode Playgrounds

Recently I discovered a neat way to use Xcode 6 Playgrounds as a kind of unit test for a framework and walks through the problem I solved with it. You can jump directly to the project here if you'd like.

I've been tinkering around with a tiny library to do some math regarding aviation and flight paths. I'm more interested in creating a basic Air Traffic Control simulator app.

As a pilot, it's fun to learn more about the mechanics of aviation traffic.

I ran into an interesting problem when writing code to maneuver a plane between points in a 2-dimensional space.

Say you're flying near San Francisco at 120kts (in aviation, speed is in knots) with a heading of 360° (due North). Air Traffic Control asks you to make a standard rate turn towards the Palo Alto airport.

San Francisco Sectional

Flying a plane is actually pretty easy. To do this maneuver: turn the yoke, push the pedals, and when you're lined up with your destination, level off.

Simple.

But in an iPad app there is only data: speed, heading angle, turn rate, coordinates, time.

Example Vector

I had to dust off my trigonometry and dig into a little bit of aviation math to build a tiny library that made vector, circle, and velocity calculations a bit simpler.

Since these equations are the foundation of my simulation, I wanted to add unit tests so that as I add more features or refactor, the base project is solid.

But doing traditional unit tests in this case meant writing math to prove math. I'd like to actually see this stuff in action before I try to build an app around it.

That's when I remembered you can use layers and views in Xcode 6 Playgrounds. Maybe I could visualize my equations?

I created a new playground in my library called VectorKit.playground to try it out.

For starters, I needed to import my library. Unlike with Swift files, a playground does not automatically import your stuff.

import VectorKit

I created a simple scene with a blank UIView. I had to slim down my units to points instead of nautical miles because playgrounds don't scale to fit.

let bounds = CGRect(x: 0, y: 0, width: 400, height: 200)
let view = UIView(frame: bounds)
view.backgroundColor = UIColor.lightGrayColor()

In order to get the view to show up in the assistant editor, import XCPlayground and call this method:

XCPShowView("view", view)
Playground Setup

I'm going to be adding a lot of dots to the scene, so I created a dot function to generate little red CALayers.

func dot() -> CALayer {
  let layer = CALayer()
  layer.bounds = CGRect(x: 0, y: 0, width: 4, height: 4)
  layer.cornerRadius = 2
  layer.backgroundColor = UIColor.redColor().CGColor
  return layer
}

I needed some data about my aircraft, as well as the coordinates of where I was going to send it:

let bank = 30.0 * M_PI / 180.0
let velocity = 15.0
let initial = (x: 100.0, y: 100.0)
let vector = (x: 300.0, y: 120.0)
let heading = 25.0 * M_PI / 180.0
let left = false

Using the dot function, I can easily see where my plane currently is.

let plane = dot()
plane.position = CGPoint(x: initial.x, y: initial.y)
view.layer.addSublayer(plane)

As well as a dot for my destination.

let vectorLayer = dot()
vectorLayer.position = CGPoint(x: vector.x, y: vector.y)
view.layer.addSublayer(vectorLayer)

To and Fro

Since I'm using XCPShowView(...) the view is updated in real time. Pretty cool!

Now that the scene is set, I can start using my small library's functions.

let r = VectorKit.turnRadius(velocity, bank)
let center = VectorKit.turnCenter(r, heading, left, initial)

I don't really need to use the VectorKit. prefix, but it helps me remember what parts the framework is doing. Plus, I avoid naming collisions (which I screwed up a lot at first).

Time to draw the full circle of the turn:

let circleBounds = CGRect(x: 0, y: 0, width: r * 2, height: r * 2)
let path = UIBezierPath(roundedRect: circleBounds, cornerRadius: circleBounds.size.height / 2.0)
let circle = CAShapeLayer()
circle.bounds = circleBounds
circle.position = CGPoint(x: center.x, y: center.y)
circle.strokeColor = UIColor.redColor().CGColor
circle.fillColor = nil
circle.lineWidth = 0.5
circle.path = path.CGPath
view.layer.addSublayer(circle)

Turn Circle

I can also figure out the final heading towards the destination coordinate using an equation that finds the line that is tangent to a circle and an external point. And of course, display it!

let tangent = VectorKit.tangentHeading(r, left, center, vector)

let line = CALayer()
line.backgroundColor = UIColor.yellowColor().CGColor
line.bounds = CGRect(x: 0, y: 0, width: 800, height: 0.5)
line.position = vectorLayer.position
line.transform = CATransform3DMakeRotation(CGFloat(tangent), 0, 0, 1.0)
view.layer.addSublayer(line)

Last, but not least, get the coordinate where we finish our turn and are pointed at the destination.

let tp = VectorKit.tangentPoint(center, tangent, r, left)

let intersect = dot()
intersect.position = CGPoint(x: tp.x, y: tp.y)
view.layer.addSublayer(intersect)

Finished Vector and Route

Furthermore, I can tweak my variables, like heading and the turn direction, and double check my math in real time.

Change in Direction

What Have We Got?

In the end, I now have my own visual test that my equations work out. If I ever revisit any of the math, or maybe refactor the library, I can quickly look at this playground to see how things look.

If you're into TDD you can use playgrounds too! Imagine building out each step and then writing functions to satisfy them. Pretty cool.

Is this a replacement for traditional unit tests? Hell no. But I'm a visual person, and this lets me see that things line up and my math is correct.

You can check out the playground and math on the Github repo.

A NSURLSessionTask Debug Story

While working on AsyncDisplayKit, we ran into a strange iOS compatibility bug. Our Kittens example project was working just fine for iOS 8, but when we ran the project on iOS 7 it instacrashed.

There was an unrecognized selector being sent to a NSURLSessionTask object. We were using the Objective-C runtime headers to slap on a metadata object for some convenience properties:

@implementation NSURLSessionTask (ASBasicImageDownloader)
static const char *kMetadataKey = NSStringFromClass(ASBasicImageDownloaderMetadata.class).UTF8String;
- (void)setAsyncdisplaykit_metadata:(ASBasicImageDownloaderMetadata *)asyncdisplaykit_metadata
{
  objc_setAssociatedObject(self, kMetadataKey, asyncdisplaykit_metadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (ASBasicImageDownloader *)asyncdisplaykit_metadata
{
  return objc_getAssociatedObject(self, kMetadataKey);
}
@end

Nothing special.

In order to debug the crash, I placed a breakpoint just before the setter that was crashing. I fired up the app on my iPhone 4 running iOS 7.1 and waited for the breakpoint to hit.

As soon as the Debugger window popped up, I used our chisel lldb extensions to inspect the class hierarchy of the object with pclass:

__NSCFLocalDownloadTask
   | __NSCFLocalSessionTask
   |    | __NSCFURLSessionTask
   |    |    | NSObject

That's odd. We are creating a NSURLSessionTask object. That class doesn't even show up in the dump.

Just to be safe, I printed the class hierarchy of the same object when running on iOS 8.1:

__NSCFLocalDownloadTask
   | __NSCFLocalSessionTask
   |    | NSURLSessionTask
   |    |    | NSObject

It appears that the NSURLSessionTask factory method downloadTaskWithURL: has some black-box magic happening on iOS 7.

Since __NSCFLocalDownloadTask doesn't inherit from NSURLSessionTask on iOS 7, that means that our associated object wont exist on our task object.

All we had to do was change the class to NSURLSessionDownloadTask and everything worked just fine. My hunch is that NSURLSessionTask is some sort of class-cluster similar to UIButton.

WatchKit and Threading

While working on some of the chapters for the upcomming WatchKit by Tutorials book, I assumed since WatchKit extensions bascially run as a Remote Procedure Call (RPC), writing asynchronous code wouldn't work. This was a bummer because running dynamic applications on Apple Watch would be very slow.

But I hadn't actually tried it.

At Facebook we have a saying that "Code Wins Arguments", so I decided to prove that something horribly, horribly wrong would happen when you wrote async code in WatchKit.

I wrote a really simple app to test a couple of things:

  • Threading in WatchKit
  • Updating the UI after willActivate: finishes
  • Updating layout after setting contents (like setImage:)

Experiment Storyboard Setup

Inside initWithContext: I download an image and set the contents of the WKInterfaceImage. I also set the contents of the label above the image with text that will wrap, just to push the watch layout system.

[self.label setText:@"Loaded at initWithContext:"];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  NSURL *url = [NSURL URLWithString:@"http://placehold.it/120x100"];
  NSData *data = [NSData dataWithContentsOfURL:url];
  UIImage *placeholder = [UIImage imageWithData:data];

  dispatch_async(dispatch_get_main_queue(), ^{
    [self.image setImage:placeholder];
  });
});

To my surprise, the image downloaded, set the WKInterfaceImage contents, resized based on the image width and height, and pushed the label below the image down!

This worked?

Granted, this is on a simulator so we will have to see what happens when running on a real device and how the Bluetooth connection handles these async callbacks, but this is a great sign!

If you want to play with this project you can check it out on Github.

Sidenote: Yes I'm using Objective-C. Swift introspection sucks and doesn't work at all with Facebook's Chisel LLDB extensions.