swiftbysundell.com
Open in
urlscan Pro
2606:4700:20::681a:2ec
Public Scan
Submitted URL: http://swiftbysundell.com/
Effective URL: https://swiftbysundell.com/
Submission: On April 07 via api from GB — Scanned from GB
Effective URL: https://swiftbysundell.com/
Submission: On April 07 via api from GB — Scanned from GB
Form analysis
0 forms found in the DOMText Content
Articles, podcasts and news about Swift development, by John Sundell. * Articles * Basics * Podcast * Discover * About * Search Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. Start automatically building, testing, and deploying your app today. * RSS feed * Categories * SWIFTUI * CONCURRENCY * GENERICS * UNIT TESTING RECENTLY PUBLISHED Show compact list * PODCAST: “ACCESSIBILITY ON APPLE’S PLATFORMS” WITH SPECIAL GUEST SOMMER PANAGE * accessibility * ui development Published on 21 Mar 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS Sommer Panage returns to the show to discuss Apple’s various accessibility APIs and tools, how to incorporate accessibility support into a team’s overall development workflow, and what it was like being an engineering manager at Apple. SPONSORS * Emerge Tools: Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Get started at emergetools.com. * Bitrise: Rock-solid continuous integration for your Swift project, which now offers 50% faster builds and ad-ons for things like automatic deployment. Go to bitrise.io/swift to get started for free. LINKS * VoiceOver * Voice Control * Dynamic Type * PSPDFKit’s blog post about Dynamic Type * Defining accessibility labels * Accessibility traits * WWDC session about custom accessibility actions * WWDC session about using AXCustomContent * isReduceMotionEnabled * prefersCrossFadeTransitions * shouldDifferentiateWithoutColor * SwiftUI’s accessibilityElement modifier * Audio graphs * Intro and outro music by Dariusz Dziuk * * Read more about ui development LAYOUT ANCHORS * Another article about ui development STACKING VIEWS IN SWIFTUI * Continue exploring ui development SWIFTUI MIX AND MATCH * ABSTRACT TYPES AND METHODS IN SWIFT * generics * protocols * reference types Published on 15 Mar 2022 Discover page available: Generics In object-oriented programming, an abstract type provides a base implementation that other types can inherit from in order to gain access to some kind of shared, common functionality. What separates abstract types from regular ones is that they’re never meant to be used as-is (in fact, some programming languages even prevent abstract types from being instantiated directly), since their sole purpose is to act as a common parent for a group of related types. For example, let’s say that we wanted to unify the way we load certain types of models over the network, by providing a shared API that we’ll be able to use to separate concerns, to facilitate dependency injection and mocking, and to keep method names consistent throughout our project. One abstract type-based way to do that would be to use a base class that’ll act as that shared, unified interface for all of our model-loading types. Since we don’t want that class to ever be used directly, we’ll make it trigger a fatalError if its base implementation is ever called by mistake: class Loadable<Model> { func load(from url: URL) async throws -> Model { fatalError("load(from:) has not been implemented") } } Then, each Loadable subclass will override the above load method in order to provide its loading functionality — like this: class UserLoader: Loadable<User> { override func load(from url: URL) async throws -> User { ... } } If the above sort of pattern looks familiar, it’s probably because it’s essentially the exact same sort of polymorphism that we typically use protocols for in Swift. That is, when we want to define an interface, a contract, that multiple types can conform to through distinct implementations. Protocols do have a significant advantage over abstract classes, though, in that the compiler will enforce that all of their requirements are properly implemented — meaning we no longer have to rely on runtime errors (such as fatalError) to guard against improper use, since there’s no way to instantiate a protocol by itself. So here’s what our Loadable and UserLoader types from before could look like if we were to go the protocol-oriented route, rather than using an abstract base class: protocol Loadable { associatedtype Model func load(from url: URL) async throws -> Model } class UserLoader: Loadable { func load(from url: URL) async throws -> User { ... } } Note how we’re now using an associated type to enable each Loadable implementation to decide what exact Model that it wants to load — which gives us a nice mix between full type safety and great flexibility. So, in general, protocols are definitely the preferred way to declare abstract types in Swift, but that doesn’t mean that they’re perfect. In fact, our protocol-based Loadable implementation currently has two main drawbacks: * First, since we had to add an associated type to our protocol in order to keep our setup generic and type-safe, that means that Loadable can no longer be referenced directly. * And second, since protocols can’t contain any form of storage, if we wanted to add any stored properties that all Loadable implementations could make use of, we’d have to re-declare those properties within every single one of those concrete implementations. That property storage aspect is really a huge advantage of our previous, abstract class-based setup. So if we were to revert Loadable back to a class, then we’d be able to store all objects that our subclasses would need right within our base class itself — removing the need to duplicate those properties across multiple types: class Loadable<Model> { let networking: Networking let cache: Cache<URL, Model> init(networking: Networking, cache: Cache<URL, Model>) { self.networking = networking self.cache = cache } func load(from url: URL) async throws -> Model { fatalError("load(from:) has not been implemented") } } class UserLoader: Loadable<User> { override func load(from url: URL) async throws -> User { if let cachedUser = cache.value(forKey: url) { return cachedUser } let data = try await networking.data(from: url) ... } } So, what we’re dealing with here is essentially a classic trade-off scenario, where both approaches (abstract classes vs protocols) give us a different set of pros and cons. But what if we could combine the two to sort of get the best of both worlds? If we think about it, the only real issue with the abstract class-based approach is that fatalError that we had to add within the method that each subclass is required to implement, so what if we were to use a protocol just for that specific method? Then we could still keep our networking and cache properties within our base class — like this: protocol LoadableProtocol { associatedtype Model func load(from url: URL) async throws -> Model } class LoadableBase<Model> { let networking: Networking let cache: Cache<URL, Model> init(networking: Networking, cache: Cache<URL, Model>) { self.networking = networking self.cache = cache } } The main disadvantage of that approach, though, is that all concrete implementations will now have to both subclass LoadableBase and declare that they conform to our new LoadableProtocol: class UserLoader: LoadableBase<User>, LoadableProtocol { ... } That might not be a huge issue, but it does arguably make our code a bit less elegant. The good news, though, is that we can actually solve that issue by using a generic type alias. Since Swift’s composition operator, &, supports combining a class with a protocol, we can re-introduce our Loadable type as a combination between LoadableBase and LoadableProtocol: typealias Loadable<Model> = LoadableBase<Model> & LoadableProtocol That way, concrete types (such as UserLoader) can simply declare that they’re Loadable-based, and the compiler will ensure that all such types implement our protocol’s load method — while still enabling those types to use the properties declared within our base class as well: class UserLoader: Loadable<User> { func load(from url: URL) async throws -> User { if let cachedUser = cache.value(forKey: url) { return cachedUser } let data = try await networking.data(from: url) ... } } Neat! The only real disadvantage of the above approach is that Loadable still can’t be referenced directly, since it’s still partially a generic protocol under the hood. That might not actually be an issue, though — and if that ever becomes the case, then we could always use techniques such as type erasure to get around such problems. Another slight caveat with our new type alias-based Loadable setup is that such combined type aliases cannot be extended, which could become an issue if we wanted to provide a few convenience APIs that we don’t want to (or can’t) implement directly within our LoadableBase class. One way to address that issue, though, would be to declare everything that’s needed to implement those convenience APIs within our protocol, which would then enable us to extend that protocol by itself: protocol LoadableProtocol { associatedtype Model var networking: Networking { get } var cache: Cache<URL, Model> { get } func load(from url: URL) async throws -> Model } extension LoadableProtocol { func loadWithCaching(from url: URL) async throws -> Model { if let cachedModel = cache.value(forKey: url) { return cachedModel } let model = try await load(from: url) cache.insert(model, forKey: url) return model } } So that’s a few different ways to use abstract types and methods in Swift. Subclassing might not currently be as popular as it once was (and remains to be within other programming languages), but I still think these sorts of techniques are great to have within our overall Swift development toolbox. If you have any questions, comments, or feedback, then feel free to reach out via either Twitter or email. Thanks for reading! Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * * GENERICS Learn how to fully utilize Swift’s powerful generics system. * Read more about protocols ALTERNATIVES TO PROTOCOLS IN SWIFT Four different ways of defining abstractions in Swift. * Another article about protocols CREATING CUSTOM SWIFTUI CONTAINER VIEWS Using a dedicated protocol to make it easy to define new containers. * Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * JUDO * Sponsored Published on 08 Mar 2022 My thanks to Judo for sponsoring Swift by Sundell — both the website and the podcast — during this first quarter of the year. Those of you who have been following my work for a while might know that I’m a big fan of the concept of “server-driven UIs”, where a UI fetches its entire view configuration from a remote server, rather than just downloading its data models. While I don’t recommend building an entire app that way — for certain kinds of screens, such as onboarding or payment flows, home screens or feeds, or other kinds of dynamic, content-driven views — being able to instantly change the way a given view is presented can be incredibly powerful. It can help make an app feel much more dynamic and up-to-date, while also letting teams iterate much quicker on such an app’s UI. However, building all of the infrastructure required to actually implement fully native, server-driven UIs is often incredibly complicated and time-consuming. That’s why, so far, we’ve mostly seen big tech companies adopt that kind of UI paradigm — but now, thanks to Judo, any team can easily start using server-driven UIs within their apps. Judo essentially consists of two parts. First, you’ve got a really nice, fully native Mac app that lets you build UIs completely visually — think Interface Builder or the SwiftUI canvas, but with a lot more power and flexibility: Then, Judo also provides SDKs for both iOS and Android that let you dynamically load and configure the experiences you’ve built using that Mac app directly within your mobile app. That means that you get to decide exactly when and where you want to deploy your Judo-based views, and those views can then seamlessly interact with the rest of your app. Your users won’t even be able to tell the difference between the views that you’ve built from scratch and those that are powered by Judo — since all views remain completely native. Once those views are in place, you’re then free to iterate on them however you wish, and you can instantly deploy your changes server-side, without requiring you to submit a new binary to the App Store. If this sounds interesting to you, then head over to judo.app/sundell to try Judo completely for free, and to help support Swift by Sundell in the process. * BASICS: EQUALITY * Basics * language features * value types * reference types Published on 04 Mar 2022 Checking whether two objects or values are considered equal is definitely one of the most commonly performed operations in all of programming. So, in this article, let’s take a look at how Swift models the concept of equality, and how that model varies between value and reference types. One the most interesting aspects of Swift’s implementation of equality is that it’s all done in a very protocol-oriented way — meaning that any type can become equatable by conforming to the Equatable protocol, which can be done like this: struct Article: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { lhs.title == rhs.title && lhs.body == rhs.body } var title: String var body: String } The way that we conform to Equatable in the above example is by implementing an overload of the == operator, which accepts the two values to compare (lhs, the left-hand side value, and rhs, the right-hand side value), and it then returns a boolean result as to whether those two values should be considered equal. The good news, though, is that we typically don’t have to write those kinds of == operator overloads ourselves, since the compiler is able to automatically synthesize such implementations whenever a type’s stored properties are all Equatable themselves. So in the case of the above Article type, we can actually remove our manual equality checking code, and simply make that type look like this: struct Article: Equatable { var title: String var body: String } The fact that Swift’s equality checks are so protocol-oriented also gives us a ton of power when working with generic types. For example, a collection of Equatable-conforming values (such as an Array or Set) are automatically considered equatable as well — without requiring any additional code on our part: let latestArticles = [ Article( title: "Writing testable code when using SwiftUI", body: "..." ), Article(title: "Combining protocols in Swift", body: "...") ] let basicsArticles = [ Article(title: "Loops", body: "..."), Article(title: "Availability checks", body: "...") ] if latestArticles == basicsArticles { ... } The way that those kinds of collection equality checks work is through Swift’s conditional conformances feature, which enables a type to conform to a specific protocol only when certain conditions are met. For example, here’s how Swift’s Array type conforms to Equatable only when the elements that are being stored within a given array are also, in turn, Equatable-conforming — which is what makes it possible for us to check whether two Article arrays are considered equal: extension Array where Element: Equatable { ... } Since none of the above logic is hard-coded into the compiler itself, we can also utilize that exact same conditional conformances-based technique if we wanted to make our own generic types conditionally equatable as well. For example, our code base might include some form of Group type that can be used to label a group of related values: struct Group<Value> { var label: String var values: [Value] } To make that Group type conform to Equatable when it’s being used to store Equatable values, we simply have to write the following empty extension, which looks almost identical to the Array extension that we took a look at above: extension Group: Equatable where Value: Equatable {} With the above in place, we can now check whether two Article-based Group values are equal, just like we could when using arrays: let latestArticles = Group( label: "Latest", values: [ Article( title: "Writing testable code when using SwiftUI", body: "..." ), Article(title: "Combining protocols in Swift", body: "...") ] ) let basicsArticles = Group( label: "Basics", values: [ Article(title: "Loops", body: "..."), Article(title: "Availability checks", body: "...") ] ) if latestArticles == basicsArticles { ... } Just like collections, Swift tuples can also be checked for equality whenever their stored values are all Equatable-conforming: let latestArticles = ( first: Article( title: "Writing testable code when using SwiftUI", body: "..." ), second: Article(title: "Combining protocols in Swift", body: "...") ) let basicsArticles = ( first: Article(title: "Loops", body: "..."), second: Article(title: "Availability checks", body: "...") ) if latestArticles == basicsArticles { ... } However, collections containing the above kind of equatable tuples do not automatically conform to Equatable. So, if we were to put the above two tuples into two identical arrays, then those wouldn’t be considered equatable: let firstArray = [latestArticles, basicsArticles] let secondArray = [latestArticles, basicsArticles] // Compiler error: Type '(first: Article, second: Article)' // cannot conform to 'Equatable': if firstArray == secondArray { ... } The reason why the above doesn’t work (at least not out of the box) is because — like the emitted compiler message alludes to — tuples can’t conform to protocols, which means that the Equatable-conforming Array extension that we took a look at earlier won’t take effect. There is a way to make the above work, though, and while I realize that the following generic code might not belong in an article labeled as ”Basics”, I still thought it would be worth taking a quick look at — since it illustrates just how flexible Swift’s equality checks are, and that we’re not just limited to implementing a single == overload in order to conform to Equatable. So if we were to add another, custom == overload, specifically for arrays that contain equatable two-element tuples, then the above code sample will actually compile successfully: extension Array { // This '==' overload will be used specifically when two // arrays containing two-element tuples are being compared: static func ==<A: Equatable, B: Equatable>( lhs: Self, rhs: Self ) -> Bool where Element == (A, B) { // First, we verify that the two arrays that are being // compared contain the same amount of elements: guard lhs.count == rhs.count else { return false } // We then "zip" the two arrays, which will give us // a collection where each element contains one element // from each array, and we then check that each of those // elements pass a standard equality check: return zip(lhs, rhs).allSatisfy(==) } } Above we can also see how Swift operators can be passed as functions, since we’re able to pass == directly to our call to allSatisfy. So far, we’ve been focusing on how value types (such as structs) behave when checked for equality, but what about reference types? For example, let’s say that we’ve now decided to turn our previous Article struct into a class instead, how would that impact its Equatable implementation? class Article: Equatable { var title: String var body: String init(title: String, body: String) { self.title = title self.body = body } } The first thing that we’ll notice when performing the above change is that the compiler is no longer able to automatically synthesize our type’s Equatable conformance — since that feature is limited to value types. So if we wanted our Article type to remain a class, then we’d have to manually implement the == overload that Equatable requires, just like we did at the beginning of this article: class Article: Equatable { static func ==(lhs: Article, rhs: Article) -> Bool { lhs.title == rhs.title && lhs.body == rhs.body } var title: String var body: String init(title: String, body: String) { self.title = title self.body = body } } However, classes that are subclasses of any kind of Objective-C-based class do inherit a default Equatable implementation from NSObject (which is the root base class for almost all Objective-C classes). So, if we were to make our Article class an NSObject subclass, then it would actually become Equatable without strictly requiring us to implement a custom == overload: class Article: NSObject { var title: String var body: String init(title: String, body: String) { self.title = title self.body = body super.init() } } While it might be tempting to use the above subclassing technique to avoid having to write custom equality checking code, it’s important to point out that the only thing that the default Objective-C-provided Equatable implementation will do is to check if two classes are the same instance — not if they contain the same data. So even though the following two Article instances have the same title and body, they won’t be considered equal when using the above NSObject-based approach: let articleA = Article(title: "Title", body: "Body") let articleB = Article(title: "Title", body: "Body") print(articleA == articleB) // false Performing those kinds of instance checks can be really useful, though — as sometimes we might want to be able to check whether two class-based references point to the same underlying instance. We don’t need our classes to inherit from NSObject to do that, though, since we can use Swift’s built-in triple-equals operator, ===, to perform such a check between any two references: let articleA = Article(title: "Title", body: "Body") let articleB = articleA print(articleA === articleB) // true To learn more about the above concept, check out “Identifying objects in Swift”. With that, I believe that we’ve covered all of the basics as to how equality works in Swift — for both values and objects, using either custom or automatically generated implementations, as well as how generics can be made conditionally equatable. If you have any questions, comments, or feedback, then feel free to reach out via either Twitter or email. Thanks for reading! Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * * Read more about Swift’s language features ENUMS WITH CUSTOM RAW TYPES Defining Swift enums based on completely custom types. * Another article about Swift’s language features HOW THE RAW VALUES OF INT-BASED ENUMS GET INCREMENTED And how that can be customized. * Continue exploring Swift’s language features WHEN CAN SWIFT’S RETURN KEYWORD BE OMITTED? * Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * PODCAST: “WHERE IS SWIFT HEADED IN 2022?” WITH SPECIAL GUEST JP SIMARD * what’s new * swift evolution * standard library Published on 27 Feb 2022 Listen using: Apple PodcastsOvercastCastroPocket CastsRSS On this 2022 season premiere, JP Simard returns to the show to discuss what’s next for Swift in 2022, and what kinds of improvements and new features that might be coming to the language during the year. SPONSORS * Judo: Quickly build native, server-driven UI for iOS and Android, and publish instantly, without having to submit updates to the App Store. Try it for free today, by going to judo.app/sundell. * Bitrise: Rock-solid continuous integration for your Swift project, which now offers 50% faster builds and ad-ons for things like automatic deployment. Go to bitrise.io/swift to get started for free. LINKS * JP on Twitter * John on Twitter * ZenTuner on the App Store * The source code for ZenTuner on GitHub * Property wrappers * Result builders * Generics * Opaque return types * Advent of Code * Codable * Swift Numerics * Swift Argument Parser * Swift Concurrency * The MainActor attribute * Sendable * Combine * Ted Kremenek’s “On the road to Swift 6” post * OpenCombine * The open source version of Foundation * Rust * Swift System * Distributed actors * Opaque parameter declarations * Swift’s new date/time API * Swift Markdown * Why can’t certain protocols be referenced directly? * Type placeholders * Adding SwiftUI’s ViewBuilder attribute to functions * JP on GitHub * JP’s website * Intro and outro music by Dariusz Dziuk * * THE STANDARD LIBRARY * Another episode about what’s new 39: “SUNDELL BY UNWRAPPED” A HOLIDAY SPECIAL FEATURING JP SIMARD AND JESSE SQUIRES * Read more about the standard library SLICING SWIFT COLLECTIONS Efficient ways of working with subsets of various collections using the standard library’s slicing APIs. * WRITING TESTABLE CODE WHEN USING SWIFTUI * swiftui * unit testing Published on 17 Feb 2022 Discover page available: SwiftUI A major part of the challenge of architecting UI-focused code bases tends to come down to deciding where to draw the line between the code that needs to interact with the platform’s various UI frameworks, versus code that’s completely within our own app’s domain of logic. That task might become especially tricky when working with SwiftUI, as so much of our UI-centric logic tends to wind up within our various View declarations, which in turn often makes such code really difficult to verify using unit tests. So, in this article, let’s take a look at how we could deal with that problem, and explore how to make UI-related logic fully testable — even when that logic is primarily used within SwiftUI-based views. Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. LOGIC INTERTWINED WITH VIEWS “You shouldn’t put business logic within your views”, is a piece of advice that’s often mentioned when discussing unit testing within the context of UI-based projects, such as iOS and Mac apps. However, in practice, that advice can sometimes be tricky to follow, as the most natural or intuitive place to put view-related logic is often within the views themselves. As an example, let’s say that we’re working on an app that contains the following SendMessageView. Although the actual message sending logic (and its associated networking) has already been abstracted using a MessageSender protocol, all of the UI-specific logic related to sending messages is currently embedded right within our view: struct SendMessageView: View { var sender: MessageSender @State private var message = "" @State private var isSending = false @State private var sendingError: Error? var body: some View { VStack { Text("Your message:") TextEditor(text: $message) Button(isSending ? "Sending..." : "Send") { isSending = true sendingError = nil Task { do { try await sender.sendMessage(message) message = "" } catch { sendingError = error } isSending = false } } .disabled(isSending || message.isEmpty) if let error = sendingError { Text(error.localizedDescription) .foregroundColor(.red) } } } } At first glance, the above might not look so bad. Our view isn’t massive by any stretch of the imagination, and the code is quite well-organized. However, unit testing that view’s logic would currently be incredibly difficult — as we’d have to find some way to spin up our view within our tests, then find its various UI controls (such as its “Send” button), and then figure out a way to trigger and observe those views ourselves. Because we have to remember that SwiftUI views aren’t actual, concrete representations of the UI that we’re drawing on-screen, which can then be controlled and inspected as we wish. Instead, they’re ephemeral descriptions of what we want our various views to look like, which the system then renders and manages on our behalf. So, although we could most likely find a way to unit test our SwiftUI views directly — ideally, we’ll probably want to verify our logic in a much more controlled, isolated environment. One way to create such an isolated environment would be to extract all of the logic that we’re looking to test out from our views, and into objects and functions that are under our complete control — for example by using a view model. Here’s what such a view model could end up looking like if we were to move all of our message sending UI logic out from our SendMessageView: @MainActor class SendMessageViewModel: ObservableObject { @Published var message = "" @Published private(set) var errorText: String? var buttonTitle: String { isSending ? "Sending..." : "Send" } var isSendingDisabled: Bool { isSending || message.isEmpty } private let sender: MessageSender private var isSending = false init(sender: MessageSender) { self.sender = sender } func send() { guard !message.isEmpty else { return } guard !isSending else { return } isSending = true errorText = nil Task { do { try await sender.sendMessage(message) message = "" } catch { errorText = error.localizedDescription } isSending = false } } } To learn more about constructing and using observable objects, check out this guide to SwiftUI’s state management system. Our logic remains almost identical, but the above refactor does give us two quite significant benefits. First, we’ll now be able to unit test our code without having to worry about SwiftUI at all. And second, we’ll even be able to improve our SwiftUI view itself, as our view model now contains all of the logic that our view needs to decide how it should be rendered — making that UI code much simpler in the process: struct SendMessageView: View { @ObservedObject var viewModel: SendMessageViewModel var body: some View { VStack(alignment: .leading) { Text("Your message:") TextEditor(text: $viewModel.message) Button(viewModel.buttonTitle) { viewModel.send() } .disabled(viewModel.isSendingDisabled) if let errorText = viewModel.errorText { Text(errorText).foregroundColor(.red) } } } } Fantastic! To now shift our focus to unit testing our code, we’re going to need two pieces of infrastructure before we can actually start writing our test cases. While those two pieces are not strictly required, they’re going to help us make our testing code so much simpler and easier to read. INVESTING IN UTILITIES First, let’s create a mocked implementation of our MessageSender protocol, which will enable us to gain complete control over how messages are sent, as well as how errors are thrown during that process: class MessageSenderMock: MessageSender { @Published private(set) var pendingMessageCount = 0 private var pendingMessageContinuations = [CheckedContinuation<Void, Error>]() func sendMessage(_ message: String) async throws { return try await withCheckedThrowingContinuation { continuation in pendingMessageContinuations.append(continuation) pendingMessageCount += 1 } } func sendPendingMessages() { let continuations = pendingMessageContinuations pendingMessageContinuations = [] pendingMessageCount = 0 continuations.forEach { $0.resume() } } func triggerError(_ error: Error) { let continuations = pendingMessageContinuations pendingMessageContinuations = [] pendingMessageCount = 0 continuations.forEach { $0.resume(throwing: error) } } } To learn more about Swift’s continuation system, check out “Connecting async/await to other Swift code”. Next, since the code that we’re looking to verify is asynchronous, we’re going to need a way to wait for a given state to be entered before proceeding with our verifications. Since we don’t want to put any observation logic within our tests themselves, let’s extend XCTestCase with a method that’ll let us wait until a given @Published-marked property has been assigned a specific value: extension XCTestCase { func waitUntil<T: Equatable>( _ propertyPublisher: Published<T>.Publisher, equals expectedValue: T, timeout: TimeInterval = 10, file: StaticString = #file, line: UInt = #line ) { let expectation = expectation( description: "Awaiting value \(expectedValue)" ) var cancellable: AnyCancellable? cancellable = propertyPublisher .dropFirst() .first(where: { $0 == expectedValue }) .sink { value in XCTAssertEqual(value, expectedValue, file: file, line: line) cancellable?.cancel() expectation.fulfill() } waitForExpectations(timeout: timeout, handler: nil) } } Above we’re using Apple’s Combine framework to observe the injected Published property’s publisher (wow, that’s quite a tongue twister, isn’t it?). To learn more about Combine, and published properties in particular, check out this Discover page. With those two pieces in place, we can now finally start writing the unit tests for our UI-related message sending logic! It might, at first, seem rather unnecessary to create all of that infrastructure just to be able to verify some simple pieces of logic — but the utilities that we’ve now created will really make our testing code much easier (and more enjoyable) to write. IT’S TESTING TIME! Let’s start by verifying that our “Send” button will be correctly enabled and disabled based on whether the user has entered a message. To do that, we’ll start by setting up an XCTestCase subclass for our tests, and we’ll then be able to easily simulate a message being entered simply by assigning a string to our view model’s message property: @MainActor class SendMessageViewModelTests: XCTestCase { private var sender: MessageSenderMock! private var viewModel: SendMessageViewModel! @MainActor override func setUp() { super.setUp() sender = MessageSenderMock() viewModel = SendMessageViewModel(sender: sender) } func testSendingDisabledWhileMessageIsEmpty() { XCTAssertTrue(viewModel.isSendingDisabled) viewModel.message = "Message" XCTAssertFalse(viewModel.isSendingDisabled) viewModel.message = "" XCTAssertTrue(viewModel.isSendingDisabled) } } Note that we need to add the MainActor attribute to both our test case itself, as well as to the setUp method that we’re overriding from the XCTestCase base class. Otherwise we wouldn’t be able to easily interact with our view model’s APIs, since those are also bound to the main actor. To learn more, check out this article. Alright, our first test is done, but we’re just getting started. Next, let’s verify that the correct states are entered while sending a message — and this is where the two utilities that we built earlier (our MessageSenderMock class and our waitUntil method) will come very much in handy: @MainActor class SendMessageViewModelTests: XCTestCase { ... func testSuccessfullySendingMessage() { // First, start sending a message, and verify the current state: viewModel.message = "Message" viewModel.send() waitUntil(sender.$pendingMessageCount, equals: 1) XCTAssertEqual(viewModel.buttonTitle, "Sending...") XCTAssertTrue(viewModel.isSendingDisabled) // Then, finish sending the message, and verify the end state: sender.sendPendingMessages() waitUntil(viewModel.$message, equals: "") XCTAssertEqual(viewModel.buttonTitle, "Send") } } That’s really the power of investing in testing infrastructure like mocks and various utility functions — they let our testing methods remain completely linear, and free from cancellables, expectations, and other sources of complexity. Let’s write one more test. This time, we’ll verify that our code behaves correctly when an error was encountered: @MainActor class SendMessageViewModelTests: XCTestCase { ... func testHandlingMessageSendingError() { // First, start sending a message: viewModel.message = "Message" viewModel.send() waitUntil(sender.$pendingMessageCount, equals: 1) // Then, make the sender throw an error and verify it: let error = URLError(.badServerResponse) sender.triggerError(error) waitUntil(viewModel.$errorText, equals: error.localizedDescription) XCTAssertEqual(viewModel.message, "Message") XCTAssertEqual(viewModel.buttonTitle, "Send") XCTAssertFalse(viewModel.isSendingDisabled) } } Just like that, we’ve now fully covered our UI-related message sending logic with unit tests — without having to actually attempt to unit test our SwiftUI view itself. As an added bonus, we also made our view code simpler in the process, and it should now be much easier to iterate on our view’s logic and styling completely separately. Combine the above approach with a few UI tests, as well as manual testing, and we should be able to release new versions of our app with confidence. IS MVVM REQUIRED FOR TESTABILITY? Now, is the point of the above series of examples that all SwiftUI-based apps should completely adopt the MVVM (Model-View-ViewModel) architecture? No, absolutely not. Instead, the point is that the easiest way to unit test any kind of UI-related code (regardless of what UI framework that the code was originally written against) is most often to move that code out from whatever view that it’s being consumed in. That way, our logic is no longer tied to any specific UI framework, and we’re free to test and manage it however we’d like. To further prove that this article isn’t about advocating for “MVVM all the things!”, let’s take a look at another example, in which using a view model would probably be quite unnecessary. Here we’ve written an EventSelectionView, which also has a significant piece of logic embedded within it — this time for deciding whether a given Event should be auto-selected when the user taps a button: struct EventSelectionView: View { var events: [Event] @Binding var selection: Event? var body: some View { List(events) { event in ... } .toolbar { Button("Select next available") { selection = events.first(where: { event in guard event.isBookable else { return false } guard event.participants.count < event.capacity else { return false } return event.startDate > .now }) } } } } Just like when we refactored our SendMessageView earlier, one way to make the above logic testable would be to create another view model, and move our logic there. But, let’s take a different (more lightweight) approach this time, and instead move that logic into our Event type itself: extension Event { var isSelectable: Bool { guard isBookable else { return false } guard participants.count < capacity else { return false } return startDate > .now } } After all, the above logic isn’t very UI-related at all (it doesn’t mutate any form of view state, and it just inspects properties that are owned by Event itself), so it doesn’t really warrant the creation of a dedicated view model. And, even without a view model, we can still fully test the above code, simply by creating and mutating an Event value: class EventTests: XCTestCase { private var event: Event! override func setUp() { super.setUp() event = Event( id: UUID(), capacity: 1, isBookable: true, startDate: .distantFuture ) } func testEventIsSelectableByDefault() { XCTAssertTrue(event.isSelectable) } func testUnBookableEventIsNotSelectable() { event.isBookable = false XCTAssertFalse(event.isSelectable) } func testFullyBookedEventIsNotSelectable() { event.participants = [.stub()] XCTAssertFalse(event.isSelectable) } func testPastEventIsNotSelectable() { event.startDate = .distantPast XCTAssertFalse(event.isSelectable) } } Just like before, a big benefit of performing the above kind of logic extraction is that doing so also tends to make our SwiftUI-based code much simpler. Thanks to our new Event extension, EventSelectionView can now simply use Swift’s key path syntax to pick the first selectable event — like this: struct EventSelectionView: View { var events: [Event] @Binding var selection: Event? var body: some View { List(events) { event in ... } .toolbar { Button("Select next available") { selection = events.first(where: \.isSelectable) } } } } So, regardless of whether we choose to go for a view model, a simple model extension, or another kind of metaphor — if we can move the UI logic that we’re looking to test out from our views themselves, then those tests tend to be much easier to write and maintain. Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. CONCLUSION So, how do I unit test my SwiftUI views? The answer is quite simply: I don’t. I also almost never test my UIView implementations either. Instead, I focus on extracting all of the logic that I wish to test out from my views and into objects that are under my complete control. That way, I can spend less time fighting with Apple’s UI frameworks in order to make them unit testing-friendly, and more time writing solid, reliable tests. I hope that this article has given you a few ideas on how you could make your SwiftUI-based code easier to test as well. If you have any questions, comments, or feedback, then feel free to reach out via either Twitter or email. Thanks for reading! * * SWIFTUI Get the most out of Apple’s new UI framework. * UNIT TESTING Building fast and stable unit testing suites using XCTest. * Listen to a podcast episode about swiftui 98: “AN ENTIRE SPECTRUM OF APPS” WITH SPECIAL GUEST SEAN ALLEN * Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * COMBINING PROTOCOLS IN SWIFT * protocols * generics Published on 09 Feb 2022 Basics article available: Protocols One of the core strengths of Swift’s protocols is that they enable us to define shared interfaces that multiple types can conform to, which in turn lets us interact with those types in a very uniform way, without necessarily knowing what underlying type that we’re currently dealing with. For example, to clearly define an API that enables us to persist a given instance onto disk, we might choose to use a protocol that looks something like this: protocol DiskWritable { func writeToDisk(at url: URL) throws } One advantage of defining commonly used APIs that way is that it helps us keep our code consistent, as we can now make any type that should be disk-writable conform to the above protocol, which then requires us to implement the exact same method for all such types. Another big advantage of Swift protocols is that they’re extendable, which makes it possible for us to define all sorts of convenience APIs for both our own protocols, as well as those that are defined externally — for example within the standard library, or within any framework that we’ve imported. When writing those kinds of convenience APIs, we might also want to mix the protocol that we’re currently extending with some functionality provided by another protocol. For example, let’s say that we wanted to provide a default implementation of our DiskWritable protocol’s writeToDisk method for types that also conform to the Encodable protocol — since a type that’s encodable can be transformed into Data, which we could then automatically write to disk. One way to make that happen would be to make our DiskWritable protocol inherit from Encodable, which in turn will require all conforming types to implement both of those two protocols’ requirements. We could then simply extend DiskWritable in order to add that default implementation of writeToDisk that we were looking to provide: protocol DiskWritable: Encodable { func writeToDisk(at url: URL) throws } extension DiskWritable { func writeToDisk(at url: URL) throws { let encoder = JSONEncoder() let data = try encoder.encode(self) try data.write(to: url) } } While powerful, the above approach does have a quite significant downside, in that we’ve now completely coupled our DiskWritable protocol with Encodable — meaning that we can no longer use that protocol by itself, without also requiring any conforming type to also fully implement Encodable, which might become problematic. Another, much more flexible approach would be to let DiskWritable remain a completely stand-alone protocol, and instead write a type-constrained extension that only adds our default writeToDisk implementation to types that also conform to Encodable separately — like this: extension DiskWritable where Self: Encodable { func writeToDisk(at url: URL) throws { let encoder = JSONEncoder() let data = try encoder.encode(self) try data.write(to: url) } } The tradeoff here is that the above approach does require each type that wants to leverage our default writeToDisk implementation to explicitly conform to both DiskWritable and Encodable, which might not be a big deal, but it could make it a bit harder to discover that default implementation — since it’s no longer automatically available on all DiskWritable-conforming types. One way to address that discoverability issue, though, could be to create a convenience type alias (using Swift’s protocol composition operator, &) that gives us an indication that DiskWritable and Encodable can be combined to unlock new functionality: typealias DiskWritableByEncoding = DiskWritable & Encodable When a type conforms to those two protocols (either using the above type alias, or completely separately), it’ll now get access to our default writeToDisk implementation (while still having the option to provide its own, custom implementation as well): struct TodoList: DiskWritableByEncoding { var name: String var items: [Item] ... } let list = TodoList(...) try list.writeToDisk(at: fileURL) Combining protocols like that can be a really powerful technique, as we’re not just limited to adding default implementations of protocol requirements — we can also add brand new APIs to any protocol combination, simply by adding new methods or computed properties within one of our extensions. For example, here we’ve added a second overload of our writeToDisk method, which makes it possible to pass a custom JSONEncoder that’ll be used when serializing the current instance: extension DiskWritable where Self: Encodable { func writeToDisk(at url: URL, encoder: JSONEncoder) throws { let data = try encoder.encode(self) try data.write(to: url) } func writeToDisk(at url: URL) throws { try writeToDisk(at: url, encoder: JSONEncoder()) } } We do have to be bit careful not to over-use the above pattern, though, since doing so could introduce conflicts if a given type ends up getting access to multiple default implementations of the same method. To illustrate, let’s say that our code base also contains a DataConvertible protocol, which we’d like to extend with a similar, default implementation of writeToDisk — like this: protocol DataConvertible { func convertToData() throws -> Data } extension DiskWritable where Self: DataConvertible { func writeToDisk(at url: URL) throws { let data = try convertToData() try data.write(to: url) } } While both of the two DiskWritable extensions that we’ve now created make perfect sense in isolation, we’ll now end up with a conflict if a given DiskWritable-conforming type also wants to conform to both Encodable and DataConvertible at the same time (which is highly likely, since both of those protocols are about transforming an instance into Data). Since the compiler won’t be able to pick which default implementation to use in cases like that, we’d have to manually implement our writeToDisk method specifically for each of those conflicting types. Not a big problem, perhaps, but it could lead us to a situation where it’s hard to tell which method implementation that will be used for which type, which in turn could make our code feel quite unpredictable and harder to debug and maintain. So let’s also explore one final, alternative approach to the above set of problems — which would be to implement our disk-writing convenience APIs within a dedicated type, rather than using protocol extensions. For example, here’s how we could define an EncodingDiskWriter, which only requires the types that it’ll be used with to conform to Encodable, since the writer itself conforms to DiskWritable: struct EncodingDiskWriter<Value: Encodable>: DiskWritable { var value: Value var encoder = JSONEncoder() func writeToDisk(at url: URL) throws { let data = try encoder.encode(value) try data.write(to: url) } } So even though the following Document type doesn’t conform to DiskWritable, we can still easily write its data to disk using our new EncodingDiskWriter: struct Document: Identifiable, Codable { let id: UUID var name: String ... } class EditorViewController: UIViewController { private var document: Document private var fileURL: URL ... private func save() throws { let writer = EncodingDiskWriter(value: document) try writer.writeToDisk(at: fileURL) } } So, although protocol extensions provide us with an incredibly powerful set of tools, it’s always important to remember that there are other alternatives that might be a better fit for what we’re trying to build. Like with so many things in programming, there are no right or wrong answers here, but I hope that this article has shown a few different ways to combine the functionality of multiple protocols, and what sort of tradeoffs that each approach comes with. If you have any questions, comments, or feedback, then feel free to send me an email, or reach out via Twitter. Thanks for reading! Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * * GENERICS Learn how to fully utilize Swift’s powerful generics system. * Read more about protocols ASSIGNING TO SELF IN STRUCT INITIALIZERS * Another article about protocols IMPLEMENTING THROWING PROTOCOL FUNCTIONS AS NON-THROWING * MEMORY MANAGEMENT WHEN USING ASYNC/AWAIT IN SWIFT * concurrency * memory management Published on 03 Feb 2022 Discover page available: Concurrency Managing an app’s memory is something that tends to be especially tricky to do within the context of asynchronous code, as various objects and values often need to be captured and retained over time in order for our asynchronous calls to be performed and handled. While Swift’s relatively new async/await syntax does make many kinds of asynchronous operations easier to write, it still requires us to be quite careful when it comes to managing the memory for the various tasks and objects that are involved in such asynchronous code. Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. IMPLICIT CAPTURES One interesting aspect of async/await (and the Task type that we need to use to wrap such code when calling it from a synchronous context) is how objects and values often end up being implicitly captured while our asynchronous code is being executed. For example, let’s say that we’re working on a DocumentViewController, which downloads and displays a Document that was downloaded from a given URL. To make our download execute lazily when our view controller is about to be displayed to the user, we’re starting that operation within our view controller’s viewWillAppear method, and we’re then either rendering the downloaded document once available, or showing any error that was encountered — like this: class DocumentViewController: UIViewController { private let documentURL: URL private let urlSession: URLSession ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Task { do { let (data, _) = try await urlSession.data(from: documentURL) let decoder = JSONDecoder() let document = try decoder.decode(Document.self, from: data) renderDocument(document) } catch { showErrorView(for: error) } } } private func renderDocument(_ document: Document) { ... } private func showErrorView(for error: Error) { ... } } Now, if we just quickly look at the above code, it might not seem like there’s any object capturing going on whatsoever. After all, asynchronous capturing has traditionally only happened within escaping closures, which in turn require us to always explicitly refer to self whenever we’re accessing a local property or method within such a closure (when self refers to a class instance, that is). So we might expect that if we start displaying our DocumentViewController, but then navigate away from it before its download has completed, that it’ll be successfully deallocated once no external code (such as its parent UINavigationController) maintains a strong reference to it. But that’s actually not the case. That’s because of the aforementioned implicit capturing that happens whenever we create a Task, or use await to wait for the result of an asynchronous call. Any object used within a Task will automatically be retained until that task has finished (or failed), including self whenever we’re referencing any of its members, like we’re doing above. In many cases, this behavior might not actually be a problem, and will likely not lead to any actual memory leaks, since all captured objects will eventually be released once their capturing task has completed. However, let’s say that we’re expecting the documents downloaded by our DocumentViewController to potentially be quite large, and that we wouldn’t want multiple view controllers (and their download operations) to remain in memory if the user quickly navigates between different screens. The classic way to address this sort of problem would be to perform a weak self capture, which is often accompanied by a guard let self expression within the capturing closure itself — in order to turn that weak reference into a strong one that can then be used within the closure’s code: class DocumentViewController: UIViewController { ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Task { [weak self] in guard let self = self else { return } do { let (data, _) = try await self.urlSession.data( from: self.documentURL ) let decoder = JSONDecoder() let document = try decoder.decode(Document.self, from: data) self.renderDocument(document) } catch { self.showErrorView(for: error) } } } ... } Unfortunately, that’s not going to work in this case, as our local self reference will still be retained while our asynchronous URLSession call is suspended, and until all of our closure’s code has finished running (just like how a local variable within a function is retained until that scope has been exited). So if we truly wanted to capture self weakly, then we’d have to consistently use that weak self reference throughout our closure. To make it somewhat simpler to use our urlSession and documentURL properties, we could capture those separately, as doing so won’t prevent our view controller itself from being deallocated: class DocumentViewController: UIViewController { ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Task { [weak self, urlSession, documentURL] in do { let (data, _) = try await urlSession.data(from: documentURL) let decoder = JSONDecoder() let document = try decoder.decode(Document.self, from: data) self?.renderDocument(document) } catch { self?.showErrorView(for: error) } } } ... } The good news is that, with the above in place, our view controller will now be successfully deallocated if it ends up being dismissed before its download has completed. However, that doesn’t mean that its task will automatically be cancelled. That might not be a problem in this particular case, but if our network call resulted in some kind of side-effect (like a database update), then that code would still run even after our view controller would be deallocated, which could result in bugs or unexpected behavior. CANCELLING TASKS One way to ensure that any ongoing download task will indeed be cancelled once our DocumentViewController goes out of memory would be to store a reference to that task, and to then call its cancel method when our view controller is being deallocated: class DocumentViewController: UIViewController { private let documentURL: URL private let urlSession: URLSession private var loadingTask: Task<Void, Never>? ... deinit { loadingTask?.cancel() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) loadingTask = Task { [weak self, urlSession, documentURL] in ... } } ... } Now everything works as expected, and all of our view controller’s memory and asynchronous state will automatically be cleaned up once it has been dismissed — but our code has also become quite complicated in the process. Having to write all of that memory management code for every view controller that performs an asynchronous task would be quite tedious, and it could even make us question whether async/await actually gives us any real benefits over technologies like Combine, delegates, or closures. Thankfully, there’s another way to implement the above pattern that doesn’t involve quite as much code and complexity. Since the convention is for long-running async methods to throw an error if they get cancelled (see this article about delaying asynchronous tasks for more info), we can simply cancel our loadingTask once our view controller is about to be dismissed — and that will make our task throw an error, exit, and release all of its captured objects (including self). That way, we no longer need to capture self weakly, or do any other kind of manual memory management work — giving us the following implementation: class DocumentViewController: UIViewController { private let documentURL: URL private let urlSession: URLSession private var loadingTask: Task<Void, Never>? ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) loadingTask = Task { do { let (data, _) = try await urlSession.data(from: documentURL) let decoder = JSONDecoder() let document = try decoder.decode(Document.self, from: data) renderDocument(document) } catch { showErrorView(for: error) } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) loadingTask?.cancel() } ... } Note that when our task is cancelled, our showErrorView method will now still be called (since an error will be thrown, and self remains in memory at that point). However, that extra method call should be completely negligible in terms of performance. LONG-RUNNING OBSERVATIONS The above set of memory management techniques should become even more important once we start using async/await to set up long-running observations of some kind of async sequence or stream. For example, here we’re making a UserListViewController observe a UserList class in order to reload its table view data once an array of User models was changed: class UserList: ObservableObject { @Published private(set) var users: [User] ... } class UserListViewController: UIViewController { private let list: UserList private lazy var tableView = UITableView() ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Task { for await users in list.$users.values { updateTableView(withUsers: users) } } } private func updateTableView(withUsers users: [User]) { ... } } To learn more about Published properties, check out this article. Note that the above implementation currently doesn’t include any of the task cancellation logic that we previously implemented within our DocumentViewController, which in this case will actually lead to a memory leak. The reason is that (unlike our previous Document-loading task) our UserList observation task will keep running indefinitely, as it’s iterating over a Publisher-based async sequence that can’t throw an error, or complete in any other way. The good news is that we can easily fix the above memory leak using the exact same technique as we previously used to prevent our DocumentViewController from being retained in memory — that is, to cancel our observation task once our view controller is about to disappear: class UserListViewController: UIViewController { private let list: UserList private lazy var tableView = UITableView() private var observationTask: Task<Void, Never>? ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) observationTask = Task { for await users in list.$users.values { updateTableView(withUsers: users) } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) observationTask?.cancel() } ... } Note that performing the above cancellation within deinit wouldn’t work in this case, since we’re dealing with an actual memory leak — meaning that deinit will never be called unless we break our observation task’s endless loop. Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. CONCLUSION At first, it might seem like technologies like Task and async/await make asynchronous, memory-related issues a thing of the past, but unfortunately we still have to be careful around how objects are captured and retained when performing various kinds of async-marked calls. While actual memory leaks and retain cycles are perhaps not as easily encountered as when using things like Combine or closures, we still have to ensure that our objects and tasks are managed in a way that makes our code robust and easy to maintain. I hope that you found this article useful. If you did, please share it! If you have any questions, comments, or feedback, then feel free to reach out via either Twitter or email. Thanks for reading! * * Watch a short video clip about concurrency SWIFT CLIP: DISPATCH QUEUES Writing asynchronous code using dispatch queues and work items. * Read more about memory management MEMORY MANAGEMENT * Another article about concurrency USING THE MAINACTOR ATTRIBUTE TO AUTOMATICALLY DISPATCH UI UPDATES ON THE MAIN QUEUE * Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * AUTOMATICALLY RETRYING AN ASYNCHRONOUS SWIFT TASK * concurrency Published on 25 Jan 2022 Discover page available: Concurrency Sometimes, we might want to automatically retry an asynchronous operation that failed, for example in order to work around temporary network problems, or to re-establish some form of connection. Here we’re doing just that when using Apple’s Combine framework to implement a network call, which we’ll retry up to 3 times before handling any error that was encountered: struct SettingsLoader { var url: URL var urlSession = URLSession.shared var decoder = JSONDecoder() func load() -> AnyPublisher<Settings, Error> { urlSession .dataTaskPublisher(for: url) .map(\.data) .decode(type: Settings.self, decoder: decoder) .retry(3) .eraseToAnyPublisher() } } Note that the above example will unconditionally retry our loading operation (up to 3 times) regardless of what kind of error that was thrown. But what if we wanted to implement something similar, but using Swift Concurrency instead? While Combine’s Publisher protocol includes the above retry operator as a built-in API, neither of Swift’s new concurrency APIs offer something similar (at least not at the time of writing), so we’ll have to get creative! One really neat aspect of Swift’s new concurrency system, and async/await in particular, is that it enables us to mix various asynchronous calls with standard control flow constructs, such as if statements and for loops. So, one way to implement automatic retries for await-marked calls would be to place the asynchronous code that we want to run within a loop that iterates over a range, which in turn describes how many retries that we wish to perform — like this: struct SettingsLoader { var url: URL var urlSession = URLSession.shared var decoder = JSONDecoder() func load() async throws -> Settings { // Perform 3 attempts, and retry on any failure: for _ in 0..<3 { do { return try await performLoading() } catch { // This 'continue' statement isn't technically // required, but makes our intent more clear: continue } } // The final attempt (which throws its error if it fails): return try await performLoading() } private func performLoading() async throws -> Settings { let (data, _) = try await urlSession.data(from: url) return try decoder.decode(Settings.self, from: data) } } The above implementation works perfectly fine, but if we’re looking to add the same kind of retrying logic in multiple places throughout a project, then it might be worth moving that code into some form of utility that could be easily reused. One way to do just that would be to extend Swift’s Task type with a convenience API that lets us quickly create such auto-retrying tasks. Our actual logic can remain almost identical to what it was before, but we’ll parameterize the maximum number of retries, and we’ll also add support for cancellation as well: extension Task where Failure == Error { @discardableResult static func retrying( priority: TaskPriority? = nil, maxRetryCount: Int = 3, operation: @Sendable @escaping () async throws -> Success ) -> Task { Task(priority: priority) { for _ in 0..<maxRetryCount { try Task<Never, Never>.checkCancellation() do { return try await operation() } catch { continue } } try Task<Never, Never>.checkCancellation() return try await operation() } } } That’s already a really useful, and completely reusable implementation, but let’s take things one step further, shall we? When retrying asynchronous operations, it’s very common to want to add a bit of delay between each retry — perhaps in order to give an external system (such as a server) a chance to recover from some kind of error before we make another attempt at calling it. So let’s also add support for such delays, which can easily be done using the built-in Task.sleep API: extension Task where Failure == Error { @discardableResult static func retrying( priority: TaskPriority? = nil, maxRetryCount: Int = 3, retryDelay: TimeInterval = 1, operation: @Sendable @escaping () async throws -> Success ) -> Task { Task(priority: priority) { for _ in 0..<maxRetryCount { do { return try await operation() } catch { let oneSecond = TimeInterval(1_000_000_000) let delay = UInt64(oneSecond * retryDelay) try await Task<Never, Never>.sleep(nanoseconds: delay) continue } } try Task<Never, Never>.checkCancellation() return try await operation() } } } Note how we can now remove the checkCancellation call at the start of our for loop, since our Task.sleep call will automatically throw an error if the task was cancelled. To learn more about delaying Task instances, check out “Delaying an asynchronous Swift Task”. If we wanted to, we could’ve also added the “semi-public” @_implicitSelfCapture attribute to our operation closure, which would give it the same implicit-self-capturing behavior as when passing a closure directly to the Task type itself. However, that’s not really something that I recommend doing (given that underscored attributes can change at any point), so let’s instead wrap things up by refactoring the SettingsLoader example from before to instead use our new Task extension to perform its retries: struct SettingsLoader { var url: URL var urlSession = URLSession.shared var decoder = JSONDecoder() func load() async throws -> Settings { try await Task.retrying { let (data, _) = try await urlSession.data(from: url) return try decoder.decode(Settings.self, from: data) } .value } } Very nice! Note how we can use a given task’s value property to observe its returned value (or re-throw any error that was thrown within the task itself). Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. CONCLUSION There are of course lots of ways that we could take this article’s Task extension further — for example by making it possible to only retry on certain errors (perhaps by enabling us to pass something like an retryPredicate closure when creating our task?) — but I hope that this article has given you a few ideas on how you could implement auto-retrying tasks within your projects. If you have any questions, comments, or feedback, then feel free to reach out via either Twitter or email. Thanks for reading! * * CONCURRENCY Explore Swift’s built-in concurrency system. * Listen to a podcast episode about concurrency 108: “CONCURRENCY AND SWIFT-DOCC” WITH SPECIAL GUEST MARIN TODOROV * Read more about concurrency BUILDING AN ASYNCHRONOUS SWIFTUI BUTTON * BACKGROUNDS AND OVERLAYS IN SWIFTUI * swiftui Published on 20 Jan 2022 Discover page available: SwiftUI SwiftUI offers several different ways for us to create stacks of overlapping views that can be arranged along the Z axis, which in turn enables us to define various kinds of overlays and backgrounds for the views that we build. Let’s explore some of those built-in stacking methods and what sort of UIs that they enable us to create. Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. ZSTACKS Like its name implies, SwiftUI’s ZStack type is the Z-axis equivalent of the horizontally-oriented HStack and the vertical VStack. When placing multiple views within a ZStack, they’re (by default) rendered back-to-front, with the first view being placed at the back. For example, here we’re creating a full-screen ContentView, which renders a gradient with a text stacked on top: Preview struct ContentView: View { var body: some View { ZStack { LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) .ignoresSafeArea() Text("Swift by Sundell") .foregroundColor(.white) .font(.title) } } } Tip: You can use the above code sample’s PREVIEW button to see what it’ll look like when rendered. The reason that the above ContentView is rendered across all of the available screen space is because a LinearGradient will always occupy as much space as possible by default, and since a any stack’s size defaults to the total size of its children, that leads to our ZStack being resized to occupy that same full-screen space. THE BACKGROUND MODIFIER However, sometimes we might not want a given background to stretch out to fill all available space, and while we could address that by applying various sizing modifiers to our background view, SwiftUI ships with a built-in tool that automatically resizes a given view’s background to perfectly fit its parent — the background modifier. Here’s how we could use that modifier to instead apply our LinearGradient background directly to our Text-based view, which makes that background take on the exact same size as our text itself (including its padding): Preview struct ContentView: View { var body: some View { Text("Swift by Sundell") .foregroundColor(.white) .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) ) } } The reason that the padding is included when calculating our background’s size in the above example is because we’re applying the padding modifier before adding our background. To learn more about that, check out “When does the order of SwiftUI modifiers matter, and why?”. One thing that’s important to point out, though, is that even though a view’s background does indeed get resized according to the parent view itself, there’s no form of clipping applied by default. So if we were to give our LinearGradient an explicit size that’s larger than its parent, then it’ll actually be rendered out of bounds (which we can clearly demonstrate by adding a border to our main Text-based view): Preview struct ContentView: View { var body: some View { Text("Swift by Sundell") .foregroundColor(.white) .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) .frame(width: 300, height: 300) ) .border(Color.blue) } } There are multiple ways to apply clipping to a view, though, which would remove the above sort of out-of-bounds rendering. For example, we could use either the clipped or clipShape modifier to tell the view to apply a clipping mask to its bounds, or we could give our view rounded corners (which also introduces clipping) — like this: Preview struct ContentView: View { var body: some View { Text("Swift by Sundell") .foregroundColor(.white) .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) .frame(width: 300, height: 300) ) .cornerRadius(20) } } Of course, the simplest way to avoid drawing a background outside of the bounds of its parent view is to simply let the SwiftUI layout system automatically determine the size of each background. That way, the size of a given background will always perfectly match the size of its parent view. ASSIGNING OVERLAYS SwiftUI also supports adding overlays to views as well, which essentially act as the inverse of backgrounds — in that they’re rendered on top of their parent views (with the same sizing behaviors as we explored above). Both overlays and backgrounds also support alignment customization, which lets us decide how such a view should be placed within its parent’s coordinate system. For views that are fully resizable (like our above LinearGradient), the alignment doesn’t matter (since those views will be resized to fit their parent view anyway), but for smaller views, specifying an alignment lets us move a view to any of its parent’s corners. For example, here’s how we could add a star image overlay to the top-trailing corner of our ContentView: Preview struct ContentView: View { var body: some View { Text("Swift by Sundell") .foregroundColor(.white) .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .overlay(starOverlay, alignment: .topTrailing) .cornerRadius(20) } private var starOverlay: some View { Image(systemName: "star") .foregroundColor(.white) .padding([.top, .trailing], 5) } } Specifying an alignment for a background is done the exact same way, by passing an alignment argument when using the background modifier. An overlay or background also inherits all of its parent’s environment values. In the case of our ContentView example, that means that we don’t actually have to apply the same foregroundColor modifier twice, like we’re doing above (since foreground colors automatically become part of the SwiftUI environment). So if we instead apply that modifier after we’ve added our overlay, then that same color will be applied to both our text and our star icon: struct ContentView: View { var body: some View { Text("Swift by Sundell") .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .overlay(starOverlay, alignment: .topTrailing) .foregroundColor(.white) .cornerRadius(20) } private var starOverlay: some View { Image(systemName: "star") .padding([.top, .trailing], 5) } } CONDITIONAL OVERLAYS AND BACKGROUNDS Sometimes, though, we might only want to apply a given overlay (or background) under certain conditions. For example, let’s say that we wanted to add a second overlay that displays a progress view while our ContentView is in some form of loading state. Since we don’t want to introduce any if/else control flow to our ContentView itself (since that’ll essentially make SwiftUI treat those two code paths as two separate views), we could create such a conditional overlay by defining a new @ViewBuilder-marked computed property — like this: Preview struct ContentView: View { @State private var isLoading = true var body: some View { Text("Swift by Sundell") .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .overlay(starOverlay, alignment: .topTrailing) .overlay(loadingOverlay) .foregroundColor(.white) .cornerRadius(20) } ... @ViewBuilder private var loadingOverlay: some View { if isLoading { ProgressView() } } } Note that it’s perfectly fine to apply multiple overlays or backgrounds to a single view. They’ll simply be stacked on top of each other, just like when using a ZStack. However, our ProgressView isn’t currently very easy to see, and could definitely use a background of its own. In this case, we do actually want that background to occupy the same space as our ContentView itself, which can be done using the ZStack-based technique that we initially explored in this article: Preview struct ContentView: View { ... @ViewBuilder private var loadingOverlay: some View { if isLoading { ZStack { Color(white: 0, opacity: 0.75) ProgressView().tint(.white) } } } } Note that if our app supported iOS 14 or earlier, then we’d have to apply CircularProgressViewStyle(tint: .white) to our progress view (using the progressViewStyle modifier), rather than using tint, since that modifier was introduced in iOS 15. iOS 15 also introduced new APIs for defining backgrounds and overlays using @ViewBuilder-marked closures. The benefit of those new APIs is that they let us use control flow (like if statements) inline within those modifier calls, which in our case would enable us to easily define both of our two overlays right within our view’s body: struct ContentView: View { @State private var isLoading = true var body: some View { Text("Swift by Sundell") .font(.title) .padding(35) .background( LinearGradient( colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .overlay(alignment: .topTrailing) { Image(systemName: "star") .padding([.top, .trailing], 5) } .overlay { if isLoading { ZStack { Color(white: 0, opacity: 0.75) ProgressView().tint(.white) } } } .foregroundColor(.white) .cornerRadius(20) } } That’s not to say that delegating certain accessory view definitions to separate properties isn’t a good idea — in fact, that’s an excellent pattern that can help us break certain massive body properties up into more manageable pieces (without having to define a ton of new view types). But sometimes, defining everything inline might be the way to go, especially for simpler overlays, and the new closure-based background and overlay modifiers certainly make doing so much easier. CLOSURE-BASED BACKWARD COMPATIBILITY It is a bit of a shame that those new closure-based APIs are iOS 15-only, though, so let’s wrap up this article by fixing that. It’s important to remember just how composable SwiftUI was designed to be, and that most of the convenience APIs that have been introduced recently are merely composing some of SwiftUI’s older building blocks — which is something that we can do ourselves as well. So here’s how we could use the same technique as we previously used to make certain async/await-based system APIs backward compatible — that is, by defining an extension that overrides the system-provided APIs with our own, iOS 13-compatible versions: @available(iOS, deprecated: 15.0, message: "Use the built-in APIs instead") extension View { func background<T: View>( alignment: Alignment = .center, @ViewBuilder content: () -> T ) -> some View { background(Group(content: content), alignment: alignment) } func overlay<T: View>( alignment: Alignment = .center, @ViewBuilder content: () -> T ) -> some View { overlay(Group(content: content), alignment: alignment) } } With that simple extension in place, we can now use the closure-based variants of both background and overlay even within projects that need to support earlier operating system versions. Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. CONCLUSION Backgrounds and overlays are two incredibly useful layout tools when working with SwiftUI, and along with ZStack, make it possible to define all sorts of depth-stacked view hierarchies. To learn more about SwiftUI’s layout system, make sure to check out my three-part guide right here, and head over to the SwiftUI Discover page if you’d like to continue exploring many other aspects of the framework. Of course, you’re always welcome to contact me via either Twitter or email if you have any questions, comments, or feedback. Thanks for reading! * * SWIFTUI Get the most out of Apple’s new UI framework. * Listen to a podcast episode about swiftui 57: “AUDIO AND SWIFT” WITH SPECIAL GUEST ADAM BELL From 80s-style synths to SwiftUI. * Read more about swiftui ADDING SWIFTUI’S VIEWBUILDER ATTRIBUTE TO FUNCTIONS Really useful for utility methods. * Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * BASICS: LOOPS * Basics * collections * language features Published on 13 Jan 2022 Swift offers many different built-in ways to iterate over collections (such as arrays, sets, and dictionaries) — the most basic of which being for loops, which let us run a piece of code for each element that was found within a given collection. For example, here we’re looping through an array of names, and we’re then outputting each name by printing it into the console: let names = ["John", "Emma", "Robert", "Julia"] for name in names { print(name) } An alternative way of accomplishing the same thing would be to instead call the forEach method on our array of names, which lets us pass a closure that’ll be run for each element: names.forEach { name in print(name) } One key difference between a for loop and forEach, though, is that the latter doesn’t enable us to break the iteration in order to stop it once a given condition has been met. For example, when going back to using a for loop again, we could decide to stop the iteration once the name Robert was encountered: let names = ["John", "Emma", "Robert", "Julia"] for name in names { if name == "Robert" { break } print(name) // Will only print "John" and "Emma" } There are also many other ways to use for loops in Swift. For example, if we’d like to gain access to what index that we’re currently handling as part of our iteration, then we could instead choose to base our loop on a range that goes from zero to the number of elements within our collection. We could then use the Array type’s subscripting feature to retrieve the current element for that index — like this: for index in 0..<names.count { print(index, names[index]) } Another way to write the exact same loop would be to instead iterate over our array’s indicies, rather than constructing a range manually: for index in names.indices { print(index, names[index]) } Yet another approach would be to use the enumerated method to convert our array into a sequence containing tuples that pair each index with its associated element: for (index, name) in names.enumerated() { print(index, name) } Note that the enumerated method always uses Int-based offsets, which in the case of Array is a perfect match, since that collection also uses Int values as its indices. Next, let’s take a look at while loops, which offer a way for us to repeatedly run a block of code as long as a given boolean condition remains true. For example, here’s how we could use a while loop to keep appending each name within our names array to a string, as long as that string contains less than 8 characters: let names = ["John", "Emma", "Robert", "Julia"] var index = 0 var string = "" while string.count < 8 { string.append(names[index]) index += 1 } print(string) // "JohnEmma" Another way to construct a while loop (which perhaps isn’t as commonly used in Swift as in other languages) is by using a separate repeat block, which will also get repeatedly run as long as our while condition evaluates to true: let names = ["John", "Emma", "Robert", "Julia"] var index = 0 var string = "" repeat { string.append(names[index]) index += 1 } while string.count < 8 print(string) // "JohnEmma" The key difference between repeat and a stand-alone while loop is that a repeat block will always be run at least once, even if the attached while condition initially evaluates to false. One important thing to keep in mind when using while loops, though, is that it’s up to us to make sure that each loop is ended at an appropriate time — either by manually using break (like we did earlier when using a for loop), or by ensuring that our loop’s boolean condition is met once the iteration should be terminated. For example, when constructing our name-based string value, we probably want to make sure that the current index won’t go out of the bounds of our names array — since otherwise our app would crash when subscripting into that array. One way to do that would be to attach a second boolean condition to our while statement — like this: while string.count < 8, index < names.count { string.append(names[index]) index += 1 } Another approach would be to instead perform the above index-check inline within the loop itself, for example by using a guard statement that’ll break our loop within its else clause: while string.count < 8 { guard index < names.count else { break } string.append(names[index]) index += 1 } When working with loops, there’s often many different ways to model the same logic, and it can usually be quite useful to prototype a few different approaches in order to figure out which one that’ll work best within each situation. As an example, we could actually have chosen to implement the above iteration using a for loop instead — since we’d be able to attach our string.count-based condition to such a loop by using the where keyword: let names = ["John", "Emma", "Robert", "Julia"] var string = "" for name in names where string.count < 8 { string.append(name) } print(string) // "JohnEmma" That doesn’t mean that the above for-based version is objectively better than the while-based one. Picking what type of loop to use is often a matter of taste and code structure, although I’d personally argue that whenever a loop is based on an actual collection of elements, using a for loop is most often the simplest way to go. Finally, let’s turn our attention to dictionaries, which can also be iterated over using the exact same tools that we’ve been using to loop through arrays and ranges. When iterating over a dictionary, though, we don’t just get access to single elements, but rather (key, value) tuple pairs. For example, here’s how we could iterate through a dictionary that contains a category-based version of our names dataset: let namesByCategory = [ "friends": ["John", "Emma"], "family": ["Robert", "Julia"] ] for (category, names) in namesByCategory { print(category, names) } Sometimes, though, we might not need access to both the keys and values that a given dictionary contains, so it’s also possible to use the _ symbol to ignore a given tuple member within our iteration. Here’s how we could do just that to ignore our dictionary’s values, and only handle its keys (or categories) within our loop: for (category, _) in namesByCategory { print(category) } However, while the above is perfectly valid Swift code, there are actually two purpose-built APIs that enable us to perform either a key-only or value-only dictionary iteration simply by accessing either the keys or values property on the dictionary that we’d like to iterate over: for category in namesByCategory.keys { print(category) } for names in namesByCategory.values { print(names) } When working with dictionaries, sets, or other collections that don’t offer a guaranteed element order, it’s important to remember that our loops also won’t be performed in a predictable order. So, for example, if we’d like to ensure that the above category iteration always happens in the same order, then we could make that happen by first sorting our dictionary’s keys into an array — like this: for category in namesByCategory.keys.sorted() { print(category) } Our categories will now always be iterated over in alphabetical order. Of course, performing the above kind of sorting operation is only required if the order of elements matters, which will likely only be true for certain iterations. I hope that you found this Basics article useful, and that you learned at least one new way of constructing a loop in Swift. For more Basics articles, check out this page, and if you have any questions, comments, or feedback (even if it’s positive!), then feel free to reach out via either Twitter or email. Thanks for reading! Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * * Read more about collections BINDABLE SWIFTUI LIST ELEMENTS * Another article about Swift’s language features CALLING INITIALIZERS WITH DOT SYNTAX AND PASSING THEM AS CLOSURES * Continue exploring Swift’s language features TYPE INFERENCE-POWERED SERIALIZATION IN SWIFT * CREATING COMBINE-COMPATIBLE VERSIONS OF ASYNC/AWAIT-BASED APIS * combine * concurrency Published on 05 Jan 2022 Discover page available: Combine A challenge that many developers face as they maintain various code bases over time is how to neatly connect different frameworks and APIs in a way that properly adheres to the conventions of each technology involved. For example, as teams around the world are starting to adopt Swift 5.5’s async/await-powered concurrency system, we’ll likely find ourselves in situations where we need to create versions of our async-marked APIs that are compatible with other asynchronous programming techniques — such as Combine. While we’ve already taken a look at how Combine relates to concurrency APIs like async sequences and streams, and how we can make it possible to call async-marked functions within a Combine pipeline — in this article, let’s explore how we could make it easy to create Combine-based variants of any async API, regardless of whether it was defined by us, by Apple, or as part of a third-party dependency. Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. ASYNC FUTURES Let’s say that an app that we’re working on contains the following ModelLoader, which can be used to load any Decodable model over the network. It performs its work through an async function that looks like this: class ModelLoader<Model: Decodable> { ... func loadModel(from url: URL) async throws -> Model { ... } } Now let’s say that we’d also like to create a Combine-based version of the above loadModel API, for example in order to be able to call it within specific parts of our code base that might’ve been written in a more reactive style using the Combine framework. We could of course choose to write that sort of compatibility code specifically for our ModelLoader type, but since this is a general problem that we’re likely to encounter multiple times when working with Combine-based code, let’s instead create a more generic solution that we’ll be able to easily reuse across our code base. Since we’re dealing with async functions that either return a single value, or throw an error, let’s use Combine’s Future publisher to wrap those calls. That publisher type was specifically built for these kinds of use cases, since it gives us a closure that can be used to report a single Result back to the framework. So let’s go ahead and extend the Future type with a convenience initializer that makes it possible to initialize an instance with an async closure: extension Future where Failure == Error { convenience init(operation: @escaping () async throws -> Output) { self.init { promise in Task { do { let output = try await operation() promise(.success(output)) } catch { promise(.failure(error)) } } } } } For more information on how Combine’s Future type works, check out “Using Combine’s futures and subjects”. The power of creating an abstraction like that, which isn’t tied to any specific use case, is that we’ll now be able to apply it to any async API that we want to make Combine-compatible. All it takes is a few lines of code that calls the API that we’re looking to bridge within a closure that’s passed to our new Future initializer — like this: extension ModelLoader { func modelPublisher(for url: URL) -> Future<Model, Error> { Future { try await self.loadModel(from: url) } } } Neat! Note how we could’ve chosen to give that Combine-based version the same loadModel name that our async-powered one has (since Swift supports method overloading). However, in this case, it might be a good idea to clearly separate the two, which is why the above new API has a name that explicitly includes the word “Publisher”. REACTIVE ASYNC SEQUENCES Async sequences and streams are perhaps the closest that the Swift standard library has ever come to adopting reactive programming, which in turn makes those APIs behave very similarly to Combine — in that they enable us to emit values over time. In fact, in the article “Async sequences, streams, and Combine”, we took a look at how Combine publishers can even be directly converted into async sequences using their values property — but what if we wanted to go the other way, and convert an async sequence (or stream) into a publisher? Continuing with the ModelLoader example from before, let’s say that our loader class also offers the following API, which lets us create an AsyncThrowingStream that emits a series of models loaded from an array of URLs: class ModelLoader<Model: Decodable> { ... func loadModels(from urls: [URL]) -> AsyncThrowingStream<Model, Error> { ... } } For an example of a concrete implementation of an API just like the one above, check out the aforementioned article “Async sequences, streams, and Combine”. Just like before, rather than rushing into writing code that specifically converts the above loadModels API into a Combine publisher, let’s instead try to come up with a generic abstraction that we’ll be able to reuse whenever we want to write similar bridging code elsewhere within our project. This time, we’ll extend Combine’s PassthroughSubject type, which gives us complete control over when its values are emitted, as well as when and how it should terminate. However, we’re not going to model this API as a convenience initializer, since we want to make it clear that calling this API will, in fact, make the created subject start emitting values right away. So let’s make it a static factory method instead — like this: extension PassthroughSubject where Failure == Error { static func emittingValues<T: AsyncSequence>( from sequence: T ) -> Self where T.Element == Output { let subject = Self() Task { do { for try await value in sequence { subject.send(value) } subject.send(completion: .finished) } catch { subject.send(completion: .failure(error)) } } return subject } } For more on how static factory methods might differ from initializers in terms of API design, check out “Initializers in Swift” With the above in place, we can now wrap our async stream-based loadModels API almost as easily as our previous async-marked one — the only extra step that’s required in this case is to type-erase our PassthroughSubject instance into an AnyPublisher, to prevent any other code from being able to send new values to our subject: extension ModelLoader { func modelPublisher(for urls: [URL]) -> AnyPublisher<Model, Error> { let subject = PassthroughSubject.emittingValues( from: loadModels(from: urls) ) return subject.eraseToAnyPublisher() } } Just like that, we’ve now created two convenience APIs that make it very straightforward for us to make code using Swift’s concurrency system backward compatible with Combine — which should prove to be incredibly convenient when working with a code base that uses Combine to some extent. Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. CONCLUSION Even though Swift does now have a built-in concurrency system that covers much of the same ground as Combine does, I think both of those two technologies will continue to be incredibly useful for years to come — so the more we can create smooth bridges between them, the better. While some developers will likely opt to completely rewrite their Combine-based code using Swift concurrency, the good news is that we don’t have to do that. With just a few convenience APIs in place, we can make it trivial to pass data and events between those two technologies, which in turn will let us keep using our Combine-based code, even as we start adopting async/await and the rest of Swift’s concurrency system. I hope that you found this article useful. If you did, or if you have any feedback, questions, or comments, then please let me know — either on Twitter or via email. Thanks for reading! * Support Swift by Sundell by checking out this sponsor: Bitrise: Easily set up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today. * Article TWO WAYS OF CAPTURING SELF STRONGLY WITHIN A CLOSURE * closures * language features Published on 02 Jan 2022 How to use either explicit self references or a capture list to capture self strongly within an escaping Swift closure. * Podcast episode 112: “THE 2021 SEASON FINALE” * holiday special * wrap-up * summary Published on 30 Dec 2021 To wrap up the 2021 season of the show, John revisits some of the key themes and topics that were discussed both on the show itself, and within the Swift community in general, throughout the year. * Article BUILDING AN ASYNCHRONOUS SWIFTUI BUTTON * swiftui * concurrency Published on 22 Dec 2021 How a custom SwiftUI button that’s capable of running asynchronous actions can be created, and how to make such a control versatile and easy to reuse across a project. * Podcast episode 111: “CUSTOM RENDERING” WITH SPECIAL GUEST JAMES THOMSON * ui development Published on 20 Dec 2021 James Thomson returns to the show to discuss the various technologies that enable us to render custom UIs on Apple’s platforms. From rendering views using Core Graphics and Core Animation, to building completely custom 3D-based UIs using SceneKit and RealityKit. * Link NEW DISCOVER PAGE: CONCURRENCY Published on 16 Dec 2021 Explore Swift’s built-in concurrency system, and how to use tools like async/await and actors to write concurrent code in robust and efficient ways. * Article DELAYING AN ASYNCHRONOUS SWIFT TASK * concurrency Published on 13 Dec 2021 How we can use the built-in Task type to delay certain operations when using Swift’s new concurrency system. * Article LIGHTWEIGHT DEPENDENCY INJECTION AND UNIT TESTING USING ASYNC FUNCTIONS * concurrency * dependency injection Published on 09 Dec 2021 Let’s explore how we could make use of Swift’s async/await capabilities to make our asynchronous code fully testable in a very lightweight way. * Podcast episode 110: “CONCURRENCY BEYOND APP DEVELOPMENT” WITH SPECIAL GUEST TIM CONDON * concurrency * swift package manager Published on 06 Dec 2021 Tim Condon joins John to discuss how both client and server-side Swift developers could utilize the new built-in concurrency system, as well as how distributed actors and other upcoming language features might continue to make Swift even more capable on the server. * More to read BROWSE ALL ARTICLES * More to listen to BROWSE ALL PODCAST EPISODES * More to watch BROWSE ALL VIDEOS Copyright © Sundell sp. z o.o. 2022. Built in Swift using Publish. Twitter | RSS | Contact