Slzii.com

https://swiftbysundell.com

Swift by Sundell
Weekly Swift articles, podcasts and tips by John Sundell
Swift by SundellArticles and podcasts about Swift development, by John Sundell.ArticlesPodcastArchiveSubscribeAboutSearchPresented by the Genius Scan SDKRSS feedCategoriesRecently publishedArchiveShow compact listUsing Swift’s defer keyword within async and throwing contextslanguage featuresconcurrencyPublished on 15 Apr 2025Swift’s defer keyword allows us to delay the execution of a given block of code until the current scope is exited. While that might initially not seem that useful (after all, can’t we simply write that code at the end of the scope instead?), it turns out that when writing modern Swift code, we’re quite often dealing with multiple potential exit points within our functions and closures — especially when writing code that throws, or when utilizing async/await.Let’s take a look at the following SearchService type’s loadItems method as an example. It uses a Database API that requires a connection to be opened before any operations can be performed, and that connection then needs to be properly closed and cleaned up before new database requests can be accepted:actor SearchService { private let database: Database ... func loadItems(maching searchString: String) throws -> [Item] { let connection = database.connect() do { let items: [Item] = try connection.runQuery(.entries( matching: searchString )) connection.close() return items } catch { connection.close() throw error } } }Note how we need to explicitly specify the type for our items above, since the runQuery method is generic, as it can return an array of any kind of database-compatible entry type that we’re looking to retrieve.Because our code has two separate branches (one for when our runQuery call succeeds, and one for when an error is thrown), we need to write separate calls to connection.close within each branch. That might initially not seem like a big deal, but just like most code duplication, it increases the chance that we’ll end up making a mistake, which could result in a quite major bug in this instance (as missing a close call would leave the database unable to accept additional requests).One way to solve the above problem would be to ensure that our code only has a single branch of execution. In the case of the above loadItems method, that could be done by using the closure-based Result initializer included in the standard library, which converts a throwing closure into a result, which can then be unwrapped once we’ve closed our database connection — like this:actor SearchService { private let database: Database ... func loadItems(maching searchString: String) throws -> [Item] { let connection = database.connect() let result = Result<[Item], Error> { try connection.runQuery(.entries(matching: searchString)) } connection.close() return try result.get() } }While that’s certainly an improvement — let’s now take a look at how defer lets us solve the problem in an arguably much more elegant way, since it’ll let us define the closing of our database connection right next to where the connection is opened:actor SearchService { private let database: Database ... func loadItems(maching searchString: String) throws -> [Item] { let connection = database.connect() defer { connection.close() } return try connection.runQuery(.entries(matching: searchString)) } }Nice! Not only are the calls to connect and close now right next to each other (which arguably makes it easier to reason about those two calls as a pair), but because we can now directly return the result of our runQuery call, we no longer have to manually specify any type information — the compiler can now automatically infer the return type of that call for us.Using defer does have somewhat of a downside, though, in that it sort of breaks the traditional control flow model that structured programming tends to follow — where instructions are always executed from top to bottom. Within our current loadItems implementation, for example, we now have three expressions: 1. Open the connection 2. Close the connection (deferred) 3. Run our query But those expressions won’t be executed in the order 1, 2, 3, but rather in the order 1, 3, 2, which might initially seem like a quite strange way of structuring our code. So, using defer might end up being somewhat of an acquired taste, and a tool that should probably not be over-used, but rather just used when there’s some specific cleanup work that we want to ensure gets performed no matter how the current scope is exited.Async contextsThe defer keyword is perhaps even more useful in the concurrent world of async/await, since one of the benefits of that way of writing async code is that it lets us “flatten” code that previously required nesting in the shape of closures or separate operations.For example, within the following ItemListService, we once again have to work with separate code branches (and thus, nesting) in order to ensure that an isLoading bool is set back to false whenever a loading operation was completed:actor ItemListService { private let networking: NetworkingService private var isLoading = false ... func loadItems(after lastItem: Item) async throws -> [Item] { guard !isLoading else { throw Error.alreadyLoading } isLoading = true do { let request = requestForLoadingItems(after: lastItem) let response = try await networking.performRequest(request) let items: [Item] = try response.decoded() isLoading = false return items } catch { isLoading = false throw error } } }In this case, we can’t rely on the Result-based approach we took earlier to flatten our code into a single branch, since there’s no built-in way to convert an async closure into a Result (although that’s something we could add, using a custom extension). So this is a type of situation where defer really comes in handy, as it lets us ensure that our isLoading state is always assigned back to false whenever an operation either succeeded or failed:actor ItemListService { private let networking: NetworkingService private var isLoading = false ... func loadItems(after lastItem: Item) async throws -> [Item] { guard !isLoading else { throw LoadingError.alreadyLoading } isLoading = true defer { isLoading = false } let request = requestForLoadingItems(after: lastItem) let response = try await networking.performRequest(request) return try response.decoded() } }The above type of approach can also be really useful when working with nested async tasks as well. For example, let’s say that we wanted to improve the above loadItems method so that it doesn’t throw an error if called while a loading operation is already underway. To do that, we could keep track of a dictionary of loading tasks (keyed by the ID of the lastItem for each task), and then use defer to ensure that a task is always removed from that dictionary when it was completed — like this:actor ItemListService { private let networking: NetworkingService private var activeTasksForLastItemID = [Item.ID: Task<[Item], Error>]() ... func loadItems(after lastItem: Item) async throws -> [Item] { if let existingTask = activeTasksForLastItemID[lastItem.id] { return try await existingTask.value } let task = Task { defer { activeTasksForLastItemID[lastItem.id] = nil } let request = requestForLoadingItems(after: lastItem) let response = try await networking.performRequest(request) return try response.decoded() as [Item] } activeTasksForLastItemID[lastItem.id] = task return try await task.value } }In general, the above technique is a neat way of preventing duplicate async actor requests, since actors only protect against simultaneous calls while they’re busy performing synchronous work. Once an actor has started an async task using await, it’s free to accept new calls while that async task is being performed. By using a nested Task combined with the defer keyword, we can ensure that such duplicate requests are properly reused and discarded once finished, all in a predictable manner.Swift by Sundell is brought to you by the Genius Scan SDK — Add a powerful document scanner to any mobile app, and turn scans into high-quality PDFs with one line of code. Try it today.ConclusionSwift’s defer keyword might initially seem like a somewhat odd language tool, as it doesn’t strictly follow the top-to-bottom control flow order that structured programming tend to use. But when it comes to cleanup operations, state management, and other tasks that we want to ensure are run no matter how a given scope is exited, it can be a really great tool — especially when utilizing Swift concurrency and the language’s native error handling model.If you’ve got questions, comments, or feedback, then feel free to reach out via either Mastodon or Bluesky.Thanks for reading!Modern URL construction in SwifturlsfoundationmacrosPublished on 31 Mar 2025These days, most applications need to work with URLs in some form. Perhaps they’re used to make network calls, to read and write files, or to perform various kinds of database operations. In Swift, URLs are by convention (and through the design of Apple’s frameworks) represented using the dedicated URL type, rather than just using plain strings, which ensures that we’re actually working with valid, properly formatted URLs.However, that also means that anytime that we have a string that we wish to treat as a URL, we have to perform a conversion that returns an optional — such as in this case:guard let url = URL(string: "https://swiftbysundell.com") else { // Hmmm... now what? return print("Invalid URL") }For URLs such as the one above, which are constructed using static string literals, using a conversion that can fail does arguably feel a bit unnecessary. After all, there’s no runtime variance involved here, so there’s really no significant risk that the above kind of conversion will result in nil, unless we’ve made a typo within our code.So, when working with such static URLs, it’s very common to simply use force unwrapping to turn the resulting optional URL into a non-optional one:let url = URL(string: "https://swiftbysundell.com")!However, having to do the above kind of force unwrapping manually every time we want to construct a URL is not quite ideal — so let’s see if we can improve things. First, let’s extend URL with an initializer that accepts a StaticString (which are Swift string literals without any kind of interpolation or dynamic components), within which we can perform the required unwrapping, but this time we’ll use a custom fatalError message in case the conversion to a URL failed:extension URL { init(staticString: StaticString) { guard let url = Self(string: "\(staticString)") else { fatalError("Invalid static URL string: \(staticString)") } self = url } }Using a custom fatalError call in situations when we have to force unwrap a value is in general a good practice, since that lets us provide additional context that can be incredibly useful if we ever need to debug a crash caused by a nil value.With the above in place, we can now easily convert any static string within our code base into a URL, without having to deal with optionals at every single call site:let url = URL(staticString: "https://swiftbysundell.com")Nice! Up until Swift 5.9, the above approach was more or less the best simple way to work with inline, static URLs in a non-optional manner (without requiring any external tools, such as code generation). However, Swift 5.9 introduced a new feature that can be incredibly useful in situations like this — macros.It’s macro time!Let’s see if we can write a Swift macro that’ll let us not just convert, but also validate static URL strings at compile time. We’ll start by jumping over to the command line, where we’ll run the following command to create a new macro-based Swift package:swift package init --type macro --name StaticURLOne thing that’s neat about macro packages, is that they come pre-filled with most of the boilerplate that we’ll need to define and vend our macro to any other targets that wish to use it — and it just so happens that the stringify macro that’s added as an example is the exact same type of macro that we’re looking to add — a freestanding expression macro.So let’s go ahead and simply rename the definition of stringify to staticURL, and change its input and output types to match the URL extension we created earlier:import Foundation @freestanding(expression) public macro staticURL(_ value: StaticString) -> URL = #externalMacro( module: "StaticURLMacros", type: "StaticURLMacro" )Next, let’s rename the StringifyMacro implementation to StaticURLMacro (matching the above definition’s type argument), and replace its previous expansion code with some logic that first ensures that the passed argument is indeed a string literal (although the Swift type system should already have verified that for us), and then extracts the string and attempts to construct a URL using it.If all checks pass, then we generate the same kind of force-unwrapping URL construction code that we manually used to write, which will be the output of our macro. Here’s what all of that looks like:public struct StaticURLMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { // Verify that a string literal was passed, and extract // the first segment. We can be sure that only one // segment exists, since we're only accepting static // strings (which cannot have any dynamic components): guard let argument = node.arguments.first?.expression, let literal = argument.as(StringLiteralExprSyntax.self), case .stringSegment(let segment) = literal.segments.first else { throw StaticURLMacroError.notAStringLiteral } // Verify that the passed string is indeed a valid URL: guard URL(string: segment.content.text) != nil else { throw StaticURLMacroError.invalidURL } // Generate the code required to construct a URL value // for the passed string at runtime: return "Foundation.URL(string: \(argument))!" } }Note how we prefix the URL type with its parent module (Foundation) above. That’s to avoid conflicts if our macro is used within a context that has declared its own URL type. Applying such prefixes isn’t typically necessary when writing code manually, but is a good practice when writing macros, since we don’t know up-front exactly where our macros will end up being used.With our macro implementation done, all that remains is to define the StaticURLMacroError type that’s used above, and to update our CompilerPlugin to provide the correct macro type:enum StaticURLMacroError: String, Error, CustomStringConvertible { case notAStringLiteral = "Argument is not a string literal" case invalidURL = "Argument is not a valid URL" public var description: String { rawValue } } @main struct StaticURLPlugin: CompilerPlugin { let providingMacros: [Macro.Type] = [StaticURLMacro.self] }With all those pieces in place, if we integrate our new StaticURL macro package within an application, then we can now easily define static, 100% compile-time validated URLs wherever we’d like:let url = #staticURL("https://swiftbysundell.com")Neat! It could definitely be argued that using a macro isn’t really necessary for a use case like this, given that our earlier StaticString-based extension approach worked just fine (apart from the danger of typos). Like in many cases when working with Swift, this is essentially a trade-off between increased complexity and compile-time safety, and whether or not the additional complexity of a macro will be worth it will likely vary from project to project.Dynamic componentsSo far, we’ve been working with URLs that are known at compile time, but what about ones that have to be constructed at runtime? For example, here we’re using string interpolation to define a URL that’ll be used to load User data from a given web API endpoint:actor NetworkingService { private static let baseURL = "https://api.myapp.com" ... func loadUser(withID id: User.ID) async throws -> User { guard let url = URL( string: "\(Self.baseURL)/users/\(id)?refresh=true" ) else { throw NetworkingError.invalidURL } ... } }Here we’re facing a very similar problem as when working with static URLs — when reading the above code, we can see that there’s no way that the performed URL conversion will ever result in nil, given that our baseURL and /users/ strings are both static, and if we assume that User.ID values are always URL-safe.So would it be possible to convince the compiler that a nil result can never occur, even when working with dynamic URL components? An initial idea might be to use Foundation’s dedicated URLComponents builder — which offers a structured way to construct dynamic URLs.While that approach does have some key advantages over using string interpolation (since we’re now assigning values to explicit parts of the URL we’re building, rather than just working with a loosely formed string) — it’s significantly more verbose in comparison, while still not getting rid of having to unwrap our URL as an optional:actor NetworkingService { private static let baseURLComponents = { var components = URLComponents() components.scheme = "https" components.host = "api.myapp.com" return components }() ... func loadUser(withID id: User.ID) async throws -> User { var urlComponents = Self.baseURLComponents urlComponents.path = "/users/\(id)" urlComponents.queryItems = [ URLQueryItem(name: "refresh", value: "true") ] guard let url = urlComponents.url else { throw NetworkingError.invalidURL } ... } }Thankfully, it turns out that there’s a much simpler suite of APIs for dynamic URL construction that were introduced in iOS 16 (and the other 2022 Apple operating system versions) that — when combined with our static URL handling code from before — lets us both completely get rid of optionals, and gives us a really nice syntax for constructing our API call URL.If we declare our base URL as a static URL value (rather than a string, or a URLComponents value), then we can simply call different overloads of the appending API on that value to construct our dynamic URL in a completely optional-free manner — like this:actor NetworkingService { private static let baseURL = #staticURL("https://api.myapp.com") ... func loadUser(withID id: User.ID) async throws -> User { let url = Self.baseURL .appending(components: "users", id) .appending(queryItems: [ URLQueryItem(name: "refresh", value: "true") ]) ... } }Very nice! And the good news is that we’re not limited to just using the above kind of solution when constructing URLs used to perform network calls — we can also use the same suite of APIs when working with file system URLs that we’d previously resolve using FileManager, such as in this example:private extension NetworkingService { func cacheResponseOnDisk(_ response: Response) throws { guard let cacheFolderURL = FileManager.default.urls( for: .cachesDirectory, in: .userDomainMask ).first else { throw NetworkingError.failedToResolveCacheFolder } ... } }If we now convert the above code to use the new URL construction APIs, then we’ll end up with a another non-optional solution, just as when constructing our web API URL:private extension NetworkingService { func cacheResponseOnDisk(_ response: Response) throws { let cacheURL = URL .cachesDirectory .appending(component: response.cacheID) ... } }URL now also contains a number of other static properties that can be used to reference common folders on Apple’s platforms, such as the home and temporary directories — all of which hold a predictable, non-optional value:URL.homeDirectory URL.documentsDirectory URL.desktopDirectory URL.temporaryDirectorySo, as long as we’re targeting the equivalent of iOS 16 or above within a given project, then we’re now able to quite easily construct both web and file system URLs, even when they contain dynamic paths and components, such as query items.Swift by Sundell is brought to you by the Genius Scan SDK — Add a powerful document scanner to any mobile app, and turn scans into high-quality PDFs with one line of code. Try it today.ConclusionUsing Foundation’s modern URL construction APIs to be able to avoid optionals when creating URL values doesn’t just simplify our code, it also reduces the risk of bugs and crashes, and further lets us work with URLs in more structured ways — by replacing things like string interpolation with dedicated APIs for appending path components and query items.I hope you’ve enjoyed reading this first Swift by Sundell article in over two years, and that you’ll find it useful when working on your Swift projects. If you have any questions, feedback, or comments, then feel free to reach out via either Mastodon or Bluesky.Thanks for reading — and hey, it’s good to be back!Swift by Sundell is brought to you by the Genius Scan SDK — Add a powerful document scanner to any mobile app, and turn scans into high-quality PDFs with one line of code. Try it today.Swift by Sundell is back!Published on 31 Mar 2025I never actually decided to stop writing Swift articles. It just sort of happened. In fact, for the past two years as this website has been very much dormant, I’ve lost count of the number of times that I’ve sat down at my desk with the intent to write and publish a new article, or the number of times I’ve added “Write a new article” to my ever-growing list of reminders and to-dos.In the summer of 2022, I became a dad, and it changed everything. In a whirlwind of happiness, excitement, sleep deprivation, and general confusion, my schedule was turned completely upside down. Family quickly replaced work as my number one priority, and anything non-essential felt like it’d have to wait. Work-wise, all that I really had any energy for was my freelancing work, and the thought of spending any nights, weekends, or other spare time on article-writing or podcasting felt ever more distant.Even as I started regaining some of that work time (and sleep!), my routine of regularly publishing new Swift articles had clearly been broken, and getting back into it started to feel like quite a challenge. I had also settled on a very nice, comfortable schedule when it came to my freelancing work, which let me work with multiple clients on different kinds of projects, all while giving me enough time to spend with my family. So, I was happy, and didn’t really want to mess with that schedule by adding anything to it.But as time went on, I started to really miss Swift by Sundell. Over the past two years, I haven’t lost my passion for Swift, or for programming in general for that matter, and being able to share my thoughts and experiences regarding Swift was something that I really wanted to get back to.However, there were two main obstacles in the way.First, I have written a lot of Swift articles over the years. So many that, honestly, I’ve completely forgotten many of them. So, anytime I wanted to write something new, I had to start by researching whether I had already covered a similar topic before, and if so, whether what I was planning to write conflicted with what I had previously published.That started to become quite tedious, especially since many of my old articles are now out of date (both because of Swift/platform changes, but also because I’ve changed my opinions and coding approaches over the years). That meant that, in order to cover a given topic, I really first would have to go back to a number of old articles and update them to all make sense as a whole. That both significantly increased the scope and time required for more or less any new article project, and ended up giving me a sort of “writer’s block”, as I scratched numerous article ideas because “I’ve already written about something similar”.The solution to that problem — meet the Swift by Sundell Archive. What I’ve done is that I’ve moved every single article that I’ve previously published on this website into an archive, that both preserves them (and their URLs) into the future, and also enables me to effectively hit RESET on my article-writing. Any topic is now up for grabs again, and I can cover any Swift API, tool, or technique that I’d like from a modern perspective.The second obstacle was simply time. Even though I now felt very motivated to get back into writing articles, my schedule was still completely filled up with freelancing work, workshops, and other commitments. This is where my good friends at Genius Scan had a, well, quite genius idea. They had been looking for ways to get the word out about their high-quality document scanning SDK (which I’ve contributed lots of code to as a freelancer, by the way), and had a proposition for me — how about they’d fund all of my article-writing time over the next six months, in exchange for some promotion on the website?So that brings us to today — and I’m excited to announce that for the next six months (at least!) you’ll see new articles published regularly on this website, covering a wide range of Swift topics. In fact, the first brand new article, ”Modern URL construction in Swift” is already available, with more to come shortly. It feels so great to be back writing again, and I hope you’ll enjoy the new articles that I’ll publish.I want to sincerely thank the fine folks at Genius Scan for helping me bring Swift by Sundell back, and to everyone in the Swift community who has reached out over the past few years with encouraging and supportive messages.Welcome back to Swift by Sundell!More to readBrowse the article archiveMore to listen toBrowse all podcast episodesMore to watchBrowse all videosCopyright © Sundell sp. z o.o. 2025.Built in Swift using Publish.Mastodon | RSS | Contact
en
en
https://swiftbysundell.com

0.0050969123840332






Weekly Swift articles, podcasts and tips by John Sundell