Adoption Curve Dot Net

Collection View Updates in iOS10 Part 2

| Comments

Along with prefetching, many of the other changes to UICollectionView introduced with iOS10 relate to performance improvements.

The lifecycle of a cell has been well-established since UICollectionView was introduced in iOS6:

prepareForReuse is called first, and is where the cell’s contents should be set back to their default state

cellForItemAtIndexPath: is where the majority of the work involved in setting up the cell takes place

willDisplayCell: is a last-minute opportunity to update the cell before it’s displayed

didEndDisplayingCell: is called as the cell disappears from view

The four lifecycle functions remain in iOS10, but there have been some subtle changes to when they’re called:

  • willDisplayCell: is now called later than previously - whereas in iOS9 and earlier the function was called immediately the cell was taken off the recycle queue, in 10 it’s not called until just before the cell is displayed
  • previously, didEndDisplayingCell was called as soon as the cell scrolled out of the visible bounds of the collection view. Now there’s a delay, which means that the cell hangs around a bit longer in case it’s scrolled back in again. This removes the need to recreate the cell from scratch, and should improve performance when changing scroll direction.

The other significant change is that cells are now dequeued individually rather than row-by-row. This helps to spread out the load of setting each cell up, rather than causing spikes in load; and also aids with making sure that cells are always ready to be displayed.

The good news is that all these changes take place under the hood, so you don’t need to make changes to pre-existing code in order to take advantage of them.

Collection View Updates in iOS10, Part 1

| Comments

Following WWDC, iOS 10 is now out in beta form ahead of a probable release in the autumn. There are a whole load of brand-new features, but also some tweaks to existing ones, and UICollectionView is no exception.

Although UICollectionView is a highly-performant control, Apple haven’t let it stand still. One of the most interesting new features is designed to improve the performance of collection views with expensive data sources.

Background

The aim for any iOS application is to run the UI at the full 60 frames per second, to deliver a completely smooth scrolling performance. Any frame rate significantly lower than this will manifest itself by dropped frames and stuttery performance.

60fps equates to 16.67ms per frame, which isn’t long if you’re dealing with drawing collection view cells using data from slow sources. The trick to high performance collecion views is to get the cellForItemAtIndexPath to return a cell as fast as possible, but this can be difficult. Techniques like asynchronous fetching of images can help, but these aren’t a miracle cure.

Note: This technique is also available to use with UITableView - just replace references to UICollectionView with UITableView, and the implementation patterns are identical.

Prefetching

In iOS10, Apple have introduced a new UICollectionViewDataSource protocol extension called UICollectionViewDataSourcePrefetching.

This introduces a new property on UICollectionView called prefetchDataSource. This is a class that implements two UICollectionViewDataSourcePrefetching protocol functions:

  • collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath])

  • collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath])

The prefetchItemsAt indexPaths: function is called by the collection view when it looks like the scrolling rate of the collection view will outstrip the ability of cellForItemAtIndexPath to deliver cells in a timely manner.

The collection view passes in an Array of NSIndexPaths for cells that are likely to be needed in the future. This gives you an opportunity to update the data source that underlies the collection view. For example, if your data source was an Array of images, you could call out to the network to download the images and insert them into the data source so that they are ready to be used by the cellForRowAtIndexPath function.

The second method is normally called when the scrolling direction changes. The reasoning for this is that you’re likely to be prefetching data well in advance of the cells being displayed - if those cells won’t now get displayed because the collection view is scrolling away from them, you may as well cancel whatever operation it is that you’re carrying out to update the data source. Again, the index paths of the cells in question are passed in as an Array of NSIndexPaths.

Implementing pre-fetching

To take advantage of the new prefetch feature, you’ll need to do four things:

  • Conform a class to the UICollectionViewDataSourcePrefetching protocol
  • Implement the collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) function to update the collection view’s dataSource
  • Optionally, implement the collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) function to cancel any in-flight prefetching operations
  • Set the conforming class as the collectionView’s prefetchDataSource property.

Conforming to UICollectionViewDataSourcePrefetching

This isn’t difficult: just mark your data source as implementing the protocol:

1
2
3
class MyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDataSourcePrefetching {
  ...
}

Implementing prefetching

Prefetching is implementing with the collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) function. There the collection view passes in an Array of NSIndexPaths, one for each of the cells that are likely to be displayed.

Your job here is to iterate across this array, and update the collection view’s data source accordingly:

1
2
3
4
5
6
7
8
9
10
11
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {

    for indexPath in indexPaths {
      
      ... expensive operation to retrieve some data...
      
        dataArray[indexPath.row] = retrievedData
      
    }
    
}

