Swift 5.1 is now officially released and despite being a minor release, it contains a number of significant changes and improvements – from basic new features, such as module stability (allowing vendors to grant SDK send pre-compiled Swift frameworks), for all the features, new syntax for SwiftUI and more. In addition to new features, Swift 5.1 contains a number of smaller features – but still makes a lot of sense – new capabilities and new enhancements. It arranges the types of changes that may seem small or even unnecessary at first but can have a significant impact on the way we write and structure Swift code. In this article, consider five of those features and which situations they might be useful.
Memberwise initializers with default values
One of the things that makes structs appealing in Swift is member generators, auto-generated “memberwise” initializers – allows us to initialize any structure (not containing private stored properties) only by passing values corresponding to each of its properties, like this:
1 2 3 4 5 6 7 |
struct Message { var subject: String var body: String } let message = Message(subject: "Hello", body: "From Swift") |
These aggregated constructors have been significantly improved in Swift 5.1, because they now take default property values into account and automatically translate those values into the default initializer arguments. . Assume that we want to extend the above struct Message with support for attachments, but we want the default value to be a blank array – at the same time, we also want to allow the Message to be initialized without The body initialization needs to be defined already, so we’ll also give the properties a default value:
1 2 3 4 5 6 |
struct Message { var subject: String var body = "" var attachments: [Attachment] = [] } |
In versions of Swift 5.0 and earlier, we still had to pass initialization parameters for all of the above properties, regardless of whether they were the default values or not. However, in Swift 5.1, we can instantiate a Message by passing only one property, like this:
1 2 |
var message = Message(subject: "Hello, world!") |
That’s great, and it makes using struct even more convenient than before. But perhaps even more amazing, like when using standard default arguments, we can still override any default attribute value by passing an argument to it – which gives Gives us lots of flexibility:
1 2 3 4 5 |
var message = Message( subject: "Hello, world!", body: "Swift 5.1 is such a great update!" ) |
However, although member creation tools are extremely useful in an application or module, they are not displayed as part of the module’s public API – that is, if we build a library or framework. , we still have to define public-facing initializers manually.
Using Self to refer to enclosing types
Swift’s Self keyword has previously allowed us to automatically refer to a category in context in which the actual category is not known. For example, by referring to the implementation type of the protocol in a protocol extension:
1 2 3 4 5 6 |
extension Numeric { func incremented(by value: Self = 1) -> Self { return self + value } } |
The scope of Self has now been extended to include specific types – such as enums, structs, and classes – that allow us to use Self as an alias referring to a method or enclosing type, like this. :
1 2 3 4 5 6 7 8 9 10 |
extension TextTransform { static var capitalize: Self { return TextTransform { $0.capitalized } } static var removeLetters: Self { return TextTransform { $0.filter { !$0.isLetter } } } } |
The fact that we can now use Self above, instead of the full TextTransform type name – It can help make our code a bit more neat, especially when dealing with long type names. We can even use Self inline in a method or attribute:
1 2 3 4 5 6 7 8 9 10 |
extension TextTransform { static var capitalize: Self { return Self { $0.capitalized } } static var removeLetters: Self { return Self { $0.filter { !$0.isLetter } } } } |
In addition to referencing a enclosing type, we can now also use Self to access static members in a method or attribute – quite useful in situations when we want to reuse the same. The value in all instances of a type, such as cellReuseIdentifier in this example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ListViewController: UITableViewController { static let cellReuseIdentifier = "list-cell" override func viewDidLoad() { super.viewDidLoad() tableView.register( ListTableViewCell.self, forCellReuseIdentifier: Self.cellReuseIdentifier ) } } |
Again, we can simply type ListViewController above when accessing our static property, but using Self will improve our code readability – and will also allow us to rename our view controller’s name. You don’t have to update how to access its static members.
Switching on optionals
Next, let’s see how Swift 5.1 helps implement pattern matching on optionals, which is really helpful when using optional value. For example, suppose we are working on a music application that contains the Song model – whose downloadState property allows us to track whether the song has been downloaded.
1 2 3 4 5 |
struct Song { ... var downloadState: DownloadState? } |
Swift’s advanced pattern matching allows us to directly convert an optional value – without having to unwrap first – however, before Swift 5.1, wanting to do so requires us to add question marks for each. Match case, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func songDownloadStateDidChange(_ song: Song) { switch song.downloadState { case .downloadInProgress?: showProgressIndiator(for: song) case .downloadFailed(let error)?: showDownloadError(error, for: song) case .downloaded?: downloadDidFinish(for: song) case nil: break } } |
In Swift 5.1, the question marks at the back are no longer needed and now we can simply refer case by case – just like when the switch sabg has a non-optional value:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func songDownloadStateDidChange(_ song: Song) { switch song.downloadState { case .downloadInProgress: showProgressIndiator(for: song) case .downloadFailed(let error): showDownloadError(error, for: song) case .downloaded: downloadDidFinish(for: song) case nil: break } } |
While the above is a really welcome change of the syntax reduction needed to implement common patterns, it comes with a slight side effect, potentially breaking the source code for a number of enums and switches. Because Swift optionals are implemented using Optional
enum under the hood, we cannot know if the property is optional or not
The Identifiable protocol
Initially introduced as part of the SwiftUI initial release. Identifiable
new Identifiable
protocol is now included in the swift standard library – and provides a simple and consistent way to mark any stable or unique identifier. To conform to this new protocol, you simply need to declare a property id
, which can contain any Hashable
type – For example, String
:
1 2 3 4 5 6 7 |
struct User: Identifiable { typealias ID = String var id: ID var name: String } |
Similarly, when Result
is added to the standard library as part of Swift 5.0, the main benefit of making changes in swift 5.1 is being able to access Identifable
for any Swift module and it can be used to Share the requirements on different code bases. For example, using the constrained protocol extension, we can add the convenience API to convert any sequence
containing identifiable elements into a dictionary – then return the extension as part of the library without requiring us to define any protocol for it:
1 2 3 4 5 6 7 8 |
public extension Sequence where Element: Identifiable { func keyedByID() -> [Element.ID : Element] { var dictionary = [Element.ID : Element]() forEach { dictionary[$0.id] = $0 } return dictionary } } |
However, while the Identifiable protocol in the standard library is really useful when dealing with collections of values that each collection has a fixed identifier, it doesn’t do much to improve the security of our actual code type. . Because all that is identifiable is asking us to identify any hashable id properties, it will not protect us from accidentally mixing an identifier – such as in this case, when we mistakenly switch the User
ID to the Video
ID acceptance function:
1 2 |
postComment(comment, onVideoWithID: user.id) |
So there are many powerful use cases for the Identifier
type match and the Identifier
protocol such as those we reviewed in [“Type-safe identifiers in Swift”] ( https: //www.swiftbysundell. com / articles / type-safe-identifiers-in-swift / ) to prevent possible types of errors. However, it is still fine to have some versions of an Identifier
protocol in the standard gay library even if it is a bit more limited.
Ordered collection diffing
Finally, let’s look at the brand new standard library API introduced as part of swift 5.1 – ordered collection diffing. As we as a community, get closer to the world of declarative programming with tools like Combine and SwiftUI. Being able to calculate the differences between the two states is becoming increasingly important. After all, declarative UI development is a continuous rendering of state snapshots – and SwiftUI and new diffable data sources will probably do most of the heavy lifting to make it a reality – self-calculating. The difference between the two states is extremely useful. For example, suppose we build a DatabaseController
that allows us to easily update our disk database with a series of models in memory. To find out whether a model should be inserted or deleted, we can call the new difference
API to calculate the difference between our old array and the new one – and then repeat through internal changes. diff to perform our database operations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class DatabaseController<Model: Hashable & Identifiable> { private let database: Database private(set) var models: [Model] = [] ... func update(with newModels: [Model]) { let diff = newModels.difference(from: models) for change in diff { switch change { case .insert(_, let model, _): database.insert(model) case .remove(_, let model, _): database.delete(model) } } models = newModels } } |
However, the above implementation does not take into account the moved models – by default, moves will be treated as separate inserts and deletions. To fix that, we’ll call the inferringMoves
method when calculating our diff – and then consider whether each insert is related to deletion and instead, let’s treat it as a moved, like after:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
func update(with newModels: [Model]) { let diff = newModels.difference(from: models).inferringMoves() for change in diff { switch change { case .insert(let index, let model, let association): // If the associated index isn't nil, that means // that the insert is associated with a removal, // and we can treat it as a move. if association != nil { database.move(model, toIndex: index) } else { database.insert(model) } case .remove(_, let model, let association): // We'll only process removals if the associated // index is nil, since otherwise we will already // have handled that operation as a move above. if association == nil { database.delete(model) } } } models = newModels } |
The fact that diffing is now integrated into the standard library (and both UIKit and AppKit) is great news – because writing an efficient, flexible and powerful diffing algorithm can be extremely difficult.
Conclusion
Swift 5.1 is not only a major support tool for SwiftUI and Combine, it is also great news for any team providing pre-compiled frameworks, because Swift is now not only ABI stable but also stable module. On top of that, Swift 5.1 also includes many small but welcome improvements and improvements that apply to almost every code base – and we have looked at five of those changes in this article.
Hope the article will be useful to you.
Reference: https://www.swiftbysundell.com/articles/5-small-but-significant-improvements-in-swift-5-1/