Repository: twostraws/whats-new-in-swift-4-1 Branch: main Commit: 26bcc8fbb688 Files: 11 Total size: 21.8 KB Directory structure: gitextract_5g1yjty1/ ├── .gitignore ├── README.md └── Whats-New-In-Swift-4-1.playground/ ├── Pages/ │ ├── Build configuration import testing.xcplaygroundpage/ │ │ └── Contents.swift │ ├── Conditional conformances.xcplaygroundpage/ │ │ └── Contents.swift │ ├── Introduction.xcplaygroundpage/ │ │ └── Contents.swift │ ├── Key decoding strategy in Codable.xcplaygroundpage/ │ │ └── Contents.swift │ ├── Recursive constraints on associated types.xcplaygroundpage/ │ │ └── Contents.swift │ ├── Synthesized Equatable and Hashable.xcplaygroundpage/ │ │ └── Contents.swift │ ├── Target environment testing.xcplaygroundpage/ │ │ └── Contents.swift │ └── flatMap() is now (partly) compactMap().xcplaygroundpage/ │ └── Contents.swift └── contents.xcplayground ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcshareddata/ xcuserdata/ ## Other .DS_Store *.moved-aside *.xccheckout *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output ================================================ FILE: README.md ================================================ # What’s new in Swift 4.1? This is an Xcode playground that demonstrates the new features introduced in Swift 4.1: * Synthesized `Equatable` and `Hashable` * Key decoding strategy in `Codable` * Conditional conformances * Recursive constraints on associated types * Build configuration import testing * Target environment testing * `flatMap()` is now (partly) `compactMap()` This is designed to complement my existing article [What’s New in Swift 4.1](https://www.hackingwithswift.com/articles/50/whats-new-in-swift-4-1). You might also want to check out [What's New in Swift 4.2](https://www.hackingwithswift.com/articles/77/whats-new-in-swift-4-2) and its [accompanying playground](https://github.com/twostraws/whats-new-in-swift-4-2). Alternatively, I have a whole website dedicated to tracking [what's new in Swift](https://www.whatsnewinswift.com) – you should check it out at . If you hit problems or have questions, you're welcome to tweet me [@twostraws](https://twitter.com/twostraws) or email . **Note:** This playground requires Swift 4.1, so you should have installed Xcode 9.3 or later. This is available through the [Mac App Store](https://itunes.apple.com/gb/app/xcode/id497799835?mt=12) or direct from . ![Screenshot of Xcode 9.3 running this playground.](playground-screenshot.png) ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/Build configuration import testing.xcplaygroundpage/Contents.swift ================================================ /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) ## Build configuration import testing Swift 4.1 implements [SE-0075](https://github.com/apple/swift-evolution/blob/master/proposals/0075-import-test.md), which introduces a new `canImport` condition that lets us check whether a specific module can be imported when our code is compiled. This is particularly important for cross-platform code: if you had a Swift file that implemented one behavior on macOS and another on iOS, or if you needed specific functionality for Linux. For example: */ #if canImport(SpriteKit) // this will be true for iOS, macOS, tvOS, and watchOS #else // this will be true for other platforms, such as Linux #endif /*: Previously you would have had to use inclusive or exclusive tests by operating system, like this: */ #if !os(Linux) // Matches macOS, iOS, watchOS, tvOS, and any other future platforms #endif #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) // Matches only Apple platforms, but needs to be kept up to date as new platforms are added #endif /*: The new `canImport` condition lets us focus on the functionality we care about rather than what platform we're compiling for, thus avoiding a variety of problems.   [< Previous](@previous)           [Home](Introduction)           [Next >](@next) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/Conditional conformances.xcplaygroundpage/Contents.swift ================================================ /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) ## Conditional conformances Swift 4.1 implements [SE-0143](https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md), which introduced proposed conditional conformances into the language. This allows types to conform to a protocol only when certain conditions are met. To demonstrate conditional conformances, let's create a `Purchaseable` protocol that we can use to buy things: */ protocol Purchaseable { func buy() } /*: We can now define a `Book` struct that conforms to the protocol, and prints a message when a book is bought: */ struct Book: Purchaseable { func buy() { print("You bought a book") } } /*: So far this is easy enough, but let's take it one step further: what if the user has a basket full of books, and wants to buy them all? We could loop over all books in the array by hand, calling `buy()` on each one. But a better approach is to write an extension on `Array` to make it conform to `Purchaseable`, then give it a `buy()` method that in turn calls `buy()` on each of its elements. This is where conditional conformances come in: if we tried to extend all arrays, we'd be adding functionality where it wouldn't make sense – we'd be adding `buy()` to arrays of strings, for example, even though those strings don't have a `buy()` method we can call. Swift 4.1 lets us make arrays conform to `Purchaseable` only if their elements also conform to `Purchaseable`, like this: */ extension Array: Purchaseable where Element: Purchaseable { func buy() { for item in self { item.buy() } } } /*: As you can see, conditional conformances let us constrain the way our extensions are applied more precisely than was possible before. Conditional conformances also make large parts of Swift code easier and safer, even if you don't do any extra work yourself. For example, this code creates two arrays of optional strings and checks whether they are equal: */ var left: [String?] = ["Andrew", "Lizzie", "Sophie"] var right: [String?] = ["Charlotte", "Paul", "John"] left == right /*: That might seem trivial, but that code wouldn't even compile in Swift 4.0 – both `String` and `[String]` were equatable, but `[String?]` was not. The introduction of conditional conformance in Swift 4.1 means that it’s now possible to add protocol conformance to a type as long as it satisfies a condition. In this case, if the elements of the array are equatable, that means the whole thing is equatable. So, the above code now compiles in Swift 4.1 Conditional conformance has been extended to the `Codable` protocol in a way that will definitely make things safer. For example: */ import Foundation struct Person { var name = "Taylor" } var people = [Person()] var encoder = JSONEncoder() // try encoder.encode(people) /*: If you uncomment the `encoder.encode(people)` line, Swift will refuse to build your code because you're trying to encode a struct that doesn't conform to `Codable`. However, that code compiled cleanly with Swift 4.0, then threw a fatal error at runtime because `Person` doesn’t conform to `Codable`. Obviously no one wants a fatal error at runtime, because it means your app crashes. Fortunately, Swift 4.1 cleans this up using conditional conformances: `Optional`, `Array`, `Dictionary`, and `Set` now only conform to `Codable` if their contents also conform to `Codable`, so the above code will refuse to compile.   [< Previous](@previous)           [Home](Introduction)           [Next >](@next) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/Introduction.xcplaygroundpage/Contents.swift ================================================ /*: # What’s new in Swift 4.1 * Created by [Paul Hudson](https://twitter.com/twostraws) – [Hacking with Swift](https://www.hackingwithswift.com) * Last update: April 4th 2018 This playground is designed to showcase new features introduced with Swift 4.1. I already [wrote an article on it](https://www.hackingwithswift.com/articles/50/whats-new-in-swift-4-1), but it's a lot more fun to see things in action and experiment yourself. If you hit problems or have questions, you're welcome to tweet me [@twostraws](https://twitter.com/twostraws) or email . - important: This playground requires **Xcode 9.3** or later, but if you're stuck on an older version you can [install a Swift toolchain](https://swift.org/download/) for your current Xcode version and use that instead.   ## Contents * [Synthesized `Equatable` and `Hashable`](Synthesized%20Equatable%20and%20Hashable) * [Key decoding strategy in `Codable`](Key%20decoding%20strategy%20in%20Codable) * [Conditional conformances](Conditional%20conformances) * [Recursive constraints on associated types](Recursive%20constraints%20on%20associated%20types) * [Build configuration import testing](Build%20configuration%20import%20testing) * [Target environment testing](Target%20environment%20testing) * [`flatMap()` is now (partly) `compactMap()`](flatMap()%20is%20now%20(partly)%20compactMap())   [Next >](@next) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/Key decoding strategy in Codable.xcplaygroundpage/Contents.swift ================================================ /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) ## Key decoding strategy in `Codable` The `Codable` protocol, introduced in Swift 4.0, is the fastest and easiest way of converting your data types to JSON and back again. However, often you'd hit a problem where `Codable` wasn't quite as easy as you had hoped: if your JSON used snake_case for its key names and your Swift code used camelCase for its matching property names, `Codable` wouldn’t be able to convert between the two. Instead, you needed to create your own `CodingKeys` mapping to explain how the two matched up. Swift 4.1 solves this problem by introducing a new `keyDecodingStrategy` property on `JSONDecoder` that can automatically convert between snake_case and camelCase if you need it. The inverse property, `keyEncodingStrategy`, also exists on `JSONEncoder` so you can convert your Swift camelCase names back into snake_case. Let's look at a practical example – this code creates some sample JSON, then converts it into a `Data` instance: */ import Foundation let jsonString = """ [ { "name": "MacBook Pro", "screen_size": 15, "cpu_count": 4 }, { "name": "iMac Pro", "screen_size": 27, "cpu_count": 18 } ] """ let jsonData = Data(jsonString.utf8) /*: That stores an array of two items, each describing a Mac. As you can see, both `screen_size` and `cpu_count` use snake_case – words are all lowercased, with underscores separating them. Now, we want to convert that JSON into an array of `Mac` instances using this struct: */ struct Mac: Codable { var name: String var screenSize: Int var cpuCount: Int } /*: That follows standard Swift naming conventions, so the property names are all camelCased – words have no separators, but second and subsequent words all start with a capital letter. Using the new `keyDecodingStrategy` of `JSONDecoder` we can convert our sample JSON into a `[Mac]` array like this: */ let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase do { let macs = try decoder.decode([Mac].self, from: jsonData) print(macs) } catch { print(error.localizedDescription) } /*: When you want to go back the other way – to convert a Codable struct with camelCase properties back to JSON with snake_case keys, set the `keyEncodingStrategy` property of your `JSONEncoder` to `.convertToSnakeCase` like this: */ if let macs = try? decoder.decode([Mac].self, from: jsonData) { // now we have an array to work with, convert it back into JSON let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase let encoded = try encoder.encode(macs) print(encoded.count) } /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/Recursive constraints on associated types.xcplaygroundpage/Contents.swift ================================================ /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) ## Recursive constraints on associated types Swift 4.1 implements [SE-0157](https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md), which lifts restrictions on the way we use associated types inside protocols. As a result, we can now create recursive constraints for our associated types: associated types that are constrained by the protocol they are defined in. To demonstrate this, let's consider a simple team hierarchy in a tech company. In this company, every employee has a manager – someone more senior to them that they report to. Each manager must also be an employee of the company, because it would be weird if they weren't. We can express this relationship in a simple `Employee` protocol: */ protocol Employee { associatedtype Manager: Employee var manager: Manager? { get set } } /*: - note: I've used an optional `Manager?` because ultimately one person (presumably the CEO) has no manager.   Even though that's a fairly self-evident relationship, it wasn't possible to compile that code in Swift 4.0 because we're using the `Employee` protocol inside itself. However, this is fixed in Swift 4.1 because of the new ability to use recursive constraints on associated types. Thanks to this new feature, we can model a simple tech company that has three kinds of team members: junior developers, senior developers, and board members. The reporting structure is also simple: junior developers are managed by senior developers, senior developers are managed by board members, and board members may be managed by another board member – e.g. the CTO reporting to the CEO. That looks exactly as you would imagine thanks to Swift 4.1: */ class BoardMember: Employee { var manager: BoardMember? } class SeniorDeveloper: Employee { var manager: BoardMember? } class JuniorDeveloper: Employee { var manager: SeniorDeveloper? } /*: - note: I've used classes here rather than structs because `BoardMember` itself contains a `BoardMember` property and that would result in an infinitely sized struct. If one of these has to be a class I personally would prefer to make all three classes just for consistency, but if you preferred you could leave `BoardMember` as a class and make both `SeniorDeveloper` and `JuniorDeveloper` into structs.   [< Previous](@previous)           [Home](Introduction)           [Next >](@next) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/Synthesized Equatable and Hashable.xcplaygroundpage/Contents.swift ================================================ /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) ## Synthesized `Equatable` and `Hashable` The `Equatable` protocol allows Swift to compare one instance of a type against another. When we say 5 == 5, Swift understands what that means because `Int` conforms to `Equatable`, which means it implements a function describing what `==` means for two instances of `Int`. Implementing `Equatable` in our own value types allows them to work like Swift’s strings, arrays, numbers, and more, and it’s usually a good idea to make your structs conform to `Equatable` just so they fit the concept of value types better. Swift 4.1 implements [SE-0185](https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md), which allows the compiler to synthesize conformance for `Equatable` – it can generate an `==` method automatically, which will compare all properties in one value with all properties in another. All you have to do is add `Equatable` to your type, and Swift can take care of the rest. For example: */ struct Person: Equatable { var firstName: String var lastName: String var age: Int } let paul = Person(firstName: "Paul", lastName: "Hudson", age: 38) let joanne = Person(firstName: "Joanne", lastName: "Rowling", age: 52) if paul == joanne { print("These people match") } else { print("No match") } /*: Of course, if you *want* you can implement `==` yourself. For example, if your type has an `id` property that identifies it uniquely, you would write `==` to compare that single value rather than letting Swift do all the extra work of comparing all properties. In this next example, each instance of `User` has a variety of properties that the default implementation of `==` would have to compare in order to check whether two objects are equal. However, by adding our own `==` implementation we can check a property we know is unique: */ struct User: Equatable { var id: Int var username: String var password: String var jobTitle: String var firstName: String var lastName: String static func ==(lhs: User, rhs: User) -> Bool { return lhs.id == rhs.id } } /*: Swift 4.1 also introduces synthesized support for the `Hashable` protocol, which means it will generate a `hashValue` property for conforming types automatically. `Hashable` was always annoying to implement because you need to return a unique (or at least mostly unique) hash for every object. It’s important, though, because it lets you use your objects as dictionary keys and store them in sets. Previously we’d need to write code like this: */ extension User: Hashable { var hashValue: Int { return firstName.hashValue ^ lastName.hashValue &* 16777619 } } /*: For the most part that’s no longer needed in Swift 4.1: just adding the `Hashable` protocol to your type will ask the Swift compiler to generate the `hashValue` property for you. As with `Equatable`, you can still write your own code if there’s something specific you need. - note: You still need to opt in to these protocols by adding a conformance to your type, and using the synthesized code does require that all properties in your type conform to `Equatable` or `Hashable` respectively.   [< Previous](@previous)           [Home](Introduction)           [Next >](@next) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/Target environment testing.xcplaygroundpage/Contents.swift ================================================ /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) ## Target environment testing Swift 4.1 implements [SE-0190](https://github.com/apple/swift-evolution/blob/master/proposals/0190-target-environment-platform-condition.md), which introduces a new `targetEnvironment` condition that lets us differentiate between builds that are for physical devices and those that are for a simulated environment. At this time `targetEnvironment` has only one value, `simulator`, which will be true if your build is targeting a simulated device such as the iOS Simulator. For example: */ #if targetEnvironment(simulator) // code for the simulator here #else // code for real devices here #endif /*: This is useful when writing code to deal with functionality the simulator doesn't support, such as capturing photos from a camera or reading the accelerometer. As an example, let's look at processing a photo from the camera. If we're running on a real device we'll create and configure a `UIImagePickerController()` to take photos using the camera, but if we're in the simulator we'll just load a sample image from our app bundle: */ import UIKit class TestViewController: UIViewController, UIImagePickerControllerDelegate { // a method that does some sort of image processing func processPhoto(_ img: UIImage) { // process photo here } // a method that loads a photo either using the camera or using a sample func takePhoto() { #if targetEnvironment(simulator) // we're building for the simulator; use the sample photo if let img = UIImage(named: "sample") { processPhoto(img) } else { fatalError("Sample image failed to load") } #else // we're building for a real device; take an actual photo let picker = UIImagePickerController() picker.sourceType = .camera vc.allowsEditing = true picker.delegate = self present(picker, animated: true) #endif } // this is called if the photo was taken successfully func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { // hide the camera picker.dismiss(animated: true) // attempt to retrieve the photo they took guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else { // that failed; bail out return } // we have an image, so we can process it processPhoto(image) } } /*: [< Previous](@previous)           [Home](Introduction)           [Next >](@next) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/Pages/flatMap() is now (partly) compactMap().xcplaygroundpage/Contents.swift ================================================ /*: [< Previous](@previous)           [Home](Introduction) ## `flatMap()` is now (partly) `compactMap()` The `flatMap()` method was useful for a variety of things in Swift 4.0, but one was particularly popular: the ability to transform each object in a collection, then remove any items that were nil afterwards Swift Evolution proposal [SE-0187](https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md) suggested changing this, and as of Swift 4.1 this `flatMap()` variant has been renamed to `compactMap()` to make its meaning clearer. For example: */ let array = ["1", "2", "Fish"] let numbers = array.compactMap { Int($0) } print(numbers) /*: That will create an `Int` array containing the numbers 1 and 2, because "Fish" will fail conversion to Int, return nil, and be ignored. - note: To avoid confusion, it's worth adding that only this specific usage `flatMap()` has been renamed to `compactMap()` – the `flatMap()` method still retains its other behaviors.   [< Previous](@previous)           [Home](Introduction) */ ================================================ FILE: Whats-New-In-Swift-4-1.playground/contents.xcplayground ================================================