There are a couple of important points to note here: the first is that you’re only updating the collection view’s underlying data source - not returning any data directly. This could be as simple as updating an Array of strings (as above), or more complex like updating a CoreData or Realm model.

The second point to bear in mind is that after the prefetch has taken place, there’s no guarantee when, or if, that retrieved will be used. The collection view’s scroll rate could slow down; or the direction reverse entirely.

For this reason, there is a chance that time-critical data could be stale by the time it’s displayed. Whether this is a factor that you need to consider will of course depend on the nature of the data being displayed.

Implenting prefetch cancellation

To an extent, requests for prefetches are a bit of a guess - the collection view is attempting to optimise for an uncertain future state that might not actually come to pass. For example, if the scroll rate slows, or reverses altogether, there’s a chance that the cells for which prefetch has been requested may never actually be displayed.

In this situation, any inflight prefetch would be wasted effort. Rather than make redundant requests, the UICollectionViewDataSourcePrefetch protocol defines a function to cancel outstanding requests: collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath])

This is called if the collection view decides that prefetching would be over-optimisation - it passes an Array of NSIndexPaths as the function’s parameter, and it’s up to you to iterate over those and cancel any requests where it makes sense to do so.

Hooking up the prefetch delegate to the collection view

With the class conforming to the protocol, and with one or both of the functions implemented, you need to add it as a prefetchDataSource to the collection view.

This is as simple as updating the property (here, we’re assuming that the collection view controller is acting as its own prefetchDataSource):

1
collectionView.prefetchDataSource = self

This has to be done before any calls to any of the UICollectionViewDataSource protocol functions, so it probably makes sense to do so at the same time as setting up the collection view’s other properties, such as cell registration.

Conclusion

UICollectionView is a high-performance control to begin with, with a significant amount of ‘under the hood’ optimisation going on that we don’t see. Prefetching is another tool to eke out even more performance, especially in situations where getting the source data for display in collection view cells is expensive or slow.

However, there’s a saying that “premature optimisation is the root of all evil” in code, so it’s not a magic bullet. And don’t overlook the fact that often the most expensive part of the process is compositing the collection view cell itself. But if you’re looking to squeeze out the last drops of performance to make your collection views as smooth as possible, prefetching may be worth a try.

Testing Asynchronous Code in Swift

| Comments

Asynchronous code is both hard to test; and important to test.

It’s important, because it often handles some of the most critical and yet unreliable parts of an app - in other words, networking.

It’s hard because it’s, well, asynchronous. One of the two jokes in software engineering goes something like “Some people, when faced with a problem, think ‘I know, I’ll use threading’. Now problems have two they.”

The standard rhythm of a unit test goes like this:

  • set up the preconditions for the test, to get the system into a known state
  • make some assertions about what result you’re looking for
  • fire the functions under test
  • verify that the assertions were met

The problem with asynchronous code is that the third and final steps don’t always come in that order, particularly if you’re attemping to test a long-running operation. It can still be churning away as your verification takes place - and your tests are unlikely to pass in that kind of scenario.

Fortunately, there are ways around this. It’s not especially pretty, but it does work.

waitForExpectations

The key to testing async code in Swift is using the waitForExpectationWithTimeout feature of the XCTest framework. An expectation in this context is just a flag that tells the test code “I’m done, you can check your assertions now”.

The expectation will hang around and wait for the fulfillment message, which gives the async code a chance to complete before the assertions are checked.

There are three stages involved in setting this up:

  • Creating an expectation
  • Calling the async function, and then flagging that the expection has been fulfilled when your code has comepleted
  • Wrapping the assertions inside a waitForExpectationWithTimeout block so that they will wait for the fulfillment to occor.

Creating the expectation

This is quite simple. At the top of your test function, create an expectation with

1
let asyncExpectation = expectationWithDescription("longRunningFunction")

This needs to be done before you call the async code.

Fulfilling the expectation

With the expectation created, you need to signal that it’s fulfilled after your asynchronous code completes. Here’s an example of a function that fires off a network request, and takes a completion handler as a parameter to process the data that’s returned:

1
func networkManager.getDataFromApiForUser(user: User, completionHandler: (userData: NSData?, error: NSError?) -> () )

In normal situations, you’d use this function with something like this:

1
2
3
4
5
6
7
networkManager.getDataFromApiForUser(testUser, completionHandler: { (userData, error) -> () in
  
  ... handle the error if one occurs
  ...
  ... otherwise process the userData object
  
)}

Testing the function in a unit test is very similar:

1
2
3
4
5
6
7
8
9
networkManager.getDataFromApiForUser(testUser, completionHandler: { (userData, error) -> () in

  ... handle the error if one occurs
  ...
  ... otherwise process the userData object

  expectation.fulfill()
  
)}

