├── .gitignore ├── README.md ├── Whats-New-In-Swift-5-4.playground ├── Pages │ ├── Creating variables that call a function of the same name.xcplaygroundpage │ │ └── Contents.swift │ ├── Improved implicit member syntax.xcplaygroundpage │ │ └── Contents.swift │ ├── Introduction.xcplaygroundpage │ │ └── Contents.swift │ ├── Local functions now support overloading.xcplaygroundpage │ │ └── Contents.swift │ ├── Multiple variadic parameters in functions.xcplaygroundpage │ │ └── Contents.swift │ ├── Packages can now declare executable targets.xcplaygroundpage │ │ └── Contents.swift │ ├── Property wrappers are now supported for local variables.xcplaygroundpage │ │ └── Contents.swift │ └── Result builders.xcplaygroundpage │ │ └── Contents.swift └── contents.xcplayground └── playground-screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcshareddata/ 19 | xcuserdata/ 20 | 21 | ## Other 22 | .DS_Store 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What’s new in Swift 5.4? 2 | 3 | This is an Xcode playground that demonstrates the new features introduced in Swift 5.4: 4 | 5 | * Improved implicit member syntax 6 | * Multiple variadic parameters in functions 7 | * Creating variables that call a function of the same name 8 | * Result builders 9 | * Local functions now support overloading 10 | * Property wrappers are now supported for local variables 11 | * Packages can now declare executable targets 12 | 13 | This is designed to complement my existing article [What’s New in Swift 5.4](https://www.hackingwithswift.com/articles/228/whats-new-in-swift-5-4). You might also want to read [What’s New in Swift 5.3](https://www.hackingwithswift.com/articles/218/whats-new-in-swift-5-3), [What’s New in Swift 5.2](https://www.hackingwithswift.com/articles/212/whats-new-in-swift-5-2) and [What’s New in Swift 5.1](https://www.hackingwithswift.com/articles/182/whats-new-in-swift-5-1). 14 | 15 | Alternatively, I have a whole website dedicated to tracking [what's new in Swift](https://www.whatsnewinswift.com) – you should check it out at . 16 | 17 | If you hit problems or have questions, you're welcome to tweet me [@twostraws](https://twitter.com/twostraws) or email . 18 | 19 | ![Screenshot of Xcode 12.5 running this playground.](playground-screenshot.png) 20 | -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Creating variables that call a function of the same name.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | 4 |   5 | 6 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 7 | # Creating variables that call a function of the same name 8 | 9 | From Swift 5.4 onwards it’s possible to create a local variable by calling a function of the same name. That might sound obscure, but it’s actually a problem we hit all the time. 10 | 11 | For example, this creates a struct with a `color(forRow:)` method, which gets called and assigned to a local variable called `color`: 12 | */ 13 | struct Table { 14 | let count = 10 15 | 16 | func color(forRow row: Int) -> String { 17 | if row.isMultiple(of: 2) { 18 | return "red" 19 | } else { 20 | return "black" 21 | } 22 | } 23 | 24 | func printRows() { 25 | for i in 0.. String { 47 | var username = username 48 | username += String(Int.random(in: 1000...9999)) 49 | return username 50 | } 51 | } 52 | 53 | let user = User() 54 | user.suggestAlternativeUsername() 55 | /*: 56 | Because this also applies to global variables, that same code works just fine even without the struct in place: 57 | */ 58 | let username = "Taytay" 59 | 60 | func suggestAlternativeUsername() -> String { 61 | var username = username 62 | username += String(Int.random(in: 1000...9999)) 63 | return username 64 | } 65 | 66 | suggestAlternativeUsername() 67 | /*: 68 | 69 |   70 | 71 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 72 | */ -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Improved implicit member syntax.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | 4 |   5 | 6 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 7 | # Improved implicit member syntax 8 | 9 | [SE-0287](https://github.com/apple/swift-evolution/blob/main/proposals/0287-implicit-member-chains.md) improves Swift’s ability to use implicit member expressions, so rather than just having support for exactly one single static member you can make chains of them. 10 | 11 | Swift has always had the ability to use implicit member syntax for simple expressions, for example if you wanted to color some text in SwiftUI you could use `.red` rather than `Color.red`: 12 | */ 13 | import SwiftUI 14 | 15 | struct ContentView1: View { 16 | var body: some View { 17 | Text("Hello, World!") 18 | .foregroundColor(.red) 19 | } 20 | } 21 | /*: 22 | Prior to Swift 5.4 this did not work with more complex expressions. For example, if you wanted your red color to be slightly transparent you would need to write this: 23 | */ 24 | struct ContentView2: View { 25 | var body: some View { 26 | Text("Hello, World!") 27 | .foregroundColor(Color.red.opacity(0.5)) 28 | } 29 | } 30 | /*: 31 | From Swift 5.4 onwards the compiler is able to understand multiple chained members, meaning that the `Color` type can be inferred: 32 | */ 33 | struct ContentView3: View { 34 | var body: some View { 35 | Text("Hello, World!") 36 | .foregroundColor(.red.opacity(0.5)) 37 | } 38 | } 39 | /*: 40 | 41 |   42 | 43 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 44 | */ -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Introduction.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # What’s new in Swift 5.4 3 | 4 | * Created by [Paul Hudson](https://twitter.com/twostraws) – [Hacking with Swift](https://www.hackingwithswift.com) 5 | 6 | This playground is designed to showcase new features introduced in Swift 5.4. If you hit problems or have questions, you're welcome to tweet me [@twostraws](https://twitter.com/twostraws) or email . 7 | 8 | 9 |   10 | * [Improved implicit member syntax](Improved%20implicit%20member%20syntax) 11 | * [Multiple variadic parameters in functions](Multiple%20variadic%20parameters%20in%20functions) 12 | * [Local functions now support overloading](Local%20functions%20now%20support%20overloading) 13 | * [Creating variables that call a function of the same name](Creating%20variables%20that%20call%20a%20function%20of%20the%20same%20name) 14 | * [Result builders](Result%20builders) 15 | * [Property wrappers are now supported for local variables](Property%20wrappers%20are%20now%20supported%20for%20local%20variables) 16 | * [Packages can now declare executable targets](Packages%20can%20now%20declare%20executable%20targets) 17 | 18 |   19 | 20 | [Next >](@next) 21 | */ 22 | -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Local functions now support overloading.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | 4 |   5 | 6 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 7 | # Local functions now support overloading 8 | 9 | [SR-10069](https://bugs.swift.org/browse/SR-10069) requested the ability to overload functions in local contexts, which in practice means nested functions can now be overloaded so that Swift chooses which one to run based on the types that are used. 10 | 11 | For example, if we wanted to make some simple cookies we might write code like this: 12 | */ 13 | struct Butter { } 14 | struct Flour { } 15 | struct Sugar { } 16 | 17 | func makeCookies() { 18 | func add(item: Butter) { 19 | print("Adding butter…") 20 | } 21 | 22 | func add(item: Flour) { 23 | print("Adding flour…") 24 | } 25 | 26 | func add(item: Sugar) { 27 | print("Adding sugar…") 28 | } 29 | 30 | add(item: Butter()) 31 | add(item: Flour()) 32 | add(item: Sugar()) 33 | print("Come and get some cookies!") 34 | } 35 | 36 | makeCookies() 37 | /*: 38 | Prior to Swift 5.4, the three `add()` methods could be overloaded only if they were not nested inside `makeCookies()`, but from Swift 5.4 onwards function overloading is supported in this case as well. 39 | 40 | Swift 5.4 also lets us call local functions before they are declared, meaning that we can now write code like this if needed: 41 | */ 42 | func makeCookies2() { 43 | add(item: Butter()) 44 | add(item: Flour()) 45 | add(item: Sugar()) 46 | 47 | func add(item: Butter) { 48 | print("Adding butter…") 49 | } 50 | 51 | func add(item: Flour) { 52 | print("Adding flour…") 53 | } 54 | 55 | func add(item: Sugar) { 56 | print("Adding sugar…") 57 | } 58 | } 59 | 60 | makeCookies2() 61 | /*: 62 | 63 |   64 | 65 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 66 | */ -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Multiple variadic parameters in functions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | 4 |   5 | 6 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 7 | # Multiple variadic parameters in functions 8 | 9 | [SE-0284](https://github.com/apple/swift-evolution/blob/main/proposals/0284-multiple-variadic-parameters.md) introduced the ability to have functions, subscripts, and initializers use multiple variadic parameters as long as all parameters that follow a variadic parameter have labels. Before Swift 5.4, you could only have one variadic parameter in this situation. 10 | 11 | So, with this improvement in place we could write a function that accepts a variadic parameter storing the times goals were scored during a football match, plus a second variadic parameter scoring the names of players who scored: 12 | */ 13 | import Foundation 14 | 15 | func summarizeGoals(times: Int..., players: String...) { 16 | let joinedNames = ListFormatter.localizedString(byJoining: players) 17 | let joinedTimes = ListFormatter.localizedString(byJoining: times.map(String.init)) 18 | 19 | print("\(times.count) goals where scored by \(joinedNames) at the follow minutes: \(joinedTimes)") 20 | } 21 | /*: 22 | To call that function, provide both sets of values as variadic parameters, making sure that all parameters after the first variadic are labeled: 23 | */ 24 | summarizeGoals(times: 18, 33, 55, 90, players: "Dani", "Jamie", "Roy") 25 | /*: 26 | 27 |   28 | 29 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 30 | */ -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Packages can now declare executable targets.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | 4 |   5 | 6 | [< Previous](@previous)           [Home](Introduction) 7 | # Packages can now declare executable targets 8 | 9 | [SE-0294](https://github.com/apple/swift-evolution/blob/main/proposals/0294-package-executable-targets.md) adds a new target option for apps using Swift Package manager, allowing us to explicitly declare an executable target. 10 | 11 | This is particularly important for folks who want to use [SE-0281](https://github.com/apple/swift-evolution/blob/main/proposals/0281-main-attribute.md) (using `@main` to mark your program’s entry point), because it didn’t play nicely with Swift Package Manager – it would always look for a main.swift file. 12 | 13 | With this change, we can now remove main.swift and use `@main` instead. - important: You must specify `// swift-tools-version:5.4` in your Package.swift file in order to get this new functionality. 14 | 15 |   16 | 17 | [< Previous](@previous)           [Home](Introduction) 18 | */ -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Property wrappers are now supported for local variables.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | 4 |   5 | 6 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 7 | # Property wrappers are now supported for local variables 8 | 9 | Property wrappers were first introduced in Swift 5.1 as a way of attaching extra functionality to properties in an easy, reusable way, but in Swift 5.4 their behavior got extended to support using them as local variables in functions. 10 | 11 | For example, we could create a property wrapper that ensures its value never goes below zero: 12 | */ 13 | @propertyWrapper struct NonNegative { 14 | var value: T 15 | 16 | var wrappedValue: T { 17 | get { value } 18 | 19 | set { 20 | if newValue < 0 { 21 | value = 0 22 | } else { 23 | value = newValue 24 | } 25 | } 26 | } 27 | 28 | init(wrappedValue: T) { 29 | if wrappedValue < 0 { 30 | self.value = 0 31 | } else { 32 | self.value = wrappedValue 33 | } 34 | } 35 | } 36 | /*: 37 | And from Swift 5.4 onwards we can use that property wrapper inside a regular function, rather than just attaching to a property. For example, we might write a game where our player can gain or lose points, but their score should never go below 0: 38 | */ 39 | func playGame() { 40 | @NonNegative var score = 0 41 | 42 | // player was correct 43 | score += 10 44 | 45 | // player was correct again 46 | score += 10 47 | 48 | // player got one wrong 49 | score -= 50 50 | 51 | print(score) 52 | } 53 | 54 | playGame() 55 | /*: 56 | 57 |   58 | 59 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 60 | */ -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/Pages/Result builders.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | 4 |   5 | 6 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 7 | # Result builders 8 | 9 | Function builders unofficially arrived in Swift 5.1, but in the run up to Swift 5.4 they formally went through the Swift Evolution proposal process as [SE-0289](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md) in order to be discussed and refined. As part of that process they were renamed to result builders to better reflect their actual purpose, and even acquired some new functionality. 10 | 11 | First up, the most important part: result builders allow us to create a new value step by step by passing in a sequence of our choosing. They power large parts of SwiftUI’s view creation system, so that when we have a `VStack` with a variety of views inside, Swift silently groups them together into an internal `TupleView` type so that they can be stored as a single child of the `VStack` – it turns a sequence of views into a single view. 12 | 13 | Result builders deserve their own detailed article, but I at least want to give you some small code examples so you can see them in action. 14 | 15 | Here is a function that returns a single string: 16 | */ 17 | func makeSentence1() -> String { 18 | "Why settle for a Duke when you can have a Prince?" 19 | } 20 | 21 | print(makeSentence1()) 22 | /*: 23 | That works great, but what if had several strings we wanted to join together? Just like SwiftUI, we might want to provide them all individually and have Swift figure it out, however this kind of code won’t work: 24 | */ 25 | // This is invalid Swift, and will not compile. 26 | // func makeSentence2() -> String { 27 | // "Why settle for a Duke" 28 | // "when you can have" 29 | // "a Prince?" 30 | // } 31 | /*: 32 | By itself, that code won’t work because Swift no longer understands what we mean. However, we could create a result builder that understands how to convert several strings into one string using whatever transformation we want, like this: 33 | */ 34 | @resultBuilder 35 | struct SimpleStringBuilder { 36 | static func buildBlock(_ parts: String...) -> String { 37 | parts.joined(separator: "\n") 38 | } 39 | } 40 | /*: 41 | Even though that’s a small amount of code, there’s a lot to unpack: 42 | 43 | - The `@resultBuilder` attribute tells SwiftUI the following type should be treated as a result builder. Previously this behavior was achieved using `@_functionBuilder`, which had an underscore to show that this wasn’t designed for general use. 44 | - Every result builder must provide at least one static method called `buildBlock()`, which should take in some sort of data and transform it. The example above takes in zero or more strings, joins them, and sends them back as a single string. 45 | - The end result is that our `SimpleStringBuilder` struct becomes a result builder, meaning that we can use `@SimpleStringBuilder` anywhere we need its string joining powers. 46 | 47 | There’s nothing to stop us from using `SimpleStringBuilder.buildBlock()` directly, like this: 48 | */ 49 | let joined = SimpleStringBuilder.buildBlock( 50 | "Why settle for a Duke", 51 | "when you can have", 52 | "a Prince?" 53 | ) 54 | 55 | print(joined) 56 | /*: 57 | However, because we used the `@resultBuilder` annotation with our `SimpleStringBuilder` struct, we can also apply that to functions, like this: 58 | */ 59 | @SimpleStringBuilder func makeSentence3() -> String { 60 | "Why settle for a Duke" 61 | "when you can have" 62 | "a Prince?" 63 | } 64 | 65 | print(makeSentence3()) 66 | /*: 67 | Notice how we no longer need the commas at the end of each string – `@resultBuilder` automatically transforms each statement in `makeSentence()` into a single string by using `SimpleStringBuilder`. 68 | 69 | In practice, result builders are capable of significantly more, accomplished by adding more methods to your builder type. For example, we could add if/else support to our `SimpleStringBuilder` by adding two extra methods that describe how we want to transform the data. In our code we don’t want to transform our strings at all, so we can send them right back: 70 | */ 71 | @resultBuilder 72 | struct ConditionalStringBuilder { 73 | static func buildBlock(_ parts: String...) -> String { 74 | parts.joined(separator: "\n") 75 | } 76 | 77 | static func buildEither(first component: String) -> String { 78 | return component 79 | } 80 | 81 | static func buildEither(second component: String) -> String { 82 | return component 83 | } 84 | } 85 | /*: 86 | I know that looks like we’ve done almost no work, but now our functions are able to use conditions: 87 | */ 88 | @ConditionalStringBuilder func makeSentence4() -> String { 89 | "Why settle for a Duke" 90 | "when you can have" 91 | 92 | if Bool.random() { 93 | "a Prince?" 94 | } else { 95 | "a King?" 96 | } 97 | } 98 | 99 | print(makeSentence4()) 100 | /*: 101 | Similarly, we could add support for loops by adding a `buildArray()` method to our builder type: 102 | */ 103 | @resultBuilder 104 | struct ComplexStringBuilder { 105 | static func buildBlock(_ parts: String...) -> String { 106 | parts.joined(separator: "\n") 107 | } 108 | 109 | static func buildEither(first component: String) -> String { 110 | return component 111 | } 112 | 113 | static func buildEither(second component: String) -> String { 114 | return component 115 | } 116 | 117 | static func buildArray(_ components: [String]) -> String { 118 | components.joined(separator: "\n") 119 | } 120 | } 121 | /*: 122 | And now we can use `for` loops: 123 | */ 124 | @ComplexStringBuilder func countDown() -> String { 125 | for i in (0...10).reversed() { 126 | "\(i)…" 127 | } 128 | 129 | "Lift off!" 130 | } 131 | 132 | print(countDown()) 133 | /*: 134 | It feels almost like magic because the result builder system is doing almost all the work for us, and even though our example has been fairly simple I hope you can get a taste for the remarkable power result builders bring to Swift. 135 | 136 | It’s worth adding that Swift 5.4 extends the result builder system to [support attributes being placed on stored properties](https://bugs.swift.org/browse/SR-13188), which automatically adjusts the implicit memberwise initializer for structs to apply the result builder. 137 | 138 | This is particularly helpful for custom SwiftUI views that use result builders, such as this one: 139 | */ 140 | import SwiftUI 141 | 142 | struct CustomVStack: View { 143 | @ViewBuilder let content: Content 144 | 145 | var body: some View { 146 | VStack { 147 | // custom functionality here 148 | content 149 | } 150 | } 151 | } 152 | /*: 153 | If you’d like to see more advanced, real-world examples of result builders in action, you should check out the [Awesome Function Builders repository on GitHub](https://github.com/carson-katri/awesome-function-builders). 154 | 155 |   156 | 157 | [< Previous](@previous)           [Home](Introduction)           [Next >](@next) 158 | */ -------------------------------------------------------------------------------- /Whats-New-In-Swift-5-4.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /playground-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twostraws/whats-new-in-swift-5-4/0c205e181a7f00243b70043495d6f835447f28eb/playground-screenshot.png --------------------------------------------------------------------------------