Bluetooth Printing Support for iPad
I'm a software engineering intern at Square and have been working on Bluetooth printer support for iPad. This feature was released in our latest Square Register build and brings physical receipt printing using Star printers to a mobile customer base. Before this version, Register for iPad supported printed receipts through Star printers connected by WiFi and USB. Adding support for a new type of printer was a great choice for an intern project as it was a combination of building off of an existing hardware integration layer and extending it to support new concurrency requirements. Integrating this new feature into Square’s codebase was a great lesson of how a well-designed code base can make writing new code easier.
Unique Bluetooth Challenges
Implementing Bluetooth printing introduced new limitations in parallel processing and communication that required me to rethink how we manage concurrency in our printing system. If you’re familiar with multi-threaded code, you probably already realize that expensive operations, such as peripheral communication, downloading and uploading, and heavy computation, are best run asynchronously off the main UI thread. In our code for WiFi printing, this is done using the most basic functionality of Grand Central Dispatch (GCD), one of Apple’s built-in mechanisms for managing concurrent tasks. For more detailed info about GCD, read Apple’s documentation . Each print operation and printer discovery operation is added to a global concurrent queue that runs the code on a private thread, abstracting away the mechanics of threading. The result of this is that the different operations are allowed to run in parallel with no restraints and no control.
I started off the implementation of Bluetooth printer support by using the same concurrency model used by our WiFi printer code and running Bluetooth printing and printer discovery blocks on a global concurrent queue. I quickly found what appeared to be a non-deterministic bug—sometimes I could print a receipt with no problem, and other times it would fail part way through. What I was in fact encountering was a timer firing and searching for connected printers while the app was in the middle of sending data to a printer. While this behavior was perfectly acceptable in communication for WiFi printers (TCP supports multiple simultaneous connections), it totally broke down in the new communication channel. It became clear that some kind of mutual exclusion was necessary to prevent this kind of collision.
My first inclination, having just taken a class in C++ using pthreads, was to simply use locks. I created a shared NSLock through a class method and locked before listening and printing, as described in Apple’s Threading Programming Guide. I was excited when it accomplished the mutual exclusion I wanted, but my coworkers were less thrilled about using such a low-level solution given the higher level abstractions available in iOS. In search of a higher-level solution, I replaced the global concurrent queue on which I was running the printer code with a serial dispatch queue, allowing only one block to be run at once. I was getting warmer, but my new solution still raised some concerns.
I kept the static method I’d written for having a single, unique lock and changed it to create a single, unique dispatch queue. The static method, however, introduced a new issue I’d overlooked: upon logging out of the app, the queue would persist while the code blocks it ran tried to access deallocated data. This new issue finally pushed me to use an even higher level concurrency abstraction, NSOperationQueue (see Apple’s documenation for more details). NSOperationQueues are built on top of GCD but provide several extra features, including the ability to cancel operations. Moving printer communication code to an NSOperation subclass was a small amount of extra work that paid big dividends. By storing the operation queue as an instance variable on the print controller, I was able to cancel all waiting printing and printer discovery operations and deallocate the queue itself in just a few lines of code.
I'd like to take a step back from discussing integration challenges to mention a few examples from the Square Register code base that made my life easier for this project. The code separates operations looking for connected peripherals and interacting with them and uses abstract superclasses to provide a consistent API to the rest of the app. Because the code was so robust, my new printer code fit well into the rest of the app, and existing code used by other printers fit well into my own new code. One good example is our receipt image renderer. The renderer works by putting data into an HTML document styled by CSS, rendering that HTML into an unseen web view and then rendering the view into PNG image data that can be sent to the printer. The HTML and CSS scale to fit different-sized windows and thus are a good generalizable solution for multiple print widths. All of the printers that we had previously supported (WiFi and USB printers) printed three-inch wide receipts. However most of the Bluetooth printers I worked with use two-inch wide paper. Thanks to the flexibility of our renderer, all I had to do was set the correct print width of a printer and voilà—the receipts were seamlessly resized and looked great.
The rest of the peripheral library code was also reusable. Admittedly, our deep class hierarchy was rather intimidating when I first came to Square, but it ended up being possibly my greatest resource in writing this feature. At the highest level, Register has a Peripheral class to model every hardware device we support. It includes common properties like manufacturer name, model name, and its connection state. At the next level down is the Printer class, which encapsulates printer-specific information like the kind of data it can print and its print width, as well as abstract methods for printing images and text. Having this infrastructure in place made the design of the Bluetooth printer class clearer, and provided a clean, generic API for the rest of the app to use to print images and text to any printer, leaving the low-level details of the printer communication to the subclasses.
Bluetooth printing was a great project—it brought many unique engineering challenges and taught me a lot about how Square’s code base works. I’ve always been taught to use inheritance when possible to organize and share code, but working with the peripheral class hierarchy was a strong reminder of just how valuable a good, well-organized, object-oriented architecture can be. There was a lot of shared code between WiFi and Bluetooth printers that I could have copied and pasted, but I’m glad that I took the time to not just make it work, but to do it right. I’m thankful that my co-workers pushed me to take the time to experiment with different abstractions of concurrency. Not only is my code better, but I now know many ways to approach the problem. In software, there’s rarely just one solution, and this is a good reminder to explore every possibility before choosing one—because it was the first idea that seemed to work.
Last but not least, though I’m enough of a geek that the code itself would have been interesting and fun for me, it was really special getting to see first-hand how the product truly helped people. Meeting a merchant who had previously only been able to give out handwritten receipts, handing her a Bluetooth printer, and seeing how our new feature would make her job easier was the highlight of my summer.