The key difference is that last line - expectation.fulfill(). What we’re doing here is to signal to the asyncExpectation that everything is completed, and it’s now safe to check the assertions.

Checking the assertions

Assertions in this context are just the usual XCTest assertions that you’ll already be familar with. What’s different is that we wrap them in a waitForExpectationsWithTimeout block:

1
2
3
4
5
6
self.waitForExpectationsWithTimeout(5) { error in
  
  XCTestAssertNil(error, "Something went horribly wrong")
  XCTestAssertEqual(testUser.orders.count, 10)
  
}

This function will do two things - firstly, it will wait for a maximum of five seconds before giving up and failing. This prevents you from locking up the tests in an infinite loop if something goes wrong with the code under test. How long to wait is dependent on the kind of processing that’s taking place - but ideally shouldn’t be so long that it locks up the test suite unncessarily.

Secondly, it will fire the XCTest expectations as soon as the expectation.fulfill() function is called. By triggering that at the end of the chunk of async code, you’re preventing the assertions racing away and being checked before the function under test has had a chance to complete.

Putting it all together

Putting that all together in a example test looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func testUserOrdersAreRetrievedFromApiAndProcessed() {

  let asyncExpectation = expectationWithDescription("longRunningFunction")
  
  let testUser = User.initWithName("Foo" andAccountNumber: 1234)
  
  networkManager.getDataFromApiForUser(testUser, completionHandler: { (userData, error) -> () in
      
      ... handle the error if one occurs
      ...
      ... otherwise process the userData object
      
      expectation.fulfill()
      
  )}
  
  self.waitForExpectationsWithTimeout(5) { error in
      
      XCTestAssertNil(error, "Something went horribly wrong")
      XCTestAssertEqual(testUser.orders.count, 10)
      
  }

}

It’s not the cleanest looking test code you’ll ever write, especially if you’re not a fan of the Junit-style syntax of the XCTest framework - but it works. And given how important asynchronous code like network requests or long-running processing tends to be to an app, it’s worth sacrificing a little style for the reassurance that high test coverage will provide.

Mocks and Expectations With Swift

| Comments

A new language, a new way of doing things - including testing. One of the harder parts of getting to grips with Swift has been figuring out how testing works, and in particular how to do more extensive stuff like mocking and stubbing.

By way of background, mocking is the art of creating “stunt doubles” for your classes that are involved in tests. You’d do this for a couple of reasons - either to manipulate your stunt doubles into a known state for testing purpose; or because it’s expensive or difficult to use the real thing.

An example of this might be testing what happens if a network service doesn’t return the result that you were expecting. If you’re testing the use of a live service, it might be tricky to create a deliberate failure just for testing purposes. Far easier to create a stunt double and use this to return the value that your tests require.

The other situation where you may want a stand-in object is if you want to verify that a method has been called. Say for example you’ve got some kind of callback, and you want to test whether this gets fired. By setting an expectation on the callback method, you can verify that it actually was called - which is especially useful if you’re testing asynchronous operations.

It was perfectly possible to do all of this in Objective-C, but it required the use of external libraries such as OCMock or Kiwi. Swift, on the other hand, is much easier. The structure of the language makes it very straight-forward to implement basic mocking and stubbing operations, without the need for external pods or libraries.

Here’s an example. Say we have a class called SooperClass, and this has a function called someExpensiveFunctionThatReturnsAStringFromTheWeb. We don’t want to really fire this as part of our tests, because (for the sake of this example) we get charged every time we make an API call. Instead, we need to mock it.

The first step is to create a mock of SooperClass inside our test class. At the top of the test class, create a FakeSooperClass which inherits from SooperClass like this:

1
2
3
4
5
6
7
8
9
class MockrTests: XCTestCase {

    class FakeSooperClass: SooperClass {

    }

    ... rest of the test class here ...

}

Next, override the method that you want to test:

1
2
3
4
5
6
7
class FakeSooperClass: SooperClass {

    override func someExpensiveFunctionThatReturnsAStringFromTheWeb() -> String {
  
    }

}

And use this overridden method to return whatever data your test needs:

1
2
3
override func someExpensiveFunctionThatReturnsAStringFromTheWeb() -> String {
    return "Finally! Some content!"
}

Now you can use this in your test:

1
2
3
4
5
6
7
8
func testThatTheExpensiveFunctionReturnsSomethingUseful() {

    let fakeSooperClass = FakeSooperClass()
    let stringFromTheWeb = fakeSooperClass.someExpensiveFunctionThatReturnsAStringFromTheWeb()

  ... use the value in the rest of the test ...
  
}

Here, we’ve created a mock instance of SooperClass, and used this to return data in the format that we need for the rest of the test. But in doing so, we haven’t had to call out to the public API that SooperClass would normally talk to.

