├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── README.md ├── Whats-new-in-Swift-4.playground ├── Pages │ ├── Associated type constraints.xcplaygroundpage │ │ └── Contents.swift │ ├── Composing classes and protocols.xcplaygroundpage │ │ └── Contents.swift │ ├── Dictionary and Set enhancements.xcplaygroundpage │ │ └── Contents.swift │ ├── Encoding and decoding.xcplaygroundpage │ │ └── Contents.swift │ ├── Generic subscripts.xcplaygroundpage │ │ └── Contents.swift │ ├── Integer protocols.xcplaygroundpage │ │ ├── Contents.swift │ │ └── Resources │ │ │ └── integer-protocols.png │ ├── Key paths.xcplaygroundpage │ │ └── Contents.swift │ ├── Limiting @objc inference.xcplaygroundpage │ │ └── Contents.swift │ ├── NSNumber bridging.xcplaygroundpage │ │ └── Contents.swift │ ├── One-sided ranges.xcplaygroundpage │ │ └── Contents.swift │ ├── Strings.xcplaygroundpage │ │ └── Contents.swift │ ├── Table of contents.xcplaygroundpage │ │ ├── Contents.swift │ │ └── Resources │ │ │ └── xcode-8-3-toolchain-dialog.png │ ├── private in same-file extensions.xcplaygroundpage │ │ └── Contents.swift │ ├── reduce with inout.xcplaygroundpage │ │ └── Contents.swift │ └── swapAt.xcplaygroundpage │ │ └── Contents.swift ├── contents.xcplayground └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Whats-new-in-Swift-4.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist └── playground-screenshot.png /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | If you updated the code for a more recent Swift toolchain, did you remember to also update the "Latest toolchain tested" date on the first page of the playground? That would be rad. 2 | 3 | Thank you so much! 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | .DS_Store 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What’s new in Swift 4 2 | 3 | An Xcode playground showcasing the new features in Swift 4.0. As seen in the [What’s New in Swift session at WWDC 2017][WWDC 2017 402]. 4 | 5 | Written by [Ole Begemann][Ole Begemann], May–September 2017. 6 | 7 | See also: my [What’s new in Swift 4.2 playground](https://github.com/ole/whats-new-in-swift-4-2) from 2018. 8 | 9 | The playground requires Swift 4. [Xcode](https://developer.apple.com/xcode/) 9 includes Swift 4 by default. 10 | 11 | You can also run it in Xcode 8.3, but you need to install [the latest Swift 4.0 snapshot from swift.org][Snapshot downloads] (don’t worry, it’s easy). 12 | 13 | ![Screenshot of the playground in Xcode 8.3][Playground screenshot] 14 | 15 | [WWDC 2017 402]: https://developer.apple.com/videos/play/wwdc2017/402/ 16 | [Ole Begemann]: https://oleb.net 17 | [Snapshot downloads]: https://swift.org/download/#snapshots 18 | [Playground screenshot]: playground-screenshot.png 19 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Associated type constraints.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## Associated type constraints 5 | 6 | [SE-0142][SE-0142]: associated types in protocols can now be constrained with `where` clauses. This seemingly small change makes the type system much more expressive and facilitates a significant simplification of the standard library. In particular, working with `Sequence` and `Collection` is a lot more intuitive in Swift 4. 7 | 8 | [SE-0142]: https://github.com/apple/swift-evolution/blob/master/proposals/0142-associated-types-constraints.md "Swift Evolution Proposal SE-0142: Permit where clauses to constrain associated types" 9 | 10 | ### `Sequence.Element` 11 | 12 | `Sequence` now has its own `Element` associated type. This is made possible by the new generics feature because `associatedtype Element where Element == Iterator.Element` can now be expressed in the type system. 13 | 14 | Anywhere you had to write `Iterator.Element` in Swift 3, you can now just write `Element`: 15 | */ 16 | extension Sequence where Element: Numeric { 17 | var sum: Element { 18 | var result: Element = 0 19 | for element in self { 20 | result += element 21 | } 22 | return result 23 | } 24 | } 25 | 26 | [1,2,3,4].sum 27 | 28 | /*: 29 | Another example: In Swift 3, this extension required more constraints because the type system could not express the idea that the elements of `Collection`’s associated `Indices` type had the same type as `Collection.Index`: 30 | 31 | // Required in Swift 3 32 | extension MutableCollection where Index == Indices.Iterator.Element { 33 | */ 34 | extension MutableCollection { 35 | /// Maps over the elements in the collection in place, replacing the existing 36 | /// elements with their transformed values. 37 | mutating func mapInPlace(_ transform: (Element) throws -> Element) rethrows { 38 | for index in indices { 39 | self[index] = try transform(self[index]) 40 | } 41 | } 42 | } 43 | 44 | /*: 45 | ## More generics features 46 | 47 | Two more important generics improvements have been accepted for Swift 4, but aren’t implemented yet: 48 | 49 | * [SE-0143 Conditional protocol conformances][SE-0143] 50 | * [SE-0157 Recursive protocol constraints][SE-0157] 51 | 52 | [SE-0143]: https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md 53 | [SE-0157]: https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md 54 | */ 55 | 56 | /*: 57 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 58 | */ 59 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Composing classes and protocols.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) 3 | 4 | ## Composing classes and protocols 5 | 6 | [SE-0156][SE-0156]: You can now write the equivalent of the Objective-C code `UIViewController *` in Swift, i.e. declare a variable of a concrete type and constrain it to one or more protocols at the same time. The syntax is `let variable: SomeClass & SomeProtocol1 & SomeProtocol2`. 7 | 8 | [SE-0156]: https://github.com/apple/swift-evolution/blob/master/proposals/0156-subclass-existentials.md "Swift Evolution Proposal SE-0156: Class and Subtype existentials" 9 | */ 10 | import UIKit 11 | 12 | protocol HeaderView {} 13 | 14 | class ViewController: UIViewController { 15 | let header: UIView & HeaderView 16 | 17 | init(header: UIView & HeaderView) { 18 | self.header = header 19 | super.init(nibName: nil, bundle: nil) 20 | } 21 | 22 | required init(coder decoder: NSCoder) { 23 | fatalError("not implemented") 24 | } 25 | } 26 | 27 | // Can't pass in a simple UIView that doesn't conform to the protocol 28 | //ViewController(header: UIView()) 29 | // error: argument type 'UIView' does not conform to expected type 'UIView & HeaderView' 30 | 31 | // Must pass in an UIView (subclass) that also conforms to the protocol 32 | extension UIImageView: HeaderView {} 33 | 34 | ViewController(header: UIImageView()) // works 35 | 36 | /*: 37 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) 38 | */ 39 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Dictionary and Set enhancements.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## `Dictionary` and `Set` enhancements 5 | 6 | [SE-0165][SE-0165] adds several nice enhancements to `Dictionary` and `Set`. 7 | 8 | [SE-0165]: https://github.com/apple/swift-evolution/blob/master/proposals/0165-dict.md "Swift Evolution Proposal SE-0165: Dictionary and Set Enhancements" 9 | 10 | ### Sequence-based initializer 11 | 12 | Create a dictionary from a sequence of key-value pairs. 13 | */ 14 | let names = ["Cagney", "Lacey", "Bensen"] 15 | let dict = Dictionary(uniqueKeysWithValues: zip(1..., names)) 16 | dict[2] 17 | 18 | /*: 19 | ### Merging initializer and `merge` method 20 | 21 | Specify how duplicate keys should be handled when creating a dictionary from a sequence or merging a sequence into an existing dictionary. 22 | 23 | Merging initializer: 24 | */ 25 | let duplicates = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] 26 | let letters = Dictionary(duplicates, uniquingKeysWith: { (first, _) in first }) 27 | letters 28 | 29 | /*: 30 | `merge` method: 31 | */ 32 | let defaults = ["darkUI": false, "energySaving": false, "smoothScrolling": false] 33 | var options = ["darkUI": true, "energySaving": false] 34 | options.merge(defaults) { (old, _) in old } 35 | options 36 | /*: 37 | ### Subscript with default value 38 | 39 | You can provide a default value that will be returned for missing keys as a subscript argument, making the return type non-optional. 40 | */ 41 | dict[4, default: "(unknown)"] 42 | 43 | /*: 44 | This is especially useful when you want to mutate a value through the subscript: 45 | */ 46 | let source = "how now brown cow" 47 | var frequencies: [Character: Int] = [:] 48 | for c in source { 49 | frequencies[c, default: 0] += 1 50 | } 51 | frequencies 52 | 53 | /*: 54 | ### Dictionary-specific `map` and `filter` 55 | 56 | `filter` returns a `Dictionary` and not an `Array`. Similarly, the new `mapValues` method transforms the values while preserving the dictionary structure. 57 | */ 58 | let filtered = dict.filter { 59 | $0.key % 2 == 0 60 | } 61 | type(of: filtered) 62 | 63 | let mapped = dict.mapValues { value in 64 | value.uppercased() 65 | } 66 | mapped 67 | 68 | /*: 69 | `Set.filter` also returns a `Set` now and not an `Array`. 70 | */ 71 | let set: Set = [1,2,3,4,5] 72 | let filteredSet = set.filter { $0 % 2 == 0 } 73 | type(of: filteredSet) 74 | filteredSet 75 | 76 | /*: 77 | ### Grouping a sequence 78 | 79 | Group a sequence of values into buckets, e.g. partition words in a word list by their initial letter. This is one of my favorites. 80 | */ 81 | let contacts = ["Julia", "Susan", "John", "Alice", "Alex"] 82 | let grouped = Dictionary(grouping: contacts, by: { $0.first! }) 83 | grouped 84 | 85 | /*: 86 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 87 | */ 88 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Encoding and decoding.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## Archival and serialization 5 | 6 | [SE-0166: Swift Archival & Serialization][SE-0166] defines a way for any Swift type (classes, structs, and enums) to describe how to archive and serialize itself. Types can make themselves (un-)archivable by conforming to the `Codable` protocol. 7 | 8 | In many cases adding the `Codable` conformance will be all you have to do for your custom types because the compiler can generate a default implementation if all of the types’ members are themselves `Codable`. You can also override the default behavior if you need to customize how your type encodes itself. There is a lot to this topic — make sure to read the proposal for details. 9 | 10 | [SE-0166]: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md "Swift Evolution Proposal SE-0166: Swift Archival & Serialization" 11 | 12 | */ 13 | // Make a custom type archivable by conforming it (and all its members) to Codable 14 | struct Card: Codable, Equatable { 15 | enum Suit: String, Codable { 16 | case clubs, spades, hearts, diamonds 17 | } 18 | 19 | enum Rank: Int, Codable { 20 | case two = 2, three, four, five, six, seven, eight, nine, ten, jack, queen, king, ace 21 | } 22 | 23 | var suit: Suit 24 | var rank: Rank 25 | 26 | static func ==(lhs: Card, rhs: Card) -> Bool { 27 | return lhs.suit == rhs.suit && lhs.rank == rhs.rank 28 | } 29 | } 30 | 31 | let hand = [Card(suit: .clubs, rank: .ace), Card(suit: .hearts, rank: .queen)] 32 | 33 | /*: 34 | ### Encoding 35 | 36 | Once you have a `Codable` value, you need to pass it to an encoder in order to archive it. 37 | 38 | You can write your own encoders and decoders that make use of the `Codable` infrastructure, but Swift will also come with a built-in set of encoders and decoders for JSON (`JSONEncoder` and `JSONDecoder`) and property lists (`PropertyListEncoder` and `PropertyListDecoder`). These are defined in [SE-0167][SE-0167]. `NSKeyedArchiver` will also support all `Codable` types. 39 | 40 | [SE-0167]: https://github.com/apple/swift-evolution/blob/master/proposals/0167-swift-encoders.md "Swift Evolution Proposal SE-0167: Swift Encoders" 41 | 42 | In its simplest form, encoding is just two lines of code: create an encoder and ask it to encode your value. Most encoders include properties you can set to customize the output. 43 | */ 44 | import Foundation 45 | 46 | var encoder = JSONEncoder() 47 | 48 | // Optional properties offered by JSONEncoder for customizing output 49 | encoder.outputFormatting = [.prettyPrinted] // another option: .sortedKeys 50 | encoder.dataEncodingStrategy 51 | encoder.dateEncodingStrategy 52 | encoder.nonConformingFloatEncodingStrategy 53 | 54 | // Every encoder and decoder has a userInfo property to pass custom configuration down the chain. The supported keys depend on the specific encode/decoder. 55 | encoder.userInfo 56 | 57 | let jsonData = try encoder.encode(hand) 58 | String(data: jsonData, encoding: .utf8) 59 | 60 | /*: 61 | ### Decoding 62 | 63 | Like encoding, decoding consists of two steps: create a decoder and pass it a `Data` value to decode. Notice that you also pass the expected type of the decoded value (`[Card]`, or `Array`, in this example). Don’t forget to handle decoding errors in production code. 64 | */ 65 | let decoder = JSONDecoder() 66 | let decodedHand = try decoder.decode([Card].self, from: jsonData) 67 | type(of: decodedHand) 68 | assert(decodedHand == hand) 69 | 70 | /*: 71 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 72 | */ 73 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Generic subscripts.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## Generic subscripts 5 | 6 | Courtesy of [SE-0148][SE-0148], subscripts can now have generic arguments and/or return types. 7 | 8 | [SE-0148]: https://github.com/apple/swift-evolution/blob/master/proposals/0148-generic-subscripts.md "Swift Evolution Proposal SE-0148: Generic Subscripts" 9 | 10 | The canonical example is a type that represents JSON data: you can define a generic subscript to have the caller’s context define the expected return type. 11 | */ 12 | struct JSON { 13 | fileprivate var storage: [String:Any] 14 | 15 | init(dictionary: [String:Any]) { 16 | self.storage = dictionary 17 | } 18 | 19 | subscript(key: String) -> T? { 20 | return storage[key] as? T 21 | } 22 | } 23 | 24 | let json = JSON(dictionary: [ 25 | "name": "Berlin", 26 | "country": "de", 27 | "population": 3_500_500 28 | ]) 29 | 30 | // No need to use as? Int 31 | let population: Int? = json["population"] 32 | 33 | /*: 34 | Another example: a subscript on `Collection` that takes a generic sequence of indices and returns an array of the values at these indices: 35 | */ 36 | extension Collection { 37 | subscript(indices indices: Indices) -> [Element] where Indices.Element == Index { 38 | var result: [Element] = [] 39 | for index in indices { 40 | result.append(self[index]) 41 | } 42 | return result 43 | } 44 | } 45 | 46 | let words = "Lorem ipsum dolor sit amet".split(separator: " ") 47 | words[indices: [1,2]] 48 | 49 | /*: 50 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 51 | */ 52 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Integer protocols.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## New integer protocols 5 | 6 | [SE-0104][SE-0104] was originally accepted for Swift 3, but didnʼt make it in then. Now a slightly revised version is coming in Swift 4. “This proposal cleans up Swifts integer APIs and makes them more useful for generic programming.” 7 | 8 | [SE-0104]: https://github.com/apple/swift-evolution/blob/master/proposals/0104-improved-integers.md "Swift Evolution Proposal SE-0104: Protocol-oriented integers" 9 | 10 | The protocol hierarchy looks like this: 11 | 12 | ![](integer-protocols.png) 13 | 14 | This might look a little intimidating, but the most important thing to take away from this is that you don’t have to care about all the protocols. Just use the integer types you’re familiar with, such as `Int` and `UInt8`, as you always have and you’ll be absolutely fine. The new protocols only come into play once you want to write generic algorithms that work on multiple numeric types. 15 | 16 | For a library that takes advantage of the new protocols, check out [NumericAnnex](https://github.com/xwu/NumericAnnex) by Xiaodi Wu. 17 | 18 | ### Heterogeneous comparison operators 19 | 20 | You can now compare an `Int` to an `UInt` without explicit conversion, thanks to new generic overloads for these functions in the standard library. 21 | */ 22 | let a: Int = 5 23 | let b: UInt = 5 24 | let c: Int8 = -10 25 | a == b // doesn't compile in Swift 3 26 | a > c // doesn't compile in Swift 3 27 | 28 | /*: 29 | Note that mixed-type _arithmetic_ doesn’t work because Swift doesn’t have a concept of _promoting_ one type to another yet. (The implicit promotion of `T` to `Optional` is an exception, hardcoded into the compiler.) This is a desirable feature for a future version. 30 | */ 31 | //a + b // doesn't compile in either Swift 3 or 4 32 | 33 | /*: 34 | There’s a lot more to the new protocols. Here are a few examples of new properties and methods: 35 | */ 36 | // Get information about the bit pattern 37 | 0xFFFF.words 38 | 0b11001000.trailingZeroBitCount 39 | (0b00001100 as UInt8).leadingZeroBitCount 40 | 0b110001.nonzeroBitCount 41 | 42 | // Artihmetic with overflow reporting 43 | let (partialValue, overflow) = Int32.max.addingReportingOverflow(1) 44 | partialValue 45 | overflow 46 | 47 | // Calculate the full result of a multiplication that would otherwise overflow 48 | let x: UInt8 = 100 49 | let y: UInt8 = 20 50 | let result = x.multipliedFullWidth(by: y) 51 | result.high 52 | result.low 53 | 54 | /*: 55 | I left out some more additions, such as new bit shifting operators and semantics. Read the proposal to get the full picture. 56 | */ 57 | /*: 58 | ### `DoubleWidth` 59 | 60 | The new `DoubleWidth` type is supposed to make it possible to create wider fixed-width integer types from the ones available in the standard library. It's implemented in the Swift master branch, but isn't included in Swift 4.0. Expect it to be part of the next point release. 61 | */ 62 | //var veryBigNumber = DoubleWidth(Int64.max) // error: Use of unresolved identifier 'DoubleWidth' 63 | //veryBigNumber + 1 64 | 65 | /*: 66 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 67 | */ 68 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Integer protocols.xcplaygroundpage/Resources/integer-protocols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ole/whats-new-in-swift-4/3c0d461d41c323c0795cc71f9572e9cf768e22be/Whats-new-in-Swift-4.playground/Pages/Integer protocols.xcplaygroundpage/Resources/integer-protocols.png -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Key paths.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## Smart key paths 5 | 6 | One of the headline features of Swift 4 is the new key paths model described in [SE-0161][SE-0161]. Unlike the string-based key paths in Cocoa, Swift key paths are strongly typed. 7 | 8 | [SE-0161]: https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md "Swift Evolution Proposal SE-0161: Smart KeyPaths: Better Key-Value Coding for Swift" 9 | */ 10 | struct Person { 11 | var name: String 12 | } 13 | 14 | struct Book { 15 | var title: String 16 | var authors: [Person] 17 | var primaryAuthor: Person { 18 | return authors.first! 19 | } 20 | } 21 | 22 | let abelson = Person(name: "Harold Abelson") 23 | let sussman = Person(name: "Gerald Jay Sussman") 24 | let book = Book(title: "Structure and Interpretation of Computer Programs", authors: [abelson, sussman]) 25 | 26 | /*: 27 | Key paths are formed by starting at a root type and drilling down any combination of property and subscript names. 28 | 29 | You write a key path by starting with a backslash: `\Book.title`. Every type automatically gets a `[keyPath: …]` subscript to get or set the value at the specified key path. 30 | */ 31 | book[keyPath: \Book.title] 32 | // Key paths can drill down multiple levels 33 | // They also work for computed properties 34 | book[keyPath: \Book.primaryAuthor.name] 35 | 36 | /*: 37 | Key paths are objects that can be stored and manipulated. For example, you can append additional segments to a key path to drill down further. 38 | */ 39 | let authorKeyPath = \Book.primaryAuthor 40 | type(of: authorKeyPath) 41 | // You can omit the type name if the compiler can infer it 42 | let nameKeyPath = authorKeyPath.appending(path: \.name) 43 | book[keyPath: nameKeyPath] 44 | 45 | /*: 46 | ### Key paths for subscripts 47 | 48 | Part of the key path proposal was that key paths can include subscript segments. This would make them very convenient to drill down into collections. The implementation for subscripts didn't make the cut for Swift 4.0, but it's now availble in Swift 4.0.3 (Xcode 9.2). 49 | */ 50 | // Now possible in Swift 4.0.3 51 | book[keyPath: \Book.authors[0].name] 52 | 53 | /*: 54 | ### Type-safe KVO with key paths 55 | 56 | The key-value observing API in Foundation has been revamped to take full advantage of the new type-safe key paths. It is much easier to use than the old API. 57 | 58 | Notice that KVO depends on the Objective-C runtime. It only works in subclasses of `NSObject`, and any observable property must be declared with `@objc dynamic`. 59 | */ 60 | import Foundation 61 | 62 | class Child: NSObject { 63 | let name: String 64 | // KVO-enabled properties must be @objc dynamic 65 | @objc dynamic var age: Int 66 | 67 | init(name: String, age: Int) { 68 | self.name = name 69 | self.age = age 70 | super.init() 71 | } 72 | 73 | func celebrateBirthday() { 74 | age += 1 75 | } 76 | } 77 | 78 | // Set up KVO 79 | let mia = Child(name: "Mia", age: 5) 80 | let observation = mia.observe(\.age, options: [.initial, .old]) { (child, change) in 81 | if let oldValue = change.oldValue { 82 | print("\(child.name)’s age changed from \(oldValue) to \(child.age)") 83 | } else { 84 | print("\(child.name)’s age is now \(child.age)") 85 | } 86 | } 87 | 88 | // Trigger KVO (see output in the console) 89 | mia.celebrateBirthday() 90 | 91 | // Deiniting or invalidating the observation token ends the observation 92 | observation.invalidate() 93 | 94 | // This doesn't trigger the KVO handler anymore 95 | mia.celebrateBirthday() 96 | 97 | /*: 98 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 99 | */ 100 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Limiting @objc inference.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## Limiting `@objc` inference 5 | 6 | In many places where Swift 3 automatically inferred a declaration to be `@objc` (and thus visible from Objective-C), Swift 4 doesn’t do this anymore ([SE-0160][SE-0160]). Most importantly, `NSObject` subclasses no longer infer `@objc` for their members. 7 | 8 | [SE-0160]: https://github.com/apple/swift-evolution/blob/master/proposals/0160-objc-inference.md "Swift Evolution Proposal SE-0160: Limiting @objc inference" 9 | */ 10 | import Foundation 11 | 12 | class MyClass : NSObject { 13 | func foo() { } // not exposed to Objective-C in Swift 4 14 | @objc func bar() { } // explicitly exposed to Objective-C 15 | } 16 | 17 | /*: 18 | ### `@objcMembers` shorthand 19 | 20 | If want to expose all or most members of a class to Objective-C, you can use the `@objcMembers` attribute on the class declaration to save you some typing: 21 | */ 22 | @objcMembers 23 | class MyClass2 : NSObject { 24 | func foo() { } // implicitly @objc 25 | func bar() -> (Int, Int) { // not @objc, because tuples aren't representable in Objective-C (unchanged from Swift 3) 26 | return (0, 0) 27 | } 28 | } 29 | 30 | /*: 31 | Use `@nonobjc` on an extension to disable `@objc` inference (through `@objcMembers`) for all declarations in the extension. 32 | */ 33 | @nonobjc extension MyClass2 { 34 | func wobble() { } // not @objc, despite @objcMembers 35 | } 36 | 37 | /*: 38 | ### `dynamic` doesn’t imply `@objc` 39 | 40 | Also note that `dynamic` doesn’t imply `@objc` anymore. If you want to use features that depend on the dynamic dispatch of the Objective-C runtime (such as KVO), you may need to add `@objc dynamic` to a declaration. See the [Key paths page](Key%20paths) for a KVO example. 41 | 42 | Since `dynamic` is currently implemented in terms of the Objective-C runtime, this means all current usages of `dynamic` also have to be `@objc`. While this sounds redundant at the moment, it’s an important step on the way to eventually make `dynamic` a pure Swift feature in a future version of the language. 43 | */ 44 | /*: 45 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 46 | */ 47 | 48 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/NSNumber bridging.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## `NSNumber` bridging 5 | 6 | [SE-0170][SE-0170] fixes some dangerous behavior when bridging between native Swift number types and `NSNumber`. 7 | 8 | [SE-0170]: https://github.com/apple/swift-evolution/blob/master/proposals/0170-nsnumber_bridge.md "Swift Evolution Proposal SE-0170: NSNumber bridging and Numeric types" 9 | */ 10 | import Foundation 11 | 12 | let n = NSNumber(value: UInt32(543)) 13 | let v = n as? Int8 // nil in Swift 4. This would be 31 in Swift 3 (try it!). 14 | 15 | /*: 16 | ### Floating-point `NSNumber` bridging 17 | 18 | Initially, in Xcode 9 beta 1, the same rules applied to floating-point bridging, which meant for example that `NSNumber(value: 0.1) as? Float` would return `nil` because the double-precision representation of `0.1` is not exactly representable as a single-precision float. [This change was reverted to the Swift 3 behavior](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170612/037499.html). 19 | */ 20 | NSNumber(value: 0.1) as? Double 21 | NSNumber(value: 0.1) as? Float // Returned nil in Xcode 9 beta 1, now changed back to Swift 3 behavior 22 | 23 | /*: 24 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 25 | */ 26 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/One-sided ranges.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## One-sided ranges 5 | 6 | [SE-0172][SE-0172] introduces a new `RangeExpression` protocol and a set of prefix/postfix operators to form one-sided ranges, i.e. ranges where either the lower or upper bound is unspecified. 7 | 8 | [SE-0172]: https://github.com/apple/swift-evolution/blob/master/proposals/0172-one-sided-ranges.md "Swift Evolution Proposal SE-0172: One-sided Ranges" 9 | 10 | ### Infinite Sequences 11 | 12 | You can use a one-sided range to construct an infinite sequence, e.g. as a more flexible replacement for `enumerated()` when you don’t want the numbering to start at zero: 13 | */ 14 | let letters = ["a","b","c","d"] 15 | let numberedLetters = zip(1..., letters) 16 | Array(numberedLetters) 17 | /*: 18 | ### Collection subscripts 19 | 20 | When you use a one-sided range for subscripting into a `Collection`, the collection’s `startIndex` or `endIndex` “fills in” the missing lower or upper bound, respectively. 21 | */ 22 | let numbers = [1,2,3,4,5,6,7,8,9,10] 23 | numbers[5...] // instead of numbers[5.. Debug Area > Activate Console_) to see the `print` output. 38 | 39 | ### String is a `Collection` again 40 | 41 | [SE-0163][SE-0163] is the first part of the revised string model for Swift 4. The biggest change is that `String` is a `Collection` again (as it used to be in Swift 1.x), i.e. the functionality of `String.CharacterView` has been folded into the parent type. (The other views, `UnicodeScalarView`, `UTF8View`, and `UTF16View`, still exist.) 42 | 43 | Another string-related change is a redesign of the `String.Index` type ([SE-0180][SE-0180]) 44 | 45 | [SE-0163]: https://github.com/apple/swift-evolution/blob/master/proposals/0163-string-revision-1.md "Swift Evolution Proposal SE-0163: String Revision: Collection Conformance, C Interop, Transcoding" 46 | [SE-0180]: https://github.com/apple/swift-evolution/blob/master/proposals/0180-string-index-overhaul.md "Swift Evolution Proposal SE-0180: String Index Overhaul" 47 | */ 48 | let greeting = "Hello, 😜!" 49 | // No need to drill down to .characters 50 | greeting.count 51 | for char in greeting { 52 | print(char) 53 | } 54 | print("---") 55 | 56 | /*: 57 | ### `Substring` is the new type for string slices 58 | 59 | String slices are now instances of type `Substring`. Both `String` and `Substring` conform to `StringProtocol`. Almost the entire string API will live in `StringProtocol` so that `String` and `Substring` behave largely the same. 60 | */ 61 | let comma = greeting.index(of: ",")! 62 | let substring = greeting[..` and `NSRange` 103 | 104 | Foundation comes with new initializers on `NSRange` and `Range` to convert between the two, removing the need to manually compute UTF-16 offsets. This makes it easier to use APIs that still work on `NSRange`s, such as `NSRegularExpression` and `NSAttributedString`. 105 | */ 106 | // Given a String range 107 | let string = "Hello 👩🏽‍🌾👨🏼‍🚒💃🏾" 108 | let index = string.index(of: Character("👩🏽‍🌾"))! 109 | let range = index... 110 | 111 | // Convert the String range to an NSRange 112 | import Foundation 113 | 114 | let nsRange = NSRange(range, in: string) 115 | nsRange.length // length in UTF-16 code units 116 | string[range].count // length in Characters 117 | assert(nsRange.length == string[range].utf16.count) 118 | 119 | // Use the NSRange to format an attributed string 120 | import UIKit 121 | 122 | let formatted = NSMutableAttributedString(string: string, attributes: [.font: UIFont.systemFont(ofSize: 14)]) 123 | formatted.addAttribute(.font, value: UIFont.systemFont(ofSize: 48), range: nsRange) 124 | 125 | // NSAttributedString APIs return NSRange 126 | let lastCharacterIndex = string.index(before: string.endIndex) 127 | let lastCharacterNSRange = NSRange(lastCharacterIndex..., in: string) 128 | var attributesNSRange = NSRange() 129 | _ = formatted.attributes(at: lastCharacterNSRange.location, longestEffectiveRange: &attributesNSRange, in: nsRange) 130 | attributesNSRange 131 | 132 | // Convert the NSRange back to Range to use it with String 133 | let attributesRange = Range(attributesNSRange, in: string)! 134 | string[attributesRange] 135 | 136 | /*: 137 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 138 | */ 139 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # What’s new in Swift 4 3 | 4 | By [Ole Begemann][Ole Begemann] • May–September 2017 5 | 6 | Latest version tested: **Xcode 9.0 from September 19, 2017** 7 | 8 | [Ole Begemann]: https://oleb.net 9 | 10 | ## Contents 11 | 12 | 1. Instructions (see below) 13 | 1. [One-sided ranges](One-sided%20ranges) 14 | 1. [Strings](Strings) 15 | 1. [Private declarations visible in same-file extensions](private%20in%20same-file%20extensions) 16 | 1. [Key paths](Key%20paths) 17 | 1. [Encoding and decoding](Encoding%20and%20decoding) 18 | 1. [Associated type constraints](Associated%20type%20constraints) 19 | 1. [`Dictionary` and `Set` enhancements](Dictionary%20and%20Set%20enhancements) 20 | 1. [`MutableCollection.swapAt` method](swapAt) 21 | 1. [`reduce` with `inout`](reduce%20with%20inout) 22 | 1. [Generic subscripts](Generic%20subscripts) 23 | 1. [New integer protocols](Integer%20protocols) 24 | 1. [`NSNumber` bridging](NSNumber%20bridging) 25 | 1. [`Limiting @objc inference`](Limiting%20@objc%20inference) 26 | 1. [Composing classes and protocols](Composing%20classes%20and%20protocols) 27 | 28 | ## Instructions 29 | 30 | This playground requires Swift 4. [Xcode](https://developer.apple.com/xcode/) 9 includes Swift 4 by default. 31 | 32 | You can also run it in Xcode 8.3, but you need to install a Swift 4 toolchain (don’t worry, it’s easy): 33 | 34 | 1. Download [the latest Swift 4.0 snapshot from swift.org](https://swift.org/download/#snapshots). 35 | 1. Run the installer to install the snapshot. 36 | 1. In Xcode, go to _Xcode > Toolchains > Manage Toolchains…_ and select the snapshot: 37 | 38 | ![Screenshot of Swift toolchain selection in Xcode 8.3 preferences](xcode-8-3-toolchain-dialog.png) 39 | 40 | It might be a good idea to quit and relaunch Xcode after switching snapshots. I occasionally had problems with syntax highlighting and code completion that a relaunch fixed. 41 | 42 | If you want to switch between multiple Swift versions on the command line (e.g. to play with Swift 4 in the REPL or to build Swift packages with Swift 4, I recommend Kyle Fuller’s excellent [swiftenv](https://swiftenv.fuller.li/). 43 | 44 | [Next page](@next) 45 | */ 46 | 47 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/Table of contents.xcplaygroundpage/Resources/xcode-8-3-toolchain-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ole/whats-new-in-swift-4/3c0d461d41c323c0795cc71f9572e9cf768e22be/Whats-new-in-Swift-4.playground/Pages/Table of contents.xcplaygroundpage/Resources/xcode-8-3-toolchain-dialog.png -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/private in same-file extensions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## Private declarations visible in same-file extensions 5 | 6 | [SE-0169][SE-0169] changes the access control rules such that `private` declarations are also visible inside extensions of the parent type _in the same file_. This makes it possible to split your type definition into multiple extensions and still use `private` for most "private" members, reducing the need for the unloved `fileprivate` keyword. 7 | 8 | [SE-0169]: https://github.com/apple/swift-evolution/blob/master/proposals/0169-improve-interaction-between-private-declarations-and-extensions.md "Swift Evolution Proposal SE-0169: Improve Interaction Between private Declarations and Extensions" 9 | */ 10 | struct SortedArray { 11 | private var storage: [Element] = [] 12 | init(unsorted: [Element]) { 13 | storage = unsorted.sorted() 14 | } 15 | } 16 | 17 | extension SortedArray { 18 | mutating func insert(_ element: Element) { 19 | // storage is visible here 20 | storage.append(element) 21 | storage.sort() 22 | } 23 | } 24 | 25 | let array = SortedArray(unsorted: [3,1,2]) 26 | 27 | // storage is _not_ visible here. It would be if it were fileprivate. 28 | //array.storage // error: 'storage' is inaccessible due to 'private' protection level 29 | 30 | /*: 31 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 32 | */ 33 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/reduce with inout.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## `reduce` with `inout` 5 | 6 | [SE-0171][SE-0171] adds a variant of `reduce` in which the partial result is passed `inout` to the combine function. This can be a significant performance boost for algorithms that use `reduce` to incrementally build a sequence by eliminating copies of the intermediate results. 7 | 8 | [SE-0171]: https://github.com/apple/swift-evolution/blob/master/proposals/0171-reduce-with-inout.md "Swift Evolution Proposal SE-0171: Reduce with `inout`" 9 | */ 10 | extension Sequence where Element: Equatable { 11 | func removingConsecutiveDuplicates() -> [Element] { 12 | return reduce(into: []) { (result: inout [Element], element) in 13 | if result.last != element { 14 | result.append(element) 15 | } 16 | } 17 | } 18 | } 19 | 20 | [1,1,1,2,3,3,4].removingConsecutiveDuplicates() 21 | 22 | /*: 23 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 24 | */ 25 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/Pages/swapAt.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 3 | 4 | ## `MutableCollection.swapAt` method 5 | 6 | [SE-0173][SE-0173] introduces a new method for swapping two elements in a collection. Unlike the existing `swap(_:_:)` function, `swapAt(_:_:)` takes the indices of the elements to be swapped, not the elements themselves (via `inout` arguments). 7 | 8 | The reason for this addition is that `swap` with two `inout` arguments is incompatible with the new exclusive memory access rules proposed in [SE-0176][SE-0176]. The existing `swap(_:_:)` function will no longer work for swapping two elements of the same collection. 9 | 10 | [SE-0173]: https://github.com/apple/swift-evolution/blob/master/proposals/0173-swap-indices.md "Swift Evolution Proposal SE-0173: Add `MutableCollection.swapAt(_:_:)`" 11 | [SE-0176]: https://github.com/apple/swift-evolution/blob/master/proposals/0176-enforce-exclusive-access-to-memory.md "Swift Evolution Proposal SE-0176: Enforce Exclusive Access to Memory" 12 | */ 13 | var numbers = [1,2,3,4,5] 14 | 15 | // Illegal in Swift 4: 16 | // error: simultaneous accesses to var 'numbers', but modification requires exclusive access; consider copying to a local variable 17 | //swap(&numbers[3], &numbers[4]) 18 | 19 | // This is the new way to do this: 20 | numbers.swapAt(0,1) 21 | numbers 22 | 23 | /*: 24 | [Table of contents](Table%20of%20contents) • [Previous page](@previous) • [Next page](@next) 25 | */ 26 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Whats-new-in-Swift-4.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /playground-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ole/whats-new-in-swift-4/3c0d461d41c323c0795cc71f9572e9cf768e22be/playground-screenshot.png --------------------------------------------------------------------------------