Async-await in Swift is getting more popular as time goes by, but Combine publishers do not have built-in support for it currently. In this blog post, we’ll see how to expand some of the existing publishers.
Async-await supported sink
One case where I have encountered this is when I have wanted to call an async function in sink. Although I could wrap the call with Task within the sink subscriber, it gets unnecessary long if I need to do it in many places. Instead, we can just do it once and add an async-await supported sink subscriber.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The Combine framework has map and tryMap for supporting throwing functions, but is lacking something like tryAwaitMap for async throwing functions. Combine has a publisher named Future which supports performing asynchronous work and publishing a value. We can use this to wrap a Task with asynchronous work. Another publisher in Combine is flatMap what is used for turning one kind of publisher to a new kind of publisher. Therefore, we can combine these to turn a downstream publisher to a new publisher of type Future. The first tryAwaitMap below is for a case where the downstream publisher emits errors, and the second one is for the case where the downstream does not emit errors. We need to handle these separately since we need to tell Combine how error types are handled (non-throwing publisher has failure type set to Never).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Back in 2019 I wrote about Testing networking code with custom URLProtocol onĀ iOS. Recently, I was using the same approach for setting up network mocking for GraphQL requests, but then I stumbled on something unexpected. If we create an URLRequest which has httpBody set, then inside the custom URLProtocol implementation, httpBody method returns nil, and we need to access httpBodyStream instead. When dealing with GraphQL requests, the GraphQL query is part of the data set to httpBody. Reading the data from httpBody property would be straight-forward, then httpBodyStream on the other hand returns an InputStream and this requires a bit of code. InputStreams can only be read once, therefore while inspecting the URLRequest inside the URL protocol, we need to make sure not to read it twice. This is a nature of how input streams work.
In the snippet below, we can see a Data extension which adds a new initializer which takes in an InputStream. The implementation opens the stream, closes it when initializer is exited, reads data 512 bytes at the time. The read function of the InputStream returns number of read bytes if successful, negative value on error and 0 when buffer end has been reached which means that there is noting more to read.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Let’s see how to use this extension and reading a GraphQL query from the URLRequest. In the example below we can see that the example GraphQL data is set to a dictionary with 3 keys: query, operationName, and variables. Therefore, we need to first turn the InputStream into Data and then decoding the data to a model type and after that reading the query. Since we know the query, we can proceed with writing network tests where the mocked response depends on the query.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Swift package manager supports creating command and build plugins. In this blog post we’ll take a closer look at build plugins since these enable us to tap into the package build process and add extra steps like generating some code for the package. Command plugins, on the other hand, add extra commands which we can invoke from the command line or from Xcode since Xcode automatically exposes these commands in the UI. The example problem we are going to tackle is creating a build plugin which takes in a JSON and outputs some Swift code which in turn is used by the package. For making things more interesting, we’ll hook up a custom executable target which contains custom logic for generating that code. In many other cases, we could use existing tools like Sourcery and skip our own executable target.
Plugins run in a sandbox environment, so they can only write to a pluginWorkDirectory. So in our case we’ll write our generated code in that folder and tell the build command to include the generated file while building the target. This wasn’t immediately clear for me that this happens automatically if I set the generated file path as an output file of the build command. But let’s start with setting up a build plugin and then adding the executable target which the build plugin ends up calling and which generates the code.
We’ll name the build plugin as “ToomasKitPlugin” since the example package is named as “ToomasKit”. Plugins need to be defined in the Package.swift where we also let the Swift build system know which target runs the plugin. The final Package.swift looks like this which includes a library with its target and testing target, plugin, and executable target.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here we can see that the plugin goes under the targets section and since we are interested in creating build plugins the capability is set to buildTool. The other option is command plugin, which was mentioned before. Also, we add a plugin dependency since the plugin ends up calling an executable which we will create ourselves. Secondly, we need to add the plugin to the library target, since Swift build system needs to know which target runs this plugin. Time to create the build plugin. Plugins go, by default, to <PackageRoot>/Plugins folder and since our plugin is named as ToomasKitPlugin the full path is <PackageRoot>/Plugins/ToomasKitPlugin. Build tool plugins need to conform to BuildToolPlugin protocol, and we also need to annotate the struct with @main for letting Swift know which type is the entry point of the plugin. Build tool plugins need to implement a createBuildCommands function which returns a list of build commands to run. At the time of writing the post there are available pre-build and build commands where the former runs every single time we build and the latter only when there is a change in input or output files which the command refers to. In our case, the input file is a JSON file and the output is the generated Swift file. The JSON file is part of the package target, so we can get a path to the file using the target.directory API. The output can only be written to pluginWorkDirectory since plugins run in a sandbox. All the paths which are added to outputFiles get included with the build. Since we are generating Swift code the package will use, we add it to outputFiles. Now when we have the plugin configured, let’s take a look at the CodeGenerator implementation, which is an executable target the plugin runs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The CodeGenerator executable target takes in two paths: input and output, where the input is the JSON file and output is the final generated Swift file. It will then decode the JSON, generate an enum based on the JSON content and writes it to the output file. This is just a simple example, but I would like to highlight the fact that using swift-argument-parser probably makes sense for a little bit more complicated executables.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If we build the package, we can see that the plugin runs our CodeGenerator, the generated Swift file gets included with the package, and we can use the generated code in other files. Exactly what we wanted.
While using SwiftUI for building UIs, sooner or later we would like to write some unit-tests as well. Of course, we could always go for UI-tests, but these are much slower and therefore not so scalable if we would just want to have a basic verification for our view. It is easy to get going with writing unit-tests for UIKit code, but it is much more difficult for SwiftUI views. Currently, there seems to be two main ways: snapshot testing using the pointfreeco’s library or inspecting views with ViewInspector. Today, we are not going to compare these libraries and instead just take a look at how to get going with ViewInspector.
Although ViewInspector supports using bindings etc for updating the view while running the unit-test, I personally feel like these kinds of tests where we interact with the view is probably better for UI-tests. Therefore, in this blog post, we just take a look at a basic SwiftUI view and how to inspect it in a unit-tests.
Here we have a basic SwiftUI view which uses a view model to provide data for the view. The style property is driving the title of the view. A pretty simple example to demonstrate the usage of the ViewInspector library.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Time to add two unit-tests where we first configure the view model and then verify. It is also possible to write a test where we dynamically change the style property, but that requires some extra code for supporting it. The first step after adding the library and only adding to the unit-testing target is to opt-in the custom view for inspection. Without the ContentView extension the library is not able to inspect any views. In the example below, we are just using the find method for looking for a Text with specific string, which depends on the style property. I feel like this library is really nice for these kinds of unit-tests where we create a view and just verify what it is displaying.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
In summary, I think that ViewInspector is a really nice library for unit-testing SwiftUI views. Since it requires more work to support reacting to view updates dynamically I feel like, at least for now, I am going to use it for static views and use UI-tests instead for tests simulating user interaction.
Why does my CI never finish and post a message to the merge request? Logged in to CI and oh, my merge job had been running for 23 minutes already, although typically it finishes in 4 minutes. What was going on? Nothing else than on unit-test marked with async was still waiting for an async function to finish. So what can we to avoid this? Let’s first create a Swift package which will be demonstrating the issue.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This test passes after 100 seconds, but clearly, we do not want to wait so long if something takes way too much time. Instead, we want to fail the test when it is still running after 5 seconds.
Exploring XCTestCase executionTimeAllowance
XCTestCase has a property called executionTimeAllowance what we can set. Ideally I would like to write something like executionTimeAllowance = 5 and Xcode would fail the test with a timeout failure after 5 seconds.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
But if we read the documentation, then it mentions that the value set to this property is rounded up to the nearest minute value. In addition, this value is not used if you do not enable it explicitly: “To use this setting, enable timeouts in your test plan or set the -test-timeouts-enabled option to YES when using xcodebuild.”. If we are working on a Swift package, then I am actually not sure how to set it in the Package.swift so that it gets set when running the test from Xcode or from a command line.
Custom test execution with XCTestExpectation
One way to avoid never finishing tests is to use good old XCTestExpectation. We can set up a method which runs the async work and then waits for the test expectation with a timeout. If a timeout occurs, the test fails. If the async function throws an error, we can capture it, fail the test with XCTFail.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sometimes we need to handle delegates in a class which has the @MainActor annotation. Often it can be a view model where we expect that code runs on the main thread. Therefore, view models have the @MainActor annotation, since we want that their methods run on the main thread when interacting with other async code. In an example below, we’ll be looking into integrating a delegate based ImageBatchLoader class which calls delegate methods on a background thread. The end goal is to handle the delegate in a view model and making sure it runs on the main thread.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
An example ImageBatchLoader with stubbed out start method.
This is an example of a class which uses delegates and calls delegate methods from background threads. If we have a view model with @MainActor annotation, then we just can’t conform to that delegate since the delegate does not use any async-await support. Xcode would show a warning saying that the protocol is non-isolated. A protocol would be isolated if it would have, for example, @MainActor annotation as well for that protocol. Let’s say this is not possible and it is a third party code instead.
The solution I have personally settled with is creating a wrapper class which conforms to that delegate and then uses main thread bound closures to notify when any of the delegate callbacks happen.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here we can see a class which conforms to the ImageBatchLoaderDelegate and provides a didLoadBatch closure which has an @MainActor annotation. Since we use @MainActor and tap into the async-await concurrency, then we need an async context as well, which the Task provides.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Finally we have hooked up the image loader, its handler and also forwarding the didLoadBatch to a separate function which is part of the view model. With a little bit of code, we achieved what we wanted: listening to delegate callbacks and forwarding them to the view model on the main thread. If we ran the code we would see that the delegate callback runs on a background thread but the view model method runs on the main thread.
While working on an app where I needed to subscribe to multiple Combine publishers, I got confused about if I should use merge, zip or combineLatest. These publishers are quite similar with subtle differences. For making sure I never get confused about it, I am going to present examples in this week’s blog post.
Merge
Merge publisher just re-publishes any values received from any of the publisher. Useful when there are multiple sources of data we would like to combine into a single flow of updates.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Zip waits until it has received at least one element from each of the underlying publisher, and then delivers the value as a tuple. If one of the publisher publishes multiple values, then the first received value is part of the tuple and other values are part of next tuples after that.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CombineLatest publishes a tuple whenever any of the underlying publishers emits an element. The tuple contains the latest value from each of the publisher.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
While I was working on a mixed UIKit and SwiftUI project, I needed a way to access the UIHostingController within the SwiftUI view so that I could use it for interacting with other UIKit methods. This blog post tackles the problem and provides a simple solution how to implement it.
The approach we are taking is using the SwiftUI environment and inserting an object into the environment, which then keeps a weak reference to the view controller hosting the SwiftUI view. Using the SwiftUI view environment has a benefit of allowing multiple other SwiftUI views within the hierarchy to use it as well. In the end, we would like to write something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
In the snippet above, we use a custom embeddedInHostingController() function which inserts a new ViewControllerProvider type the to the environment. Let’s take a closer look how this function and type are implemented.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The ViewControllerProvider class keeps a weak reference to the view controller. Since UIHostingController is a subclass of UIViewController we can just use UIViewController as a type. The embedded function creates an instance of the provider and a hosting controller, inserts the provider into the SwiftUI view environment and then sets the weak property which we can access later.
SwiftUI has view modifiers like onAppear() and onDisappear() for letting the view know when it is going to be displayed and when it is removed from the screen. In addition, there is a task() view modifier for running async functions. Something to keep in mind with onAppear() and task() is that the closure passed into the view modifier can be called multiple times when the view hierarchy changes. For example, when we have a TabView then the view receives onAppear() callback and also the task part of the task() is triggered each time when the tab presenting it is activated. In this blog post, we are looking into a case where we have some code which we only want to run once during the view’s lifetime. One of the use-cases is preparing content in view models. Let’s take a look at these cases where one view and its view model has synchronous prepare function and the other one has async prepare function (e.g. starting a network requests in the prepare function).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SwiftUI uses view modifiers for configuring views. This is what we want to do here as well. We can create a new view modifier by conforming to the ViewModifier protocol and implementing the body function, where we add additional functionality to the existing view. The view modifier uses internal state for tracking if the closure was called already or not in the onAppear(). SwiftUI ensures that onAppear is called before the view is rendered. Below is the view modifier’s implementation with a view extension which creates it and finally an example view and its view model using it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If we have a view model which needs to do async calls in the prepare() function then we need a slightly different view modifier. Since async functions can run a long time, then we should also handle cancellation. If the view disappears, we should cancel the task if it is still running and restart it next time when the view is shown. Cancellation is implemented by keeping a reference to the task and calling cancel() on the task in the onDisappear(). For making the cancellation working properly, we need to make sure the async function actually implements cancellation by using, for example, Task.checkCancellation() within its implementation. Other than that, the view modifier implementation looks quite similar to the one above.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters