Keeping up with Swift's latest evolutions
Daniel Steinberg was our guest for an Ask Me Anything session (AMA) held on April 13, 2021, hosted by the iOS expert Vincent Pradeilles, and dedicated to the evolutions that have been happening in the Swift language in the two years since Swift 5 was released. Steinberg is the author of several programming books, including A SwiftUI Kickstart and A Swift Kickstart, and has written apps for the iPhone and iPad since the SDKs first appeared, as well as Mac programs that date all the way back to System 7. He currently presents iPhone, Cocoa, and Swift training and consults through his company, Dim Sum Thinking. In this article, you will find the main topics Steinberg and Pradeilles discussed during the session. If you’d like to listen to our future AMAs live, join the Welcome Tech club on Clubhouse!
On the importance of reading Swift Evolution proposals
Vincent Pradeilles: Something I really recommend to people who want to get some in-depth knowledge about the latest evolutions happening in Swift is checking Swift Evolution proposals. They are on GitHub, so they are all accessible. In addition to finding lots of very interesting information and edge cases relating to the latest features, you will also get a glimpse of the mindset of the people who made the proposals as well as the alternatives they have explored. I think it’s a really great way to learn and to go beyond the Swift documentation.
Daniel Steinberg: And things that you think might are obvious are sometimes not. The function
count(where:), which was part of proposal 220, is a good example. After it was implemented, Ben [Cohen] found a corner case that impacted performance. So now you have this function that seems to be a very obvious solution but it’s been left to languish. And with the new rules of Swift Evolution, it may time out.
VP: An evolution proposal could actually be about removing something. For instance, in one of the proposals, they removed the feature that allowed us to compare optionals. At the time, this function seemed like a great idea, but it was leading to some very weird situations. For example, if you had an array of people, with some owning pets, and you asked for the people with pets who are less than 8 years old, people with no pets would show in the result, because
nil was less than everything. When [the Swift team] saw this edge case, they removed the feature from the language. This shows how people behind the language sometimes go back on their choices.
DS: There was also this feature at the very beginning of Swift that enabled you to write your functions as curried functions, and you could call a few at a time. They removed that in Swift Evolution as well.
VP: Also, when calling a function with two arguments, you were able to pass a tuple of two arguments, and that would work. But they thought that that could lead to some very misleading cases. So they changed their mind about a lot of things.
Property wrappers as function arguments
DS: One thing I really enjoyed about this Swift Evolution proposal in particular was the process, because it has gone back and forth several times and people have really worked to make it better, and it’s been a very friendly discussion. The idea of this evolution is that you can consume properties, like property wrappers, with arguments. You can make these changes on the way into a function call or a method call without having to depend on the receiver to do anything to transform it. So it looks like there are going to be a lot of cases where we can now use property wrappers.
VP: I saw a very interesting use case of property wrappers recently. It was a wrapper called
redacted, and what it was doing was implementing the CustomStringConvertible protocol, but instead of writing the content of the property, it was just writing “redacted”. And it was a way to make sure that some sensitive information was not returned in the log in production by mistake. It’s not like swizzling, but it feels like it’s a safer version of swizzling.
But I think the killer use case for property wrappers is wrapping your user defaults. And it’s such a killer use case that, actually, SwiftUI now comes with a property wrapper that does exactly this and listens to changes on the user defaults. And there is another use case that I like, and I think it would also make a lot of sense with using property wrappers for arguments—it’s to use them as debugging tools, kind of like a breakpoint. You add the property wrapper when you want your code to write more information in the console. For example, whenever someone reads the variable, you would like to know about it and store some extra data, such as all the values that were previously set on the property. So it’s kind of like a breakpoint, but one that is very embedded in your code. I think that might be an interesting topic for an open-source project.
DS: Some might say that this is a sign of Swift being Java-like, because it feels like very aspect-oriented programming, like when you’re doing things on an orthogonal axis across your code.
VP: It does indeed. And on Android, property wrappers are even more powerful than the ones we have in iOS. You can write the signature of a function, and your program is able to automatically generate the code to make a network call that is going to match the signature of the function. You then just have to annotate it and everything is done automatically for you. I don’t think Swift wants to go in that direction, but that’s something I would like to have.
DS: I agree. The other issue that I’ve come up against with property wrappers is wanting to nest them and finding that they don’t always nest as nicely as app storage, for example. I want something that is both driving it to user defaults and announcing when a change has been made.
VP: It feels like it’s kind of a one-shot. Once you have used the property wrapper, that’s it. You can’t use a second one or you have to manually nest them together. But then there are going to be so many combinations that it’s going to become unmanageable.
DS: Right. It would be nice to have some utility that allows us to do it sort of generically.
VP: Actually, if you try to put two property wrappers on the same property, the error message on Xcode is that it is not supported yet.
DS: I tried just yesterday to do the same thing, and it’s still not supported.
DS: One thing we’re seeing in the async proposals is that they sort of changed their minds about some things in Swift. In Swift 3, I believe, they changed functions from being non-escaping to you having to signify when something was escaping. So you have to say when something is async or when something could throw an error. And I like this that Swift makes you be specific. And so there was a discussion recently about, “Should you be able to combine a lot of these tries into one instead of having to do all of them separately with the tries and the dos?” I liked that they’re separate. I’d like to indicate, “This is the line of code that can go bad.”
VP: I agree. And I think that one of the things that is very interesting when you read async/await is that await does nothing from the point of view of the compiler. It’s a no-op, which is only there to mark the call side to say that we are calling an async function on this line, so it will probably lead to a suspend point. And there are a lot of non-trivial things that can be happening here.
DS: When they first introduced Grand Central Dispatch [GCD], it looked awful. But for the most part, we don’t have to write GCD code. For the most part, the things that we need are being done in high-level code and the GCD is being pushed lower. I’m hoping the same will be true for async code—that I won’t have to write a lot of it. That I won’t have to read a lot of it. That it’s being done by the frameworks for me on some level.
VP: From what I’ve seen is available, when you want to write your own async code, you have to use some primitives that feel very unsafe, such as
withCheckedContinuation. So I hope that we’re going to have all of the nice APIs in Foundation, so that we don’t actually have to write our own async function at the bottom level.
DS: To come back to the parallel with Java, when Douglas S. Lea’s concurrency APIs were introduced to Java, he said, “In general, you don’t want to do a lot of it because you often make your code worse, not better, when you start trying to handle these things yourself.”
VP: If you’re a developer on a project, you don’t want to get started in this field unless there is a big advantage for your app in terms either of performance or to shorten the time it takes to develop your features. But you definitely don’t want to find yourself in a situation where you have gotten heavily on the async/await train purely because it was the fashionable thing to do. You will have a lot of issues and then end up regretting it. So I’m curious to see how Apple is actually going to present the feature. Are they going to try to tell people that everything should use async/await or are they going to be more cautious about it?
DS: I hope they’re going to be more cautious. And I think it’s going to be a big topic at this year’s WWDC. I expect Ben [Cohen] and Doug [Gregor]’s session will probably be about it. Tony Parker and Philippe Hausler wrote the async/await for sequences in proposal 298. They’re the ones who did the Combine stuff, and they did the async/await for sequences, which is a great way of taking this async/await and applying it to something that we can see in the code. And so that’s one of the nice things about having Swift open source. We’ll be able to take a peek and see what they did to make this work.
DS: We encountered result builders in SwiftUI. If we create a VStack and put a piece of text and an image inside it, then somehow that curly brace around the text on one line and an image on the next line has to be converted into something. And so of course, if you look at the init for VStack, you see that VStack takes things like alignment and spacing, but the final thing that it takes is a function that accepts nothing and returns this view. And so the result builder is what enables you to take this collection of views and turn it into a single entity. In some ways, it’s fancy syntactic sugar, but it makes the call site really nice. Antoine van der Lee wrote an amazing post about result builders that looked at all the little things you can do when you’re combining things with
buildBlocks, but you could also handle
if let and other things like that.
But regarding the alignment and spacing for the VStack, one thing you can’t do is handle those parameters in the result builder itself, because it has to take elements that are essentially of the same type and produce something. And so those parameters for the alignment and the spacing need to be handled in the init. For example, I have an image library that I’m using to teach children about how to take an image and flip it horizontally or vertically, or how to put two images next to each other or rotate them by a fixed angle. If I, say, rotate 30 degrees, and I pass in an image, I can’t handle the 30 degrees in my result builder. I have to handle that somewhere else. I can only handle other views in my result builder. So I’m hoping the problem is that all of the methods inside result builders are static methods, and so there’s no notion of state for them, things pass through quickly. That’s been a discussion lately because, in the result builder proposal, it said that’s not part of the current proposal, but it could happen down the line.
And I think the combination of result builders and property wrappers can make this a very attractive language for us to build languages for children to learn. And for people new to programming who wouldn’t have to learn all the Swift syntax. And so that’s part of what I’m experimenting with—building these Domain Specific Languages [DSLs]. Right now, I’m doing this one for pictures, but I can imagine them being built for music in other settings as well. I’ve also used result builders to generate slides from my code.
VP: What I recommend to those people who have never implemented a property wrapper or a result builder is to really try to implement a simple one by following articles like the one by Antoine van der Lee. Actually, it’s very easy to do, but it’s super-interesting to understand how all of this works. Having these attributes that we can decorate our code with but are themselves written in Swift is quite new. And to understand how we can write Swift code that applies to other code, I think it’s really nice to give us an intuition of what’s happening behind the scenes. Sometimes it can also help us better understand when errors occur.
DS: Van der Lee’s article is very nice because, if I remember correctly, he’s using autolayout constraints in his example. So it’s something that we can all read and understand and see.
VP: I tried to use a result builder in my tests to say, “I want to assert on a list of properties.” The idea was to collect all of the properties together and then execute them one after the other to make sure that the test was passing. But when a test was failing, it was impossible to find the line of code where the issue was happening in the result builder. And since I didn’t have this information, I couldn’t make the error message appear on the correct line in Xcode when the test was running. When you have a lot of asserts, if you have to find the line manually, you lose all of the interesting aspects of the feature. If they were to add this missing part, I think it would open the way to even more useful use cases!
DS: A lot of the ideas behind behavior-driven development [BDD], where it’s the
if when then or
given when then, could benefit from these trailing closures from result builders. That could be very interesting.
This article is part of Behind the Code, the media for developers, by developers. Discover more articles and videos by visiting Behind the Code!
Want to contribute? Get published!
Follow us on Twitter to stay tuned!
Illustration by WTTJ
Tech Editor @ WTTJ
- Ajouter aux favoris
- Partager sur Twitter
- Partager sur Facebook
- Partager sur Linkedin