Help find bugs lurking in the Tumblr iOS app before they get released to the bug-hating public.
Who’s eligible: The first 10,000 people to register. You need to be an iOS user, obviously. And you should use Tumblr every day—because bugs are natural sneaks. You won’t find them if you’re not using the app all the time.
How it works: You’ll get access to each new release about a week before it goes public. When you see a bug, take a screenshot and email it to to ios-beta@tumblr.com with a description of what happened and what you were doing at the time. (Some legal stuff: Any feedback that you send in is completely voluntarily but it will be exclusively owned by Tumblr so we can use it to make Tumblr better—even the mean stuff).
What you should know: You’ll be looking for bugs, which means there are bugs to look for. Don’t register if you’re expecting a bug-free experience. But if you get tired of bugs, you can leave the beta program at any time. Also, even though it’s a beta, it’s still the Tumblr app and the same Tumblr Terms of Service and Privacy Policy applies.
How to register: Join this Google group with your email. In a couple days you’ll get an email from TestFlight with instructions on how to install the beta.
FBSnapshotTestCase is a powerful tool that was built to unit test the visual appearance of UIViews, but that’s not all it can do. This talk will explore how you can leverage FBSnapshotTestCase to simulate the visual feedback of Interface Builder when programmatically laying out views.
You can find the code used for this exercise on Paul’s GitHub.
Paul Rehkugler(pr) is a Staff Engineer at Tumblr. If he’s not writing software, he’s probably hiking or playing guitar.
When compiling with the iOS 10 SDK and running on an iOS 10 device, calling MFMailComposeViewController() can return nil when MFMailComposeViewController.canSendMail is false, despite the fact that its initializer is not marked as failable.
When Tumblr announced its Spring ’16 Hack Day, we decided to build an audio composer into the app. We codenamed it Mozart.
Giving people “tools” to create something - in this case a musical composition - in a way that feels effortless and powerful felt greatly rewarding. A huge source of inspiration for us was the Byte app. We were also excited because this was going to be in the Tumblr post form.
WWDC 2016 has come and passed, but we wanted to take the time to call out the new ideas that Apple unveiled which we think are important as developers, as well as things to make our product teams aware of for future launches.
WWDC has slowly returning back to a software and developer focused event over these passed few years, and this year was no exception. Many new technologies, tools, and ideas introduced for developers to plug into to enrich both their applications as well as the Apple ecosystem in general. So let’s get into what we saw and enjoyed.
We’ve experienced issues with iOS’s dynamic linker increasing our app’s launch time to unacceptable levels. To learn more about the linker’s inner workings and explore possible solutions, we created a Ruby script that generates dependency hierarchies of arbitrary complexity and tested their effect on launch times.
Doing a clean build of the Tumblr iOS app takes…a while, and we have a bunch of developers on the team, assuming each person does at least two clean builds a day1 that adds up to, well, too much time. As we march forward with Swift for new features, we noticed our compilations times increasing at surprising rates. There have been a few blog posts outlining expressions that the Swift compiler has trouble on. For instance, type inference of nested literal expressions2 and operator overloads are expensive to resolve.
To address this, we decided to automate monitoring of compilation performance. The goal was to create a weekly job that would compile our project with specific debug flags3, process the results, and email out the slowest compilation paths.
The result is a Swift script, called SwiftCompilationPerformanceReporter (nicknamed SwiftCPR), that we use to generate our weekly compilation report. Below are the steps SwiftCPR takes:
where workspacePath, scheme, and buildOutputDirectory are the workspace file, scheme, and output directory for the raw compilation logs, respectively. These can be specified in the config.json file.
Processes the raw compilation logs and merges duplicate entires.
Sample raw logs:
5992.1ms /Users/tumblr/workspace/SwiftCPR/orangina/Classes/PerformanceLoggingEvent.swift:267:37 final get {}
5718.3ms /Users/tumblr/workspace/SwiftCPR/orangina/Classes/PerformanceLoggingEvent.swift:267:37 final get {}
4376.1ms /Users/tumblr/workspace/SwiftCPR/orangina/Classes/UniversalLink.swift:127:25 private final class func dictionaryOfAppArgumentsFromQueryString(string: String) -> [NSObject : AnyObject]?
...
Outputs a final report with the total compilation time and the slowest limit compilation paths, where limit can be configured in the config.json file.
Sample report:
Total build time: 1115.24661797285
11.7104 /Users/tumblr/workspace/SwiftCPR/orangina/Classes/PerformanceLoggingEvent.swift:267:37 final get {}
8.5783 /Users/tumblr/workspace/SwiftCPR/orangina/Classes/UniversalLink.swift:127:25 private final class func dictionaryOfAppArgumentsFromQueryString(string: String) -> [NSObject : AnyObject]?
...
Once the above steps are complete, our job emails the report to the team! From these insights, we have been able to refactor functions that took over 10 seconds to compile to roughly a tenth of a second. Hope this script can help your team better profile Swift compilation times!
Unit testing networking code can be problematic, due mostly to its asynchronous nature. Using a staging server introduces lag and external factors that can cause your tests to run slowly, or not at all. Frameworks like OCMock exist to specify how an object responds to a specific query to address this behavior, but a mock object must still be set up for each type of behavior being mocked.
Using Apple’s NSURLProtocol, we can create a test suite that eschews these problems by mocking the response to our network calls centrally, essentially letting your test focus only on business logic. This protocol can be used not only with the built-in NSURLSession class, but can also be used to test classes and structs written with modern third party networking libraries, such as the popular Alamofire. In this article, we will look at mocking network responses in Swift for requests made using Alamofire. The sample project can be found on github.
NSURLProtocol’s main purpose is to extend URL loading to incorporate custom schemes or enhance existing ones. A secondary, yet extremely powerful, use of NSURLProtocol is to mock a server by sending canned responses back to callbacks and delegates. Say we have a very simple struct that uses Alamofire to make an HTTP GET request.
Fig 1 - A simple struct that serves as a REST client
The sample in Figure 1 creates a struct with an NSURL as an init parameter, and a sole method, getAvailableItems(), taking in a completion block as an argument, making a rest call to the NSURL and populating an array of MyItem in the block sent into it. From a testing perspective, we’d like to have a JSON response that matches the expected response, containing an object called items whose value pair is an array of strings. In order to make our tests as thorough and robust as possible, we’d also include at least two other mock responses: a JSON response that does not match this expectation, to test the else clause, and a garbage or erroneous response to check our error handling.
Fig 2 - A valid response
Fig 3 - A non-valid response
Fig 4 - A throw-away garbage response
Figures 2, 3 and 4 show a valid response for our purposes, a non-valid yet correct JSON response, and a throw-away string that isn’t even valid JSON, respectively. Without having to make a full-blown staging server, let’s see how we could go about testing these using NSURLProtocol.
To understand where NSURLProtocol fits into this problem, it’s important to look at a bit of the architecture Alamofire employs. Alamofire works as a singleton, as one can see from the above example. There is no instantiation required. Just feed a URL in, and make a request. Under the hood, the entity making the request is called the Manager. Manager is the entity that actually stores the URL and parameters, and is responsible for firing off an NSURLSession request abstracted from the caller class.
The manager for Alamofire can be initialized with a custom configuration of type NSURLSessionConfiguration, which has a property called protocolClasses, an array of NSURLProtocol members. By creating a new protocol that defines what happens when NSURLSession tries to reach a certain type of endpoint, loading it into the protocol array of a new configuration at index 0 (the default configuration), and initializing a new Manager object with this configuration, we can inject Alamofire with a simple, local mock server that will return whatever we want, given any request. Let’s start setting up a test class for our REST client by extending NSURLProtocol to respond to GET requests, and creating an Alamofire.Manager object with a custom NSURLSessionConfiguration that employs our protocol.
Fig 5 - Setting up a testing class for our client
Great, now we have an NSURLProtocol class that takes a GET request, checks the URL, and returns either a valid JSON response, or a simple “GARBAGE” response. This should allow us to test how our client responds. We still haven’t written any cases. We have a MyRESTClient property, as well as a Manager property. We also have a setup initial method that instantiates and loads our custom protocol into the manager instance. We now need a way to inject this manager instance into our Alamofire singleton. Let’s extend our client to the following.
Fig 6 - The REST client with an injectable “manager” parameter
We’ve added an initializer to our struct that allows us to send either a custom manager or nil into Alamofire. When the parameter is nil, the manager will load with its standard configuration. We also edited the request execution to be called via the manager we selected instead of directly through Alamofire. We can now add the following test case to our test class.
Fig 7 - Our first test case
In this test case, we create a new client, and give it our custom manager through the new initializer. We set a testing expectation, since the result comes back on a closure, and, after loading our itemsArray inside, fulfill the expectation. We tell the test case to wait for said expectation to be fulfilled, and, once it is, we make sure the itemsArray contains three items. If so, our test is successful, and our business logic is tested for getAvailableItems. Notice that we have used a bogus URL of “http://notNil”, which we have defined in the protocol to be selected in the conditional for populating the response correctly. To test the “garbage” case, we could write a test like the following.
Fig 8 - A test case for verifying a garbage response
In this second test case, the mocked URL of “http://nil” is not recognized, and the protocol responds by returning “GARBAGE”, thus not populating the response array. If our method is written correctly, it will call the closure with a nil array.
Fig 9 - A test case for verifying an incorrect response
In the third and final test case, our protocol class will return a “concepts” array instead of an “items” array, so the end result should still be a nil array in the closure.
As you can see, using NSURLProtocol we have created what amounts to a tiny server that responds to our requests and replies as specified, perfect for testing our asynchronous net calls. Now, go forth and test!