We can take the fake class one step further, and test that the method actually gets called. We’ll set an expectation that the function will be called once during the code that we’re testing - if it isn’t, something has gone wrong somewhere.

To do this, we need to extend our fake class slightly, to include a flag:

1
2
3
4
5
6
7
8
class FakeSooperClass: SooperClass {

    var didCallExpensiveFunctionFlag = false

    override func someExpensiveFunctionThatReturnsAStringFromTheWeb() -> String {
        return "Finally! Some content!"
    }
}

This flag is initially false (because the function hasn’t been called yet). When the function is called, we’ll flip the flag inside the fake class. By getting the test to check the state of the flag, it will be possible to see if the function was called as we expected.

This needs a tweak to our fake someExpensiveMethodThatReturnsAStringFromTheWeb() function:

1
2
3
4
5
6
7
8
9
class FakeSooperClass: SooperClass {

    var didCallExpensiveFunctionFlat = false

    override func someExpensiveMethodThatReturnsAStringFromTheWeb() -> String {
        didCallExpensiveFunctionFlag = true
        return "Finally! Some content!"
    }
}

Now we can test all this in our test case:

1
2
3
4
5
6
7
8
9
10
11
12
func testThatTheExpensiveFunctionGetsCalled() {

    let fakeSooperClass = FakeSooperClass()

    // Code that eventually ends up with someExpensiveFunctionThatReturnsAStringFromTheWeb() being called,
    // like this:
    // let result = fakeSooperClass.someExpensiveFunctionThatReturnsAStringFromTheWeb()

    // Set an assertion to check that the flag
    XCTAssert(fakeSooperClass.didCallExpensiveFunction == true, "expected function to be called, but wasn't")

}

If the function WAS called, the flag will be flipped, and the assertion will pass. If for some reason the function didn’t get called, then the flag will be false and will cause the assertion to fail.

You could also extend this to keep track of the number of times the function gets called, by incrementing a counter. It uses exactly the same approach of overriding a function in a fake class, and checking the results with an assertion.

So, not difficult to do. Not only that, but rolling your own mocks and expectations in Swift also has the benefit that you’re not dependent on external 3rd party libraries with all the attendent complexities that these can sometimes bring along.

Testing NSUserDefaults

| Comments

NSUserDefaults is where you can store persistent app settings, as an alternative to storing them in some kind of in-app database. You shouldn’t try to store actual application data in NSUserDefaults, but they are useful for capturing “small” settings like environment URLs and so on.

If you’re using NSUserDefaults, you should be testing NSUserDefaults. Here’s how.

One of the most common testing scenarios is that you want to store some value in the defaults store so that it can be read out later. This is the process for testing that your code actually does so. Assume that we’ve got a class called myClass which has an IBAction method called didTapUpdateSettingsButton which will write out a specific value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it(@"should write the configured environment to the user defaults"), ^{

  NSString *apiUrl = @"http://foo.com/bar";
  NSString *theKey = @"defaultApiUrl";

  id mockStandardDefaults = [NSUserDefaults nullMock];
  
  [NSUserDefaults stub:@selector(standardUserDefaults) andReturn:mockStandardDefaults];
  
  [[mockStandardDefaults should] receive:@selector(setObject:forKey:) withArguments:theObject, theKey];
  
  [[mockStandardDefaults should] receive:@selector(sychronize)];
  
  [myClass didTapUpdateSettingsButton:nil];

}

Walking through this, we first create variables for the key and object that we are expecting the didTapUpdateSettings method to attempt to write to the defaults store.

Next, we create a mock object which will stand in for the instance of the NSUserDefaults class that would normally be returned by the [NSUserDefaults standardUserDefaults] method. We’re going to use this mock to catch and test the messages that our code under test sends. It’s created as a null mock here so that it won’t complain if it gets messages other than the ones that we send it.

Then we need to stub the standardUserDefaults method of the real, live NSUserDefaults class, and return our mock instance instead of the real thing.

Once we’ve stubbed out NSUserDefaults, we can now set some expectations about what our mock standardUserDefaults will receive. There are two things we’re interested in - firstly, that it receives the setObject:forKey message with the values that we expect. If that message isn’t received, or the values differ from what we expect, then there’s something wrong with the code that we’re writing.

Secondly, we’re also setting an expectation that the mock receives the synchronize message. This saves the values that we’ve updated - not sending a synchronize message to NSUserDefaults is a common mistake, and hard to diagnost - with a specific test, we’ll catch any situations where we forget that final step.

Finally, we invoke the code under test by “tapping” the button (or in this case, calling the IBAction method that the button is wired up to).

If you’re taking a test-first approach to writing the code, obviously this will initially fail. But by fixing each failing assertion in turn, you’ll end up with a method that both writes and saves the correct values.