Monday, June 29, 2020

SwiftPM and Resources

Last year I wrote about the sad state of dependency management for iOS projects. Later this year Xcode 12 will be released and it will address a couple of the big limitations of the Swift Package Manager (SwiftPM). I won't go into much detail about how they work, but you can watch a couple of great (and short!) WWDC videos about Resources and Localization and Binary Frameworks.

Last week I spent some time with the Xcode 12 beta trying the new support for resources, and even had a few great lab appointments with Apple engineers. Here are some observations. 

Resources

Xcode does a good job of bundling resources found among your sources, if it knows what to do with them. It automatically processes things like Xibs and asset catalogs. For things that it doesn't know about, you can tell it what to do in a list of resources for each target in Package.swift. See the aforementioned video for details. 

Bundle.module

If you are converting an existing framework that contains resources to SwiftPM, you'll have to make some adjustments. In a framework that is built from a traditional Xcode project, it is common to load resources such as images and Xibs using Bundle(for: AnyClass). This initializer does not work in a Swift package. Instead, Swift package code should use Bundle.module. While this is new with Xcode 12, thankfully it is backward deployable and can by used by iOS versions prior to  iOS 14. 

There is a catch though. Bundle.module is only available when building a Swift package. So, an xcodeproj framework can't use Bundle.module and has to continue to use Bundle(for: AnyClass), but a Swift package can't use Bundle(for: AnyClass) and has to use Bundle.module. This is a problem for many framework vendors that will need to support Carthage and SwiftPM concurrently for some time. It is also a problem for internal frameworks as we work toward SwiftPM, but will continue to build frameworks as Xcode projects until Xcode 12 ships. One solution is to migrate existing code to Bundle.module, and then add something like this to your project: 

#if !SWIFT_PACKAGE
extension Bundle {
    class BundleClass { }
    static let module = Bundle(for: BundleClass.self)
}
#endif

This provides an alternate implementation of Bundle.module when not building with SwiftPM. Perhaps this will be resolved before Xcode 12 ships (FB7792343 for Apple folks). 

Interface Builder

Although Xibs are now being properly copied into the target bundle, I noticed they weren't being loaded properly at runtime. Eventually I determined that I have to uncheck "Inherit Module From Target" for any custom views that are used. 
In order to make sure I caught all instances of this, I edited a lot of Xibs in a text editor. I suspect the need to uncheck "Inherit Module From Target" is a bug (FB7793693). Hopefully it will be fixed eventually. 

Testing... 

The trouble with both the Bundle(for: AnyClass) change and the Interface Builder custom class issue, is that these problems are not caught at compile time. Both result in runtime bugs or crashes. Also, these types of problems are often not covered by unit tests. If you are converting an existing framework with resources to SwiftPM, you'll need to carefully audit the project and test thoroughly. 

A Light at the End of the Tunnel

Many framework vendors already support SwiftPM. Hopefully the new support for resources and binary frameworks will enabled the rest to adopt SwiftPM soon. I look forward to fully embracing SwiftPM later this year. 


Thursday, May 21, 2020

Home Office

Spring of 2020 was defined by the COVID-19 pandemic. My job, like many others, switched to have everyone work from home for several months. I feel very fortunate to have a nice dedicated home office to work from.

Coincidentally, I began upgrading my office in December 2019 by ordering a new desk and some other items. As a result, by the time we started working from home in March the office was in good shape.

The office is a 10x11 foot space with two desks.

My desk is the Uplift 60" with curved bamboo top. It is quite stable and I find the beveled curve to be very comfortable.

The monitor is the Dell U2720Q. If Apple ever ships a 5K display I will probably get it. Until then I'll probably live with 4K. The display is really good and I like that it has a foot that is smaller than most displays. This allows me to push my keyboard farther forward and also means the display itself is a more comfortable distance. The display delivers 90W of power over USB-C, but I don't use the power delivery because the display is plugged into a dock.

The dock is the 15-port CalDigit TS3 Plus. It works really well and has the unique advantage of a vertical orientation. That means it takes up very little desk space. As you can see, it currently resides on the monitor's foot. The dock allows me to connect to my gigabit fiber internet connection via ethernet instead of WiFi.

My chair is a HON 7808 "High Back High Performance Executive Chair" that I picked up at an office surplus sale for $20. I've tried many chairs by Herman Miller and others while at various jobs, but none of them are as comfortable as my old chair. John Siracusa would be pleased to know that I removed the arms several years ago. Office chairs are better without arms.

The windows to the front and left of the desk bring in light so that I don't look like I'm in the witness protection program while in Zoom meetings. The window to the left faces north and I enjoy the view of Mount Timpanogos. Here are some photos taken from that window with an iPhone.


I also have a treadmill desk that I built a few years ago. I find that I can operate the computer well while walking at 2mph. Some days I walk 4 miles while working.

When I'm ready for a break I swim laps in the pool.


Sunday, October 27, 2019

Dependency Heck

The state of iOS dependency management in 2019 is not great, and might get worse before it gets better. 


A Brief History

For years we had CocoaPods. Love it or hate it, it was all we had [1]. 2014 brought us Swift and dynamic framework support in iOS 8. Shortly thereafter Carthage was born. It was written in Swift and took advantage of the new dynamic framework support. Carthage has a different philosophy from CocoaPods. CocoaPods modifies the project file to pull the sources from the various dependencies into the build. Carthage prefers more of a hands-off approach which requires a little more from the application developer in exchange for more control and flexibility. Furthermore Carthage separates the building of the dependencies from the building of the consuming project, which allows the option of using pre-built binary frameworks [2]. Also, CocoaPods requires library vendors to provide a “pod spec” to define the contents of the library and the rules to build it. This often means maintaining two sources of truth because an Xcode project file is still desirable for framework development. Carthage uses just the standard Xcode project file. Today most framework projects support both Carthage and CocoaPods, to the credit of the framework authors. There are valid reasons why application developers prefer one over the other. 

Enter the Swift Package Manager

In 2015, many people rejoiced to learn that Apple was working on the Swift Package Manager. It sounds great right? A dependency manager built and supported by Apple themselves. Well, we had to wait nearly four years for SwiftPM support to be added to Xcode, and for it to be usable with iOS projects (instead of just macOS and Linux projects).

But now things are good, right? Not exactly. SwiftPM has a few crucial limitations. 
  • It does not support resources. If your framework includes icons, Xibs/Storyboards, or anything else that isn’t source code, you’re out of luck [3].
  • It does not support mixed source frameworks. While SwiftPM supports several different languages, you can only use one language per package target. For example, you can’t have a target with both Swift and Objective-C code. 
  • There is no support for binary-only dependencies. If you need a binary framework such as Twilio or Mapbox, you’re out of luck. This can be particularly bad if you have modularized your development. For example, perhaps your app has a map feature that was developed as a framework, and the feature uses MapBox. The app doesn’t directly use MapBox but the feature framework declares the dependency on MapBox. 

Will it Work?

At my place of work we have multiple mobile teams and multiple apps. We use a handful of 3rd party frameworks, and many internal frameworks of our own making. We’ve used CocoaPods in the past, but switched to Carthage a couple of years ago. Carthage has problems [4], but it is still probably the better solution for our use case at this time. I wondered how far we could get if we tried to switch to SwiftPM. Some of the features of SwiftPM (such as the ease of switching frameworks in and out of “development mode”), as well as the Xcode integration are very compelling. I knew about some of the aforementioned limitations, but suspected we might be able to workaround some of them. For example, I’m pretty sure we could workaround the resource limitation with some custom tooling and conventions. I started with the leaf nodes of our dependency graph and worked up. Adding `Package.swift` files to the projects was easy enough, and felt good. Before long I ran into the mixed source problem. We have a framework that contains Swift and Objective C++, so that was a no-go. I thought we might be able to split it into multiple frameworks with each one only having source files of only one type. I wasn’t able to get that to work either. This framework worked fine with both CocoaPods and Carthage. 

So What Now? 

We don’t know how long it will take for SwiftPM to be feature-rich enough for most people to use. I don’t even know if that is their goal. My fear though is that the existence of SwiftPM, especially now that it is integrated into Xcode and declared to be some form of “done,” will kill off development of CocoaPods and Carthage [5]. Also, framework vendors, tired of supporting multiple dependency managers, might adopt SwiftPM and drop support for Carthage and CocoaPods. 

What Can You Do? 

If you have the patience for it, you could contribute to SwiftPM. The non-Xcode parts of it are open source, after all. If you use Carthage or CocoaPods, consider contributing to those projects as well. They could use your help right now.  If you are a framework author, please continue to support Carthage and CocoaPods for your framework. Maybe your framework is pure Swift with no resources, so SwiftPM works fine for you. However, many people who would like to use your framework may have other dependencies that can’t support SwiftPM any time soon. The framework vendors that support all of the dependency managers are the heroes we need but don't deserve [6]. You could also file radars (or “feedbacks”) about the limitations of SwiftPM, if you’re into that sort of thing. 



[1] Yes, I know there are other options, such as pulling 3rd party code into your project and managing versions yourself, or refusing to use 3rd party code. 

[2] There are a lot of limitations with pre-built binaries due to the ever-changing Swift ABI. Even the stable ABI introduced with Swift 5.1 comes with a lot of tradeoffs and caveats. It also requires framework vendors to opt-in. See WWDC2019 session 416. 

[3] There is an active proposal to add support for resources. Hopefully it won’t take years to complete. https://forums.swift.org/t/draft-proposal-package-resources/29941

[4] The dependency resolver is quite buggy for moderately complex dependency graphs. There seem to be concurrency bugs related to fetching git repos. Builds can be slow because they are not parallelized, it builds for all architectures though you might not need them, and it builds all frameworks in a repo even though you might only use one (for repos containing multiple frameworks).



Thursday, May 26, 2016

Fixing the Apple TV with Instant Apps

The Apple TV is already a pretty great platform. Hopefully it will become even better this year.

Many Apple TV apps require a companion iOS app. My friend wrote a great trivia game for up to four players. Each player must use an iOS app to play the game. Trivia questions are displayed on the TV and players race to select their answers using the companion iOS app. The need to find and install the companion iOS app in the App Store is a significant source of friction. It is also a nuisance to have a companion app icon cluttering up your home screen when the app is only useful while playing the Apple TV game.

A few months ago I had an idea to improve this situation. My idea was to give developers the ability to write iOS extensions to tvOS apps. These extensions could install and run automatically within the Remote app on an iOS device. Basically, the Remote app would double as a container to run these tvOS app extensions. This would enable the Remote app to morph into whatever it needs to be based on the app that is currently running on the Apple TV. The Remote app could take the form of a game controller, or a second screen when watching video content on the TV.

Last week at Google I/O the concept of Instant Apps was introduced. The idea is that the mobile operating system can quickly and automatically install and run a portion of an app as needed. This can be useful to interact with a parking meter, or load content within an app if someone sends you a deep link. It occurs to me that this is similar to my idea for iOS extensions to tvOS apps, except it is more generic and useful for a lot of situations.

Imagine you launch CrossyRoad on your Apple TV. Then you hold your iPhone next to the Apple TV, and iOS asks if you would like to install and launch the CrossyRoad companion game controller app. This would be much easier than trying to find the app in the iOS App Store.  Ideally the companion app wouldn't even show up on the home screen once installed unless CrossyRoad is currently running on a nearby Apple TV.

We also need a nice communication framework for iOS apps and tvOS apps to interact with each other.

I hope we'll see some innovations like this at WWDC this year. Until then, cut the cord and try my MythTV app.

Sunday, November 15, 2015

MythTV on the New Apple TV

For years I have wished there was an App Store for the Apple TV. Like most people, I looked forward to things like games or streaming video content beyond the limited offerings bundled with previous version of the Apple TV. I also wanted something else: a MythTV Frontend.

MythTV is an open source project that helps you build your own digital video recorder (DVR). MythTV has a client/server architecture, called Frontend/Backend respectively. Ideally the Backend lives in a wiring closet or server room. A Frontend is attached to each television and plays content stored on the backend.

A MythTV Backend is fairly easy to set up with PC or Mac hardware and a networked television tuner such as HDHomeRun. A MythTV Frontend is more challenging to set up. It usually involves finding hardware that supports video decoding and has proper Linux support. You also need an IR receiver and a remote. Configuring the IR commands can be challenging. I used to tell people that MythTV is more of a hobby than a solution.

That all changed with the new Apple TV.

I received my Apple TV developer kit about six weeks prior to the launch of the New Apple TV. I immediately began building a MythTV application for tvOS. The MythTV Backend has a REST API that provides a great way to access content and data. Things went pretty smoothly and the MythTV app for tvOS was in the App Store on launch day.

Thanks to the New Apple TV and the MythTV app for tvOS, it is now much easier to set up a MythTV system. For the backend you can use MythBuntu, a special build of Ubuntu that has MythTV baked in. Or you can set up a MythTV Backend on a Mac using these instructions. They may seem a bit daunting, but are mostly just commands you can cut and paste into Terminal. You'll also need a HDHomeRun. You don't need one of the new models. Mine is a dual tuner unit that is about 10 years old, pictured above. The older models can be found on eBay for around $50. For the Frontend, just use buy a new Apple TV and install the MythTV app. Now you have a TiVo-like DVR system and you can watch your recordings from any TV in your house.


The MythTV app for tvOS currently has the following features:
  • Recorded programs are listed and multiple episodes of the same show are grouped together. 
  • Top Shelf integration: quickly find and launch a show from the Apple TV home screen.
  • Scrubbing with snapshot thumbnails.
  • Fast-forward supports several speeds.
  • Watch content more quickly with 1.3x fast-forward (good for things like talk shows).
  • Easily skip commercials with 30-second jump forward and 8-second jump back.
  • Pick up where you left off. Playback progress is stored and synced across all your Apple TVs.
  • Delete shows when you are done watching them.
One feature that is not yet ready is the Program Guide. Until this is done you can use MythWeb, a web interface that runs on the MythTV Backend. It allows you to browse listing and manage schedules and recording rules. This works out pretty well because once schedules are set up (TiVo calls them "season passes"), the Backend pretty much runs on autopilot and doesn't need much maintenance.

As more content becomes available from streaming sources, "cutting the cable" becomes more attractive. One of the remaining barriers is recording and "time-shifting" local broadcasts. MythTV and the MythTV app for Apple TV fill this gap.

Apple TV apps can't be viewed in a web browser, but you can see screenshots below.







Thursday, June 11, 2015

Guard is Good (Swift)

Several good fixes and improvements to Swift were introduced at WWDC15 as a part of Swift2 -- the first major update to the excellent Swift programming language. One of these updates was the new `guard` keyword. It immediately resonated with me because I saw that it fixed some pain points that I have often encountered.

Pyramid of Doom


Anyone who has used Swift is familiar with the `if let` construct. It is an elegant way to unwrap an Optional, and do something with the unwrapped value if it is not nil. This leads to code that looks like this:

    func personFromData(data:NSData?) -> Person? {
        if let data = data {
            let json = JSON(data:data) // SwiftyJSON
            if let firstName = json["firstName"].string {
                if let lastName = json["lastName"].string {
                    return Person(firstName:firstName, lastName:lastName)
                }
            }

        }
        return nil
    }

Even in this small example, you can what has been referred to as the Pyramid of Doom.

Swift 1.2, released earlier this year, added support for unwrapping multiple Optional values at once with `if let`. This looks a bit better:

    func personFromData(data:NSData?) -> Person? {
        if let data = data {
            let json = JSON(data:data)
            if let firstName = json["firstName"].string
                   lastName = json["lastName"].string 
            {
                return Person(firstName:firstName, lastName:lastName)
            }
        }
        return nil
    }

Note that we couldn't completely collapse the pyramid because we have to convert the NSData to a JSON object, and you can't throw that statement in with the other assignments that are unwrapping Optionals. 

One problem with the examples so far is that there is no error handling. While it's nice to unwrap multiple Optionals at once, sometimes you might need unique error handling for each nil Optional. That means we're back to the Pyramid of Doom, except it's even worse now:
        
    func personFromData(data:NSData?) -> Result<Person, String> {
        if let data = data {
            let json = JSON(data:data)
            if let firstName = json["firstName"].string {
                if let lastName = json["lastName"].string {
                    return Result(value:Person(firstName:firstName, lastName:lastName))
                }
                else {
                    return Result(error:"missing lastName in JSON")
                }
            }
            else {
                return Result(error:"missing firstName in JSON")
            }
        }
        else {
            return Result(error:"where data was nil")
        }
    }

This is really ugly. Even in the small example above you can see that it's hard to associate the error handling code in each else clause with the corresponding `if let` that failed because of a nil Optional. The error handling code for each error condition is in the reverse order of the code that checks for the error conditions. 

Guard all the Things


Guards are kind of like `if let`, but inverted. Instead of using `if let` to unwrap an Optional while testing for nil, use `guard let ... else` to unwrap the Optional. If the Optional is nil, the else clause is executed. You have to return from the else clause. Our example can now be rewritten like this: 


    func personFromData(data:NSData?) -> Result<Person, String {
        guard let theData = data else {
            return Result(error:"data was nil")
        }
        let json = JSON(data:theData)

        guard let firstName = json["firstName"].string else {

            return Result(error:"missing firstName in JSON")
        }
        guard let lastName = json["lastName"].string else {
            return Result(error:"missing lastName in JSON")
        }
        return Result(value:Person(firstName:firstName, lastName:lastName))
    }

This is much better. Observe that the code to deal with errors is next to the error condition that was tested. Also, once all of the error checking is out of the way, the code that continues on for the no-error situation is no longer indented several levels deep. 

Guards can be used to check any condition -- they aren't just tied to Optional unwrapping. For example: 

        guard index <= 3 else {
            return Result(error:"Index out of bounds")
        }

This is not drastically different from what could be done before, however, the nuance is interesting. The alternative looks like this: 

        if index > 3 {
            return Result(error:"Index out of bounds")
        }

The version using `guard` reads better because the expression indicates the success state. It reads more like an assertion. Also, with guard the compiler requires a return in the error clause. A common programming mistake is to log something about the error but forget to return, allowing program execution to continue as though there was not an error. 

Naming Things


In the first code examples above, we used the common convention 
    if let data = data {

With `guard let,` we can no longer do this because the new constant isn't constrained within a new scope. We have to use a different name for the Optional and non-Optional versions of a variable or constant. Naming things is hard, but the benefits of using `guard` are worth it. 

Swift continues to evolve and become even more powerful and enjoyable to use. 



Thursday, January 29, 2015

(ab)Using APNS for Fun and For Profit

When we started building an event sharing social network, we didn't initially realize we were building a chat/instant messenger application. Once we added the ability to have a discussion around an event, we needed the ability to "live-update" the discussion if someone added a comment while you were in the discussion view (think iMessage). We didn't have a web socket or long-poll infrastructure in place at the time, but needed to get something working quickly.

We discovered we could use push notifications to accomplish this functionality. The challenge was to send a push notification that the app could intercept and react to if the app was in the foreground, but if the app was not in the foreground, iOS needed to ignore the push notification (no banner, sound, or badge update). This was before iOS 7 was released, so there was no official support for "silent" push notifications. We discovered we could send a push notification with an empty alert dictionary. Such a push notification didn't produce a banner if the app was not in the foreground.
{ aps: { alert: {} }, ...} 

This tactic continues to work with iOS 7 and iOS 8, and seems to get around Apple rate-limiting. The notifications are always delivered immediately. I suspect this is due to the alert in the push payload. The server doesn't think it is a silent notification, and therefore doesn't rate-limit the notifications. However, since the alert dictionary is empty, there are no visible or audible artifacts when the iOS device receives the push notifications.

Once iOS 7 was available, we used the new content-available setting to solve another problem. All of our logic to determine an accurate home screen icon badge count was in the client-side code. Because of this, the server could not send a badge value in the push notifications. iOS 7 allowed us to pass
content-available:1
which would wake up our app in the background to retrieve events from the server and calculate the badge count. We have some evidence that this does not work reliably. If the device is not charging, the push notification is received but sometimes the app does not get awakened and iOS logs an error:
“Silent Push: Deny app no resources reason: Insufficient power budget 0”

To sum up, even though it appears we can get around the rate limiting with an empty alert dictionary, iOS does not reliably wake the app to do a background fetch.