├── .gitignore ├── README.md ├── ch02-enums └── Enums.playground │ ├── Pages │ ├── Algebraic data types.xcplaygroundpage │ │ └── Contents.swift │ ├── An invalid Message.xcplaygroundpage │ │ └── Contents.swift │ ├── Answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Creating a string type enum.xcplaygroundpage │ │ └── Contents.swift │ ├── Enums instead of subclassing Workout type.xcplaygroundpage │ │ └── Contents.swift │ ├── Enums instead of subclassing.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises.xcplaygroundpage │ │ └── Contents.swift │ ├── Explicit matching on raw value enum.xcplaygroundpage │ │ └── Contents.swift │ ├── Matching on strings.xcplaygroundpage │ │ └── Contents.swift │ ├── Message as a struct.xcplaygroundpage │ │ └── Contents.swift │ ├── Message as an enum.xcplaygroundpage │ │ └── Contents.swift │ ├── Polymorphism.xcplaygroundpage │ │ └── Contents.swift │ ├── Raw value enum with single value specified.xcplaygroundpage │ │ └── Contents.swift │ ├── Raw value enum with values omitted.xcplaygroundpage │ │ └── Contents.swift │ ├── Raw value enum.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch03-properties └── Properties.playground │ ├── Pages │ ├── Answers.xcplaygroundpage │ │ ├── Contents.swift │ │ └── Resources │ │ │ ├── File │ │ │ └── songs.plist │ ├── Exercises.xcplaygroundpage │ │ ├── Contents.swift │ │ └── Resources │ │ │ └── songs.plist │ ├── LearningPlan lazy loading after copying.xcplaygroundpage │ │ └── Contents.swift │ ├── LearningPlan lazy loading before copying.xcplaygroundpage │ │ └── Contents.swift │ ├── LearningPlan with a breaking lazy property.xcplaygroundpage │ │ └── Contents.swift │ ├── LearningPlan with a lazy private setter.xcplaygroundpage │ │ └── Contents.swift │ ├── LearningPlan with a lazy property.xcplaygroundpage │ │ └── Contents.swift │ ├── LearningPlan.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Property observer initializer.xcplaygroundpage │ │ └── Contents.swift │ ├── Property observer.xcplaygroundpage │ │ └── Contents.swift │ ├── Run with computed properties setter.xcplaygroundpage │ │ └── Contents.swift │ ├── Run with computed properties.xcplaygroundpage │ │ └── Contents.swift │ ├── Run with functions.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch04-optionals └── Optionals.playground │ ├── Pages │ ├── Answer optional booleans.xcplaygroundpage │ │ └── Contents.swift │ ├── Clean optional unwrapping.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercise Implicitly Unwrapped Optionals.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercise optional booleans.xcplaygroundpage │ │ └── Contents.swift │ ├── Fallback values.xcplaygroundpage │ │ └── Contents.swift │ ├── First answers.xcplaygroundpage │ │ └── Contents.swift │ ├── First exercises.xcplaygroundpage │ │ └── Contents.swift │ ├── Granular unwrapping.xcplaygroundpage │ │ └── Contents.swift │ ├── Implicitly Unwrapped Optionals initialization.xcplaygroundpage │ │ └── Contents.swift │ ├── Optional boolean enum.xcplaygroundpage │ │ └── Contents.swift │ ├── Optional boolean nil coalescing.xcplaygroundpage │ │ ├── Contents.swift │ │ └── Resources │ │ │ └── hello.txt │ ├── Optional chaining.xcplaygroundpage │ │ ├── Contents.swift │ │ └── Resources │ │ │ └── mayo.jpg │ ├── Optional enums.xcplaygroundpage │ │ └── Contents.swift │ ├── Prohibited optionals.xcplaygroundpage │ │ └── Contents.swift │ ├── Returning an optional string.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch05-initializers ├── 1. Struct initializer rules.playground │ ├── Pages │ │ ├── Answers.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Custom initializer.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Exercises.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Memberwise and custom initializer.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Memberwise initializer.xcplaygroundpage │ │ │ └── Contents.swift │ │ └── Table of contents.xcplaygroundpage │ │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── 2. Class initializers.playground │ ├── Pages │ │ ├── Answer.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Boardgame.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Exercise.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Mutability Land with broken initializer.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Mutability Land with designated initializer.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Mutability Land with designated override.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Mutability Land.xcplaygroundpage │ │ │ └── Contents.swift │ │ └── Table of contents.xcplaygroundpage │ │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── 3. Minimizing initializers.playground │ ├── Pages │ │ ├── Answer.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Convenience initializer override.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Convenience override subsub classes.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Exercise.xcplaygroundpage │ │ │ └── Contents.swift │ │ └── Table of contents.xcplaygroundpage │ │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── 4. Required.playground │ ├── Pages │ ├── Answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises.xcplaygroundpage │ │ └── Contents.swift │ ├── Factory method.xcplaygroundpage │ │ └── Contents.swift │ ├── Final class.xcplaygroundpage │ │ └── Contents.swift │ ├── Protocol.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch06-error-handling └── ErrorHandling.playground │ ├── Pages │ ├── Capturing validity within a type.xcplaygroundpage │ │ └── Contents.swift │ ├── Errors.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises.xcplaygroundpage │ │ └── Contents.swift │ ├── Keeping predictable state.xcplaygroundpage │ │ └── Contents.swift │ ├── Optionals.xcplaygroundpage │ │ └── Contents.swift │ ├── Propagation.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch07-generics └── Generics.playground │ ├── Pages │ ├── Conforming to Comparable.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises and answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Generic constrained function.xcplaygroundpage │ │ └── Contents.swift │ ├── Generic function.xcplaygroundpage │ │ └── Contents.swift │ ├── Invariance.xcplaygroundpage │ │ └── Contents.swift │ ├── Multiple constraints.xcplaygroundpage │ │ └── Contents.swift │ ├── Pair with conditional conformance.xcplaygroundpage │ │ └── Contents.swift │ ├── Pair with manual Hashable implementation.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch08-protocols └── Putting the pro in protocol-oriented programming.playground │ ├── Pages │ ├── A portfolio without generics.xcplaygroundpage │ │ └── Contents.swift │ ├── Answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Building a crypto portfolio.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises.xcplaygroundpage │ │ └── Contents.swift │ ├── Passing protocols with associated types.xcplaygroundpage │ │ └── Contents.swift │ ├── Runtime vs compiletime.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch09-iterators-sequences-collections └── iterators_sequences_collections.playground │ ├── Pages │ ├── Exercise and Answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Exploring collection types.xcplaygroundpage │ │ └── Contents.swift │ ├── Implementing Collection.xcplaygroundpage │ │ └── Contents.swift │ ├── IteratorProtocol.xcplaygroundpage │ │ └── Contents.swift │ ├── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── The Bag datatype.xcplaygroundpage │ │ └── Contents.swift │ └── Useful methods on Sequence.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch10-mapflatmap └── Map flatMap compactMap.playground │ ├── Pages │ ├── Exercises and answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Map is an abstraction.xcplaygroundpage │ │ └── Contents.swift │ ├── Mapping over collections.xcplaygroundpage │ │ └── Contents.swift │ ├── Mapping over optionals.xcplaygroundpage │ │ └── Contents.swift │ ├── Mapping over sequences.xcplaygroundpage │ │ └── Contents.swift │ ├── Optional flatMap.xcplaygroundpage │ │ └── Contents.swift │ ├── Pyramid of doom.xcplaygroundpage │ │ └── Contents.swift │ ├── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── compactMap.xcplaygroundpage │ │ └── Contents.swift │ └── flatMapping over collections.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch11-async-error-handling └── ErrorHandlingResult.playground │ ├── Pages │ ├── Error recovery with Result.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercise 4.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises 1 2 and 3.xcplaygroundpage │ │ └── Contents.swift │ ├── Mixing Result with throwing functions.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Never.xcplaygroundpage │ │ └── Contents.swift │ ├── Returning multiple error types.xcplaygroundpage │ │ └── Contents.swift │ ├── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── iTunes api add search.xcplaygroundpage │ │ └── Contents.swift │ ├── iTunes api cocoa touch style.xcplaygroundpage │ │ └── Contents.swift │ ├── iTunes api convert to Result.xcplaygroundpage │ │ └── Contents.swift │ ├── iTunes api flatMap.xcplaygroundpage │ │ └── Contents.swift │ ├── iTunes api mapping.xcplaygroundpage │ │ └── Contents.swift │ └── iTunes api with result.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch12-extensions └── Extensions.playground │ ├── Pages │ ├── A mailer using protocol composition.xcplaygroundpage │ │ └── Contents.swift │ ├── A mailer using protocol inheritance.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises and Answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Extending Sequence.xcplaygroundpage │ │ └── Contents.swift │ ├── Extending in two directions.xcplaygroundpage │ │ └── Contents.swift │ ├── Extending with associated types.xcplaygroundpage │ │ └── Contents.swift │ ├── Extending with concrete constraints.xcplaygroundpage │ │ └── Contents.swift │ ├── Horizontal modeling.xcplaygroundpage │ │ └── Contents.swift │ ├── Protocol overrides.xcplaygroundpage │ │ └── Contents.swift │ └── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ch13-patterns-with-protocols └── Patterns.playground │ ├── Pages │ ├── Answers.xcplaygroundpage │ │ └── Contents.swift │ ├── Conditional Conformance CachedValue.xcplaygroundpage │ │ └── Contents.swift │ ├── Conditional Conformance part 2.xcplaygroundpage │ │ └── Contents.swift │ ├── Conditional Conformance.xcplaygroundpage │ │ └── Contents.swift │ ├── Exercises.xcplaygroundpage │ │ └── Contents.swift │ ├── Mocking with protocols.xcplaygroundpage │ │ └── Contents.swift │ ├── Poker with an enum.xcplaygroundpage │ │ └── Contents.swift │ ├── Poker with type erasure.xcplaygroundpage │ │ └── Contents.swift │ ├── Poker.xcplaygroundpage │ │ └── Contents.swift │ ├── Table of contents.xcplaygroundpage │ │ └── Contents.swift │ └── Validator as a generic struct.xcplaygroundpage │ │ └── Contents.swift │ ├── contents.xcplayground │ └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── swiftindepth.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.xcuserstate 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift in Depth 2 | 3 | Here you can find the source and exercises of the book Swift in Depth, published by Manning. 4 | 5 | ![Book cover](swiftindepth.png) 6 | 7 | [Link to the book](https://www.manning.com/books/swift-in-depth) 8 | 9 | Each chapter has a playground which can be opened with Xcode. You may find the source and exercises here. 10 | 11 | You need Xcode 10 and Swift 4.2. 12 | 13 | If you don't see the pages, press cmd+1 to open Xcode's file inspector. 14 | 15 | For a better view in Xcode, make sure rendered markedup is enabled. You can do this by going to Editor > Show Rendered Markup. 16 | 17 | Enjoy and good luck with the exercises! 18 | 19 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Algebraic data types.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Algebraic data types 4 | 5 | import Foundation 6 | 7 | enum Day { 8 | case sunday 9 | case monday 10 | case tuesday 11 | case wednesday 12 | case thursday 13 | case friday 14 | case saturday 15 | } 16 | 17 | enum Age { 18 | case known(UInt8) 19 | case unknown 20 | } 21 | 22 | struct BooleanContainer { 23 | let first: Bool 24 | let second: Bool 25 | } 26 | 27 | BooleanContainer(first: true, second: true) 28 | BooleanContainer(first: true, second: false) 29 | BooleanContainer(first: false, second: true) 30 | BooleanContainer(first: false, second: false) 31 | 32 | enum PaymentType { 33 | case invoice 34 | case creditcard 35 | case cash 36 | } 37 | struct PaymentStatus { 38 | let paymentDate: Date? 39 | let isRecurring: Bool 40 | let paymentType: PaymentType 41 | } 42 | // 43 | //// Alternatively, the enum and struct can be turned into a single struct. 44 | //enum PaymentStatus { 45 | // case invoice(paymentDate: Date?, isRecurring: Bool) 46 | // case creditcard(paymentDate: Date?, isRecurring: Bool) 47 | // case cash(paymentDate: Date?, isRecurring: Bool) 48 | //} 49 | 50 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 51 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/An invalid Message.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # An invalid Message 4 | 5 | import Foundation 6 | 7 | struct Message { 8 | let userId: String 9 | let contents: String? 10 | let date: Date 11 | 12 | let hasJoined: Bool 13 | let hasLeft: Bool 14 | 15 | let isBeingDrafted: Bool 16 | let isSendingBalloons: Bool 17 | } 18 | 19 | //: This message has invalid state. Can't have both "hasJoined" and "hasLeft". 20 | 21 | let brokenMessage = Message(userId: "1", 22 | contents: "Hi there", // We have text to show 23 | date: Date(), 24 | hasJoined: true, // But this message also signals a joining state 25 | hasLeft: true, // ... and a leaving state 26 | isBeingDrafted: false, 27 | isSendingBalloons: false) 28 | 29 | // chatroom.sendMessage(brokenMessage) 30 | 31 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 32 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Answers.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 2 | 3 | //: # Answers 4 | 5 | //: 1. A superclass prevents duplication, no need to declare the same property twice. With subclassing you can also override existing functionality. 6 | //: 2. No need to refactor anything if you add another type. Whereas with subclassing you risk refactoring a superclass and its existing subclasses. Second, you are not forced to use classes. 7 | //: 3. What are the number possible variations of Bagel? 8 | // Twelve. There are 3 for Topping times 4 for BagelType. 9 | 10 | // 4. Turn Bagel into a struct 11 | 12 | // There are two ways since Bagel contains two enums. So you can pick which enum to store the data in. 13 | 14 | enum Topping { 15 | case creamCheese 16 | case peanutButter 17 | case jam 18 | } 19 | 20 | 21 | 22 | enum Bagel { 23 | case cinnamonRaisin(Topping) 24 | case glutenFree(Topping) 25 | case oatMeal(Topping) 26 | case blueberry(Topping) 27 | } 28 | 29 | //: Alternatively 30 | 31 | //enum Bagel { 32 | // case creamCheese(BagelType) 33 | // case peanutButter(BagelType) 34 | // case jam(BagelType) 35 | //} 36 | 37 | 38 | //: 5. 39 | enum AgeRange { 40 | case baby 41 | case toddler 42 | case preschooler 43 | case gradeschooler 44 | case teenager 45 | } 46 | 47 | struct Puzzle { 48 | let ageRange: AgeRange 49 | let numberOfPieces: Int 50 | } 51 | 52 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 53 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Creating a string type enum.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Creating a string type enum 4 | 5 | enum ImageType: String { 6 | case jpg 7 | case bmp 8 | case gif 9 | 10 | init?(rawValue: String) { 11 | switch rawValue.lowercased() { 12 | case "jpg", "jpeg": self = .jpg 13 | case "bmp", "bitmap": self = .bmp 14 | case "gif", "gifv": self = .gif 15 | default: return nil 16 | } 17 | } 18 | 19 | } 20 | 21 | func iconName(for fileExtension: String) -> String { 22 | guard let imageType = ImageType(rawValue: fileExtension) else { 23 | return "assetIconUnknown" 24 | } 25 | switch imageType { 26 | case .jpg: return "assetIconJpeg" 27 | case .bmp: return "assetIconBitmap" 28 | case .gif: return "assetIconGif" 29 | } 30 | } 31 | 32 | iconName(for: "jpg") // "Received jpg" 33 | iconName(for: "jpeg") // "Received jpg" 34 | iconName(for: "JPG") // "Received a jpg" 35 | iconName(for: "JPEG") // "Received a jpg" 36 | iconName(for: "gif") // "Received a gif" 37 | 38 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 39 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Enums instead of subclassing Workout type.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation // We need foundation for the Date type. 3 | 4 | struct Run { 5 | let id: String 6 | let startTime: Date 7 | let endTime: Date 8 | let distance: Float 9 | let onRunningTrack: Bool 10 | } 11 | 12 | struct Cycle { 13 | 14 | enum CycleType { 15 | case regular 16 | case mountainBike 17 | case racetrack 18 | } 19 | 20 | let id: String 21 | let startTime: Date 22 | let endTime: Date 23 | let distance: Float 24 | let incline: Int 25 | let type: CycleType 26 | } 27 | 28 | struct Pushups { 29 | let repetitions: [Int] 30 | let date: Date 31 | } 32 | 33 | let run = Run(id: "3", startTime: Date(), endTime: Date(timeIntervalSinceNow: 3600), distance: 300, onRunningTrack: false) 34 | 35 | let cycle = Cycle(id: "4", startTime: Date(), endTime: Date(timeIntervalSinceNow: 3600), distance: 400, incline: 20, type: .mountainBike) 36 | 37 | let pushups = Pushups(repetitions: [22,20,10], date: Date()) 38 | 39 | enum Workout { 40 | case run(Run) 41 | case cycle(Cycle) 42 | case pushups(Pushups) 43 | } 44 | 45 | let workout = Workout.pushups(pushups) 46 | 47 | 48 | switch workout { 49 | case .run(let run): 50 | print("Run: \(run)") 51 | case .cycle(let cycle): 52 | print("Cycle: \(cycle)") 53 | case .pushups(let pushups): 54 | print("Pushups: \(pushups)") 55 | } 56 | 57 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 58 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Enums instead of subclassing.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation // We need foundation for the Date type. 3 | 4 | struct Run { 5 | let id: String 6 | let startTime: Date 7 | let endTime: Date 8 | let distance: Float 9 | let onRunningTrack: Bool 10 | } 11 | 12 | struct Cycle { 13 | 14 | enum CycleType { 15 | case regular 16 | case mountainBike 17 | case racetrack 18 | } 19 | 20 | let id: String 21 | let startTime: Date 22 | let endTime: Date 23 | let distance: Float 24 | let incline: Int 25 | let type: CycleType 26 | } 27 | 28 | let run = Run(id: "3", startTime: Date(), endTime: Date(timeIntervalSinceNow: 3600), distance: 300, onRunningTrack: false) 29 | 30 | let cycle = Cycle(id: "4", startTime: Date(), endTime: Date(timeIntervalSinceNow: 3600), distance: 400, incline: 20, type: .mountainBike) 31 | 32 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 33 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Exercises.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Exercises 6 | 7 | //: 1. Can you name two benefits of using subclassing over enums with associated types? 8 | //: 2. Can you name two benefits of using enums with associated types over subclassing? 9 | //: 3. What is the number of possible variations of Bagel? 10 | 11 | enum Topping { 12 | case creamCheese 13 | case peanutButter 14 | case jam 15 | } 16 | 17 | enum BagelType { 18 | case cinnamonRaisin 19 | case glutenFree 20 | case oatMeal 21 | case blueberry 22 | } 23 | 24 | struct Bagel { 25 | let topping: Topping 26 | let type: BagelType 27 | } 28 | 29 | //: 4. Turn Bagel into an enum while keeping the same amount of possible variations. 30 | 31 | //: 5. Given this enum representing a Puzzle game for a certain age range (baby, toddler etc) and containing a number of puzzle pieces. 32 | //: How would could this enum be represented as a struct instead? 33 | enum Puzzle { 34 | case baby(numberOfPieces: Int) 35 | case toddler(numberOfPieces: Int) 36 | case preschooler(numberOfPieces: Int) 37 | case gradeschooler(numberOfPieces: Int) 38 | case teenager(numberOfPieces: Int) 39 | } 40 | 41 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 42 | 43 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Explicit matching on raw value enum.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Explicit matching on an enum 4 | //: We ignore any raw value that an enum may have. 5 | 6 | enum Currency: String { 7 | case euro = "eur" 8 | case usd 9 | case gbp 10 | } 11 | 12 | let currency = Currency.euro 13 | print(currency.rawValue) // "eur" 14 | 15 | let parameters: [String: String] 16 | switch currency { 17 | case .euro: parameters = ["filter": "euro"] 18 | case .usd: parameters = ["filter": "usd"] 19 | case .gbp: parameters = ["filter": "gbp"] 20 | } 21 | 22 | // We're back to using "euro" again 23 | print(parameters) // ["filter": "euro"] 24 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 25 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Matching on strings.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Matching on strings 4 | 5 | func iconName(for fileExtension: String) -> String { 6 | switch fileExtension { 7 | case "jpg": return "assetIconJpeg" 8 | case "bmp": return "assetIconBitmap" 9 | case "gif": return "assetIconGif" 10 | default: return "assetIconUnknown" 11 | } 12 | } 13 | 14 | iconName(for: "jpg") // "assetIconJpeg" 15 | iconName(for: "JPG") // "assetIconUnknown", not favorable. 16 | 17 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 18 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Message as a struct.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Message as a struct 4 | 5 | import Foundation 6 | 7 | struct Message { 8 | let userId: String 9 | let contents: String? 10 | let date: Date 11 | 12 | let hasJoined: Bool 13 | let hasLeft: Bool 14 | 15 | let isBeingDrafted: Bool 16 | let isSendingBalloons: Bool 17 | } 18 | 19 | let joinMessage = Message(userId: "1", 20 | contents: nil, 21 | date: Date(), 22 | hasJoined: true, // We set the joined boolean 23 | hasLeft: false, 24 | isBeingDrafted: false, 25 | isSendingBalloons: false) 26 | 27 | 28 | let textMessage = Message(userId: "2", 29 | contents: "Hey everyone!", // We pass a message 30 | date: Date(), 31 | hasJoined: false, 32 | hasLeft: false, 33 | isBeingDrafted: false, 34 | isSendingBalloons: false) 35 | 36 | // chatroom.sendMessage(joinMessage) 37 | // chatroom.sendMessage(textMessage) 38 | 39 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 40 | 41 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Message as an enum.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Message as an enum 4 | 5 | import Foundation 6 | 7 | enum Message { 8 | case text(userId: String, contents: String, date: Date) 9 | case draft(userId: String, date: Date) 10 | case join(userId: String, date: Date) 11 | case leave(userId: String, date: Date) 12 | case balloon(userId: String, date: Date) 13 | } 14 | 15 | let textMessage = Message.text(userId: "2", contents: "Bonjour!", date: Date()) 16 | 17 | let joinMessage = Message.join(userId: "2", date: Date()) 18 | 19 | func logMessage(message: Message) { 20 | switch message { 21 | case let .text(userId: id, contents: contents, date: date): 22 | print("[\(date)] User \(id) sends message: \(contents)") 23 | case let .draft(userId: id, date: date): 24 | print("[\(date)] User \(id) is drafting a message") 25 | case let .join(userId: id, date: date): 26 | print("[\(date)] User \(id) has joined the chatroom") 27 | case let .leave(userId: id, date: date): 28 | print("[\(date)] User \(id) has left the chatroom") 29 | case let .balloon(userId: id, date: date): 30 | print("[\(date)] User \(id) is sending balloons") 31 | } 32 | } 33 | 34 | logMessage(message: joinMessage) // User 2 has joined the chatroom 35 | logMessage(message: textMessage) // User 2 sends message: Bonjour! 36 | 37 | 38 | 39 | if case let Message.text(_, contents: contents, _) = textMessage { 40 | print("Received: \(contents)") // Bonjour! 41 | } 42 | 43 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 44 | 45 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/Pages/Polymorphism.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | let arr: [Any] = [Date(), "Why was six afraid of seven?", "Because...", 789] 6 | 7 | for element: Any in arr { 8 | // element is "Any" type 9 | switch element { 10 | case let stringValue as String: "received a string: \(stringValue)" 11 | case let intValue as Int: "received an Int: \(intValue)" 12 | case let dateValue as Date: "received a date: \(dateValue)" 13 | default: "I don't want anything else" 14 | } 15 | } 16 | 17 | 18 | enum DateType { 19 | case singleDate(Date) 20 | case dateRange(Range) 21 | // case year(Int8) // Uncomment to see compiler complaints 22 | } 23 | 24 | let now = Date() 25 | let hourFromNow = Date(timeIntervalSinceNow: 3600) 26 | 27 | let dates: [DateType] = [ 28 | DateType.singleDate(now), 29 | DateType.dateRange(now.. Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Message as a struct](Message%20as%20a%20struct) 25 | 1. [An invalid Message](An%20invalid%20Message) 26 | 1. [Message as an enum](Message%20as%20an%20enum) 27 | 1. [Polymorphism](Polymorphism) 28 | 1. [Enums instead of subclassing](Enums%20instead%20of%20subclassing) 29 | 1. [Enums instead of subclassing Workout type](Enums%20instead%20of%20subclassing%20Workout%20type) 30 | 1. [Algebraic Data Types](Algebraic%20data%20types) 31 | 1. [Raw value enum](Raw%20value%20enum) 32 | 1. [Raw value enum with values omitted](Raw%20value%20enum%20with%20values%20omitted) 33 | 1. [Raw value enum with single value specified](Raw%20value%20enum%20with%20single%20value%20specified) 34 | 1. [Explicit matching on raw value enum](Explicit%20matching%20on%20raw%20value%20enum) 35 | 1. [Matching on strings](Matching%20on%20strings) 36 | 1. [Creating a string type enum](Creating%20a%20string%20type%20enum) 37 | 1. [Exercises](Exercises) 38 | 1. [Answers](Answers) 39 | */ 40 | //: [Next](@next) 41 | 42 | 43 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch02-enums/Enums.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Answers.xcplaygroundpage/Resources/File: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Answers.xcplaygroundpage/Resources/songs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | artist 7 | Bill Withers 8 | track 9 | Ain't no sunshine 10 | year 11 | 1971 12 | duration 13 | 125 14 | 15 | 16 | artist 17 | Bill Withers 18 | track 19 | Lovely day 20 | year 21 | 1977 22 | duration 23 | 253 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Exercises.xcplaygroundpage/Resources/songs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | artist 7 | Bill Withers 8 | track 9 | Ain't no sunshine 10 | year 11 | 1971 12 | duration 13 | 125 14 | 15 | 16 | artist 17 | Bill Withers 18 | track 19 | Lovely day 20 | year 21 | 1977 22 | duration 23 | 253 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/LearningPlan lazy loading after copying.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # LearningPlan lazy loading after copying 4 | 5 | import Foundation 6 | 7 | //: ## Introducing the LearningPlan struct 8 | 9 | struct LearningPlan { 10 | 11 | var level: Int 12 | 13 | var description: String 14 | 15 | lazy private(set) var contents: String = { 16 | // Smart algorithm calculation simulated here 17 | print("I'm taking my sweet time to calculate.") 18 | sleep(2) 19 | 20 | switch level { 21 | case ..<25: return "Watch an English documentary." 22 | case ..<50: return "Translate a newspaper article to English and transcribe one song." 23 | case 100...: return "Read two academic papers and translate them into your native language." 24 | default: return "Try to have 30 mins of English reading." 25 | } 26 | }() 27 | 28 | init(level: Int, description: String) { 29 | self.level = level 30 | self.description = description 31 | } 32 | } 33 | 34 | 35 | var intensePlan = LearningPlan(level: 138, description: "A special plan for today!") 36 | 37 | 38 | //: Copy is being made. 39 | var easyPlan = intensePlan 40 | 41 | //: Trigger lazy loading after a copy. 42 | intensePlan.contents 43 | 44 | //: We lower the intensity 45 | easyPlan.level = 0 46 | 47 | //: Now each plan loads their own contents separately 48 | print(intensePlan.contents) // Read two academic papers and translate them into your native language. 49 | print(easyPlan.contents) // Watch an English documentary. 50 | 51 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 52 | 53 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/LearningPlan lazy loading before copying.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # LearningPlan lazy loading before copying 4 | 5 | import Foundation 6 | 7 | //: ## Introducing the LearningPlan struct 8 | 9 | struct LearningPlan { 10 | 11 | var level: Int 12 | 13 | var description: String 14 | 15 | //: contents is now a lazy property. 16 | lazy private(set) var contents: String = { 17 | // Smart algorithm calculation simulated here 18 | print("I'm taking my sweet time to calculate.") 19 | sleep(2) 20 | 21 | switch level { 22 | case ..<25: return "Watch an English documentary." 23 | case ..<50: return "Translate a newspaper article to English and transcribe one song." 24 | case 100...: return "Read two academic papers and translate them into your native language." 25 | default: return "Try to have 30 mins of English reading." 26 | } 27 | }() 28 | 29 | init(level: Int, description: String) { 30 | self.level = level 31 | self.description = description 32 | } 33 | } 34 | 35 | 36 | var intensePlan = LearningPlan(level: 138, description: "A special plan for today!") 37 | 38 | //: Trigger lazy loading before a copy. 39 | intensePlan.contents 40 | 41 | //: Copy is being made. 42 | var easyPlan = intensePlan 43 | 44 | //: We lower the intensity 45 | easyPlan.level = 0 46 | //: It still recommends reading academic papers, a lazily loaded property is copied 47 | print(easyPlan.contents) 48 | 49 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 50 | 51 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/LearningPlan with a breaking lazy property.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # LearningPlan with a breaking lazy property 4 | 5 | import Foundation 6 | 7 | //: ## Introducing the LearningPlan struct 8 | 9 | struct LearningPlan { 10 | 11 | let level: Int 12 | 13 | var description: String 14 | 15 | //: contents is now a lazy property. 16 | lazy var contents: String = { 17 | // Smart algorithm calculation simulated here 18 | print("I'm taking my sweet time to calculate.") 19 | sleep(2) 20 | 21 | switch level { 22 | case ..<25: return "Watch an English documentary." 23 | case ..<50: return "Translate a newspaper article to English and transcribe one song." 24 | case 100...: return "Read two academic papers and translate them into your native language." 25 | default: return "Try to have 30 mins of English reading." 26 | } 27 | }() 28 | 29 | init(level: Int, description: String) { 30 | self.level = level 31 | self.description = description 32 | } 33 | } 34 | 35 | var plan = LearningPlan(level: 18, description: "A special plan for today!") 36 | //: We can easily break lazy properties. 37 | plan.contents = "Let's eat pizza and watch Netflix all day" 38 | print(plan.contents) // "Let's eat pizza and watch Netflix all day" 39 | 40 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 41 | 42 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/LearningPlan with a lazy private setter.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # LearningPlan with a lazy private setter 4 | 5 | import Foundation 6 | 7 | //: ## Introducing the LearningPlan struct 8 | 9 | struct LearningPlan { 10 | 11 | let level: Int 12 | 13 | var description: String 14 | 15 | //: contents is now a lazy property. 16 | lazy private(set) var contents: String = { 17 | // Smart algorithm calculation simulated here 18 | print("I'm taking my sweet time to calculate.") 19 | sleep(2) 20 | 21 | switch level { 22 | case ..<25: return "Watch an English documentary." 23 | case ..<50: return "Translate a newspaper article to English and transcribe one song." 24 | case 100...: return "Read two academic papers and translate them into your native language." 25 | default: return "Try to have 30 mins of English reading." 26 | } 27 | }() 28 | 29 | init(level: Int, description: String) { 30 | self.level = level 31 | self.description = description 32 | } 33 | } 34 | 35 | var plan = LearningPlan(level: 18, description: "A special plan for today!") 36 | //: Now, plan's content cannot be overriden, much safer! 37 | //: See for yourself by uncommenting the next line. 38 | //plan.contents = "Let's eat pizza and watch Netflix all day" 39 | 40 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 41 | 42 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/LearningPlan with a lazy property.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # LearningPlan with a lazy property 4 | 5 | import Foundation 6 | 7 | //: ## Introducing the LearningPlan struct 8 | 9 | struct LearningPlan { 10 | 11 | let level: Int 12 | 13 | var description: String 14 | 15 | //: contents is now a lazy property. 16 | lazy var contents: String = { 17 | // Smart algorithm calculation simulated here 18 | print("I'm taking my sweet time to calculate.") 19 | sleep(2) 20 | 21 | switch level { 22 | case ..<25: return "Watch an English documentary." 23 | case ..<50: return "Translate a newspaper article to English and transcribe one song." 24 | case 100...: return "Read two academic papers and translate them into your native language." 25 | default: return "Try to have 30 mins of English reading." 26 | } 27 | }() 28 | 29 | init(level: Int, description: String) { 30 | self.level = level 31 | self.description = description 32 | } 33 | } 34 | 35 | var plan = LearningPlan(level: 18, description: "A special plan for today!") 36 | 37 | //: Contents only loads once. 38 | 39 | print(Date()) 40 | for _ in 0..<5 { 41 | plan.contents 42 | } 43 | print(Date()) 44 | 45 | 46 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 47 | 48 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/LearningPlan.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # LearningPlan 4 | 5 | import Foundation 6 | 7 | //: ## Introducing the LearningPlan struct 8 | 9 | struct LearningPlan { 10 | 11 | let level: Int 12 | 13 | var description: String 14 | 15 | //: contents is a computed property. 16 | var contents: String { 17 | // Smart algorithm calculation simulated here 18 | print("I'm taking my sweet time to calculate.") 19 | sleep(2) 20 | 21 | switch level { 22 | case ..<25: return "Watch an English documentary." 23 | case ..<50: return "Translate a newspaper article to English and transcribe one song." 24 | case 100...: return "Read two academic papers and translate them into your native language." 25 | default: return "Try to have 30 mins of English reading." 26 | } 27 | } 28 | } 29 | 30 | var plan = LearningPlan(level: 18, description: "A special plan for today!") 31 | print(Date()) 32 | print(plan.contents) 33 | print(Date()) 34 | //: Running contents five times takes ten seconds. 35 | print(Date()) 36 | for _ in 0..<5 { 37 | plan.contents 38 | } 39 | print(Date()) 40 | 41 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 42 | 43 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/LearningPlan.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Property observer initializer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Property observer initializer 4 | 5 | import Foundation 6 | 7 | class Player { 8 | 9 | let id: String 10 | 11 | var name: String { 12 | didSet { 13 | print("My previous name was \(oldValue)") 14 | name = name.trimmingCharacters(in: .whitespaces) 15 | } 16 | } 17 | 18 | init(id: String, name: String) { 19 | defer { self.name = name } 20 | self.id = id 21 | self.name = name 22 | } 23 | } 24 | 25 | //: Now the initializer also cleans up the whitespace. 26 | let jeff = Player(id: "1", name: "SuperJeff ") 27 | print(jeff.name) 28 | print(jeff.name.count) // 9 29 | 30 | //: Changing the name cleans up the whitespace of the property. 31 | 32 | jeff.name = "SuperJeff " 33 | print(jeff.name) 34 | print(jeff.name.count) // 9 35 | 36 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Property observer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Property observer 4 | 5 | import Foundation 6 | 7 | //: ## Introducing the Player struct 8 | 9 | class Player { 10 | 11 | let id: String 12 | 13 | var name: String { 14 | didSet { 15 | name = name.trimmingCharacters(in: .whitespaces) 16 | } 17 | } 18 | 19 | init(id: String, name: String) { 20 | self.id = id 21 | self.name = name 22 | } 23 | } 24 | 25 | //: Initializing the struct doesn't clean up the whitespace of the property. 26 | 27 | let jeff = Player(id: "1", name: "SuperJeff ") 28 | print(jeff.name) 29 | print(jeff.name.count) // 13 30 | 31 | //: Changing the name does clean up the whitespace of the property. 32 | 33 | jeff.name = "SuperJeff " 34 | print(jeff.name) 35 | print(jeff.name.count) // 9 36 | 37 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Run with computed properties setter.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Run with computed properties setter 4 | //: ## Introducing the Run workout 5 | 6 | import Foundation 7 | 8 | struct Run { 9 | let id: String 10 | let startTime: Date 11 | var endTime: Date? 12 | 13 | var elapsedTime: TimeInterval { 14 | return Date().timeIntervalSince(startTime) 15 | } 16 | 17 | var isFinished: Bool { 18 | get { 19 | return endTime != nil 20 | } set { 21 | if !newValue { 22 | endTime = nil 23 | } else if endTime == nil { // Only set endTime if it wasn't finished 24 | endTime = Date() 25 | } 26 | } 27 | } 28 | 29 | init(id: String, startTime: Date) { 30 | self.id = id 31 | self.startTime = startTime 32 | self.endTime = nil 33 | } 34 | } 35 | 36 | //: ## Creating a Run workout 37 | 38 | var run = Run(id: "10", startTime: Date()) 39 | 40 | //: Here we are reading the values dynamically. 41 | 42 | print(run.endTime) // nil 43 | print(run.isFinished) // false 44 | run.isFinished = true 45 | print(run.endTime) // Optional(2017-09-30 15:41:22 +0000) 46 | // We continue again 47 | run.isFinished = false 48 | print(run.endTime) // nil 49 | 50 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 51 | 52 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Run with computed properties.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Run with computed properties 4 | //: ## Introducing the Run workout 5 | import Foundation 6 | 7 | struct Run { 8 | let id: String 9 | let startTime: Date 10 | var endTime: Date? 11 | 12 | var elapsedTime: TimeInterval { 13 | return Date().timeIntervalSince(startTime) 14 | } 15 | 16 | var isFinished: Bool { 17 | return endTime != nil 18 | } 19 | 20 | mutating func setFinished() { 21 | endTime = Date() 22 | } 23 | 24 | init(id: String, startTime: Date) { 25 | self.id = id 26 | self.startTime = startTime 27 | self.endTime = nil 28 | } 29 | } 30 | 31 | //: ## Creating a Run workout 32 | 33 | var run = Run(id: "10", startTime: Date()) 34 | 35 | //: Here we are reading the values dynamically. 36 | 37 | print(run.elapsedTime) 38 | sleep(4) 39 | print(run.elapsedTime) 40 | print(run.isFinished) 41 | run.endTime = Date() 42 | print(run.isFinished) 43 | 44 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 45 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Run with functions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Run with functions 4 | //: ## Introducing the Run workout 5 | import Foundation 6 | 7 | struct Run { 8 | let id: String 9 | let startTime: Date 10 | var endTime: Date? 11 | 12 | func elapsedTime() -> TimeInterval { 13 | return Date().timeIntervalSince(startTime) 14 | } 15 | 16 | func isFinished() -> Bool { 17 | return endTime != nil 18 | } 19 | 20 | mutating func setFinished() { 21 | endTime = Date() 22 | } 23 | 24 | init(id: String, startTime: Date) { 25 | self.id = id 26 | self.startTime = startTime 27 | self.endTime = nil 28 | } 29 | } 30 | 31 | //: ## Creating a Run workout 32 | 33 | var run = Run(id: "10", startTime: Date()) 34 | 35 | //: Here we are reading the values dynamically. 36 | 37 | print(run.elapsedTime()) 38 | sleep(4) 39 | print(run.elapsedTime()) 40 | print(run.isFinished()) 41 | run.setFinished() 42 | print(run.elapsedTime()) 43 | 44 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 45 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Working with properties 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Run with functions](Run%20with%20functions) 25 | 1. [Run with computed properties](Run%20with%20computed%20properties) 26 | 1. [Run with computed properties setter](Run%20with%20computed%20properties%20setter) 27 | 1. [LearningPlan](LearningPlan) 28 | 1. [LearningPlan with a lazy property](LearningPlan%20with%20a%20lazy%20property) 29 | 1. [LearningPlan with a breaking lazy property](LearningPlan%20with%20a%20breaking%20lazy%20property) 30 | 1. [LearningPlan with a lazy private setter](LearningPlan%20with%20a%20lazy%20private%20setter) 31 | 1. [LearningPlan lazy loading before copying](LearningPlan%20lazy%20loading%20before%20copying) 32 | 1. [LearningPlan lazy loading after copying](LearningPlan%20lazy%20loading%20after%20copying) 33 | 1. [Property observer](Property%20observer) 34 | 1. [Property observer initializer](Property%20observer%20initializer) 35 | 1. [Exercises](Exercises) 36 | 1. [Answers](Answers) 37 | */ 38 | //: [Next](@next) 39 | 40 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch03-properties/Properties.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Answer optional booleans.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Answer: Optional booleans 4 | 5 | enum AudioSetting: RawRepresentable { 6 | case enabled 7 | case disabled 8 | case unknown 9 | 10 | init(rawValue: Bool?) { 11 | switch rawValue { 12 | case let isEnabled? where isEnabled: self = .enabled 13 | case let isEnabled? where !isEnabled: self = .disabled 14 | default: self = .unknown 15 | } 16 | } 17 | 18 | var rawValue: Bool? { 19 | switch self { 20 | case .enabled: return true 21 | case .disabled: return false 22 | case .unknown: return nil 23 | } 24 | } 25 | 26 | } 27 | 28 | //: Given this optional Boolean 29 | let configuration = ["audioEnabled": true] 30 | 31 | //: Create an enum out of it so it can handle all three cases. 32 | 33 | let audioSetting = AudioSetting(rawValue: configuration["audioEnabled"]) 34 | 35 | switch audioSetting { 36 | case .enabled: print("Turn up the jam!") 37 | case .disabled: print("sshh") 38 | case .unknown: print("Ask the user for the audio setting") 39 | } 40 | 41 | //: Get the booelan value out of the enum again as well. 42 | 43 | let isEnabled = audioSetting.rawValue 44 | 45 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 46 | 47 | 48 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Exercise Implicitly Unwrapped Optionals.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 2 | 3 | //: # Exercise: Implicitly Unwrapped Optionals 4 | 5 | //: Question: What are good alternatives to Implicitly Unwrapped Optionals? 6 | 7 | //: Answer: Lazy properties or factories that are passed via an initializer. 8 | 9 | 10 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 11 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Exercise optional booleans.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | 2 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 3 | 4 | //: # Exercise: Optional booleans 5 | 6 | //: Given this optional Boolean 7 | let configuration = ["audioEnabled": true] 8 | 9 | //: Create an enum out of it so it can handle all three cases. 10 | 11 | let audioSetting = AudioSetting(rawValue: configuration["audioEnabled"]) 12 | 13 | switch audioSetting { 14 | case .enabled: print("Turn up the jam!") 15 | case .disabled: print("sshh") 16 | case .unknown: print("Ask the user for the audio setting") 17 | } 18 | 19 | //: Get the booelan value out of the enum again as well. 20 | 21 | let isEnabled = audioSetting.rawValue 22 | 23 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 24 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Fallback values.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Fallback values 4 | 5 | struct Customer { 6 | let id: String 7 | let email: String 8 | let firstName: String? 9 | let lastName: String? 10 | let balance: Int 11 | 12 | var displayName: String? { 13 | switch (firstName, lastName) { 14 | case let (first?, last?): return first + " " + last 15 | case let (first?, nil): return first 16 | case let (nil, last?): return last 17 | default: return nil 18 | } 19 | } 20 | 21 | } 22 | 23 | func createConfirmationMessage(name: String, product: String) -> String { 24 | return """ 25 | Dear \(name), 26 | Thank you for ordering the \(product)" 27 | Your order will be delivered tomorrow. 28 | 29 | Kind regards, 30 | The Mayonnaise depot. 31 | """ 32 | } 33 | 34 | let customer = Customer(id: "30", email: "famthompson@gmail.com", firstName: nil, lastName: "Thompson", balance: 300) 35 | 36 | let name: String = customer.displayName ?? "customer" 37 | createConfirmationMessage(name: name, product: "Economy size party tub!") 38 | 39 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/First answers.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Answers: multiple optionals 4 | 5 | //: 1. If no optionals in a function are allowed to have a value, what would be a good tactic to make all optionals are filled? 6 | //: Use a guard, this can block optionals at the top of a function. 7 | 8 | //: 2. If the functions takes different paths depending on the optionals inside it, what would be a good approach to take to handle all these paths? 9 | //: Putting multiple optionals inside a tuple allows you to pattern match on them. 10 | 11 | //: Given these two enums which represent the data being copied, erased and pasted from the pasteboard (aka clipboard). 12 | 13 | enum PasteBoardEvent { 14 | case added 15 | case erased 16 | case pasted 17 | } 18 | 19 | enum PasteBoardContents { 20 | case url(url: String) 21 | case emailAddress(emailAddress: String) 22 | case other(contents: String) 23 | } 24 | 25 | // We can use a single switch statement inside describeAction. 26 | func describeAction(event: PasteBoardEvent?, contents: PasteBoardContents?) -> String { 27 | switch (event, contents) { 28 | case let (.added?, .url(url)?): return "User added a url to pasteboard: \(url)" 29 | case (.added?, _): return "User added something to pasteboard" 30 | case (.erased?, .emailAddress?): return "User erased an email address from the pasteboard" 31 | default: return "The pasteboard is updated" 32 | } 33 | } 34 | 35 | //: Given this input 36 | describeAction(event: .added, contents: .url(url: "www.manning.com")) 37 | describeAction(event: .added, contents: .emailAddress(emailAddress: "info@manning.com")) 38 | describeAction(event: .erased, contents: .emailAddress(emailAddress: "info@manning.com")) 39 | describeAction(event: .erased, contents: nil) 40 | describeAction(event: nil, contents: .other(contents: "Swift in Depth")) 41 | 42 | //: Make sure the output is as follows 43 | 44 | "User added a url to pasteboard: www.manning.com" 45 | "User added something to pasteboard" 46 | "User erased an email address from the pasteboard" 47 | "The pasteboard is updated" 48 | "The pasteboard is updated" 49 | 50 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 51 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/First exercises.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Exercises: multiple optionals 4 | 5 | //: 1. If no optionals in a function are allowed to have a value, what would be a good tactic to make all optionals are filled? 6 | //: 2. If the functions takes different paths depending on the optionals inside it, what would be a good approach to take to handle all these paths? 7 | 8 | //: 3. Given these two enums which represent the data being copied, erased and pasted from the pasteboard (aka clipboard). 9 | 10 | enum PasteBoardContents { 11 | case url(url: String) 12 | case emailAddress(emailAddress: String) 13 | case other(contents: String) 14 | } 15 | 16 | //: The PasteBoardEvent represents the event related to PasteBoardContents. Perhaps the contents were added to the pasteboard, erased from the pasteboard, or pasted from the pasteboard. 17 | 18 | enum PasteBoardEvent { 19 | case added 20 | case erased 21 | case pasted 22 | } 23 | 24 | //: The describeAction function takes on the two enums, and return a String describing the event. Such as "The user added an email address to pasteboard". The goal of this exercise is to fill the body of a function. 25 | 26 | func describeAction(event: PasteBoardEvent?, contents: PasteBoardContents?) -> String { 27 | // What goes here? 28 | return "" 29 | } 30 | 31 | //: Given this input: 32 | 33 | describeAction(event: .added, contents: .url(url: "www.manning.com")) 34 | describeAction(event: .added, contents: .emailAddress(emailAddress: "info@manning.com")) 35 | describeAction(event: .erased, contents: .emailAddress(emailAddress: "info@manning.com")) 36 | describeAction(event: .erased, contents: nil) 37 | describeAction(event: nil, contents: .other(contents: "Swift in Depth")) 38 | 39 | //: Make sure that the output is as follows: 40 | 41 | "User added an url to pasteboard: www.manning.com." 42 | "User added something to pasteboard." 43 | "User erased an email address from the pasteboard." 44 | "The pasteboard is updated." 45 | "The pasteboard is updated." 46 | 47 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 48 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Granular unwrapping.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Granular unwrapping 4 | 5 | 6 | struct Customer { 7 | let id: String 8 | let email: String 9 | let firstName: String? 10 | let lastName: String? 11 | let balance: Int 12 | 13 | var displayName: String? { 14 | switch (firstName, lastName) { 15 | case let (first?, last?): return first + " " + last 16 | case let (first?, nil): return first 17 | case let (nil, last?): return last 18 | default: return nil 19 | } 20 | } 21 | 22 | } 23 | 24 | func createConfirmationMessage(name: String, product: String) -> String { 25 | return """ 26 | Dear \(name), 27 | Thank you for ordering the \(product)" 28 | Your order will be delivered tomorrow. 29 | 30 | Kind regards, 31 | The Mayonnaise depot. 32 | """ 33 | } 34 | 35 | let customer = Customer(id: "30", email: "famthompson@gmail.com", firstName: nil, lastName: "Thompson", balance: 300) 36 | 37 | if let displayName = customer.displayName { 38 | createConfirmationMessage(name: displayName, product: "Economy size party tub!") 39 | } else { 40 | createConfirmationMessage(name: "customer", product: "Economy size party tub!") 41 | } 42 | 43 | 44 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 45 | 46 | 47 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Implicitly Unwrapped Optionals initialization.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | class ChatService { 6 | var isHealthy = true 7 | // Left empty for demonstration purposes. 8 | } 9 | 10 | class ProcessMonitor { 11 | 12 | class func start() -> ProcessMonitor { 13 | return ProcessMonitor() 14 | } 15 | 16 | var chatService: ChatService! 17 | 18 | func status() -> String { 19 | if chatService.isHealthy { 20 | return "Everything is up and running" 21 | } else { 22 | return "Chatservice is down!" 23 | } 24 | } 25 | } 26 | 27 | let processMonitor = ProcessMonitor.start() 28 | //processMonitor.status() // fatal error: unexpectedly found nil 29 | 30 | let chat = ChatService() 31 | 32 | processMonitor.chatService = chat 33 | processMonitor.status() // "Everything is up and running" 34 | 35 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 36 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Optional boolean enum.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Optional boolean enum 4 | 5 | import Foundation 6 | 7 | enum UserPreference: RawRepresentable { 8 | case enabled 9 | case disabled 10 | case notSet 11 | 12 | init(rawValue: Bool?) { 13 | switch rawValue { 14 | case true?: self = .enabled 15 | case false?: self = .enabled 16 | default: self = .notSet 17 | } 18 | } 19 | 20 | var rawValue: Bool? { 21 | switch self { 22 | case .enabled: return true 23 | case .disabled: return false 24 | case .notSet: return nil 25 | } 26 | } 27 | 28 | } 29 | 30 | let preferences = ["autoLogin": true, "faceIdEnabled": true] 31 | 32 | let isFaceIdEnabled = preferences["faceIdEnabled"] 33 | print(isFaceIdEnabled) // Optional(true) 34 | 35 | // We convert the optional bool to an enum here. 36 | let faceIdPreference = UserPreference(rawValue: isFaceIdEnabled) 37 | 38 | // Now we can pass around the enum. 39 | // Implementers can match on it. 40 | switch faceIdPreference { 41 | case .enabled: print("Face ID is enabled") 42 | case .disabled: print("Face ID is disabled") 43 | case .notSet: print("Face ID preference is not set") 44 | } 45 | 46 | 47 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 48 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Optional boolean nil coalescing.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Optional boolean nil coalescing 6 | 7 | //: ## Falling back to false 8 | 9 | let preferences = ["autoLogin": true, "faceIdEnabled": true] 10 | print(preferences["faceIdEnabled"]) // Optional(true) 11 | let isFaceIdEnabled = preferences["faceIdEnabled"] ?? false 12 | print(isFaceIdEnabled) // Now a Bool instead of Optional(Bool) 13 | //: ## Falling back on true 14 | 15 | if preferences["faceIdEnabled"] ?? true { 16 | // go to Face ID settings screen. 17 | } else { 18 | // customer has disabled Face ID 19 | } 20 | 21 | 22 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Optional boolean nil coalescing.xcplaygroundpage/Resources/hello.txt: -------------------------------------------------------------------------------- 1 | Howdy! -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Optional chaining.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Optional chaining 4 | 5 | import UIKit 6 | 7 | enum Membership { 8 | /// 10% discount 9 | case gold 10 | /// 5% discount 11 | case silver 12 | } 13 | 14 | struct Customer { 15 | let id: String 16 | let email: String 17 | let firstName: String? 18 | let lastName: String? 19 | let balance: Int 20 | 21 | let membership: Membership? 22 | 23 | var displayName: String? { 24 | switch (firstName, lastName) { 25 | case let (first?, last?): return first + " " + last 26 | case let (first?, nil): return first 27 | case let (nil, last?): return last 28 | default: return nil 29 | } 30 | } 31 | 32 | let favoriteProduct: Product? 33 | 34 | } 35 | 36 | struct Product { 37 | let id: String 38 | let name: String 39 | let image: UIImage? 40 | } 41 | 42 | let url = Bundle.main.url(forResource: "mayo", withExtension: "jpg") 43 | //: We normally won't force unwrap this. 44 | let imageData = try! Data(contentsOf: url!) // We normally won't force unwrap this 45 | 46 | let product = Product(id: "3", name: "Hellmann's", image: UIImage(data: imageData)) 47 | 48 | let customer = Customer(id: "2", email: "fake@customer.com", firstName: "Jeff", lastName: nil, balance: 2000, membership: nil, favoriteProduct: product) 49 | 50 | let imageView = UIImageView() 51 | imageView.image = customer.favoriteProduct?.image 52 | 53 | //: Optional chaining with nil coalescing 54 | imageView.image = customer.favoriteProduct?.image ?? UIImage(named: "missing_image") 55 | 56 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 57 | 58 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Optional chaining.xcplaygroundpage/Resources/mayo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjeerdintveen/manning-swift-in-depth/4e44789d659a992ec2723d782847096ea066375a/ch04-optionals/Optionals.playground/Pages/Optional chaining.xcplaygroundpage/Resources/mayo.jpg -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Optional enums.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Optional enums 4 | 5 | enum Membership { 6 | /// 10% discount 7 | case gold 8 | /// 5% discount 9 | case silver 10 | } 11 | 12 | struct Customer { 13 | let id: String 14 | let email: String 15 | let firstName: String? 16 | let lastName: String? 17 | let balance: Int 18 | 19 | let membership: Membership? 20 | 21 | var displayName: String? { 22 | switch (firstName, lastName) { 23 | case let (first?, last?): return first + " " + last 24 | case let (first?, nil): return first 25 | case let (nil, last?): return last 26 | default: return nil 27 | } 28 | } 29 | 30 | } 31 | 32 | let customer = Customer(id: "30", email: "famthompson@gmail.com", firstName: nil, lastName: "Thompson", balance: 300, membership: .gold) 33 | 34 | if let membership = customer.membership { 35 | switch membership { 36 | case .gold: print("Customer gets 10% discount") 37 | case .silver: print("Customer gets 5% discount") 38 | } 39 | } else { 40 | print("Customer pays regular price") 41 | } 42 | 43 | switch customer.membership { 44 | case .gold?: print("Customer gets 10% discount") 45 | case .silver?: print("Customer gets 5% discount") 46 | case nil: print("Customer pays regular price") 47 | } 48 | 49 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Prohibited optionals.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Prohibited optionals 4 | 5 | struct Customer { 6 | let id: String 7 | let email: String 8 | let firstName: String? 9 | let lastName: String? 10 | let balance: Int 11 | 12 | var displayName: String { 13 | guard let firstName = firstName, let lastName = lastName else { 14 | return "" 15 | } 16 | return "\(firstName) \(lastName)" 17 | } 18 | 19 | } 20 | 21 | func createConfirmationMessage(name: String, product: String) -> String { 22 | return """ 23 | Dear \(name), 24 | Thank you for ordering the \(product)! 25 | Your order will be delivered tomorrow. 26 | 27 | Kind regards, 28 | The Mayonnaise depot. 29 | """ 30 | } 31 | 32 | let confirmationMessage = createConfirmationMessage(name: "Jeff", product: "Economy size party tub") 33 | print(confirmationMessage) 34 | 35 | let customer = Customer(id: "30", email: "mayolover@gmail.com", firstName: "Jake", lastName: "Freemason", balance: 300) 36 | 37 | if !customer.displayName.isEmpty { 38 | createConfirmationMessage(name: customer.displayName, product: "Economy size party tub!") 39 | } else { 40 | createConfirmationMessage(name: "customer", product: "Economy size party tub!") 41 | } 42 | 43 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 44 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Returning an optional string.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Returning an optional string 4 | 5 | struct Customer { 6 | let id: String 7 | let email: String 8 | let firstName: String? 9 | let lastName: String? 10 | let balance: Int 11 | 12 | var displayName: String? { 13 | guard let firstName = firstName, let lastName = lastName else { 14 | return "" 15 | } 16 | return "\(firstName) \(lastName)" 17 | } 18 | 19 | } 20 | 21 | func createConfirmationMessage(name: String, product: String) -> String { 22 | return """ 23 | Dear \(name), 24 | Thank you for ordering the \(product)" 25 | Your order will be delivered tomorrow. 26 | 27 | Kind regards, 28 | The Mayonnaise depot. 29 | """ 30 | } 31 | 32 | let customer = Customer(id: "30", email: "mayolover@gmail.com", firstName: "Jake", lastName: "Freemason", balance: 300) 33 | 34 | if let displayName = customer.displayName { 35 | createConfirmationMessage(name: displayName, product: "Economy size party tub!") 36 | } else { 37 | createConfirmationMessage(name: "customer", product: "Economy size party tub!") 38 | } 39 | 40 | 41 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 42 | 43 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Making optionals second nature 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Clean optional unwrapping](Clean%20optional%20unwrapping) 25 | 1. [Prohibited optionals](Prohibited%20optionals) 26 | 1. [Returning an optional string](Returning%20an%20optional%20string) 27 | 1. [Granular unwrapping](Granular%20unwrapping) 28 | 1. [First exercises](First%20exercises) 29 | 1. [First answers](First%20answers) 30 | 1. [Fallback values](Fallback%20values) 31 | 1. [Optional enums](Optional%20enums) 32 | 1. [Optionals chaining](Optional%20chaining) 33 | 1. [Optionals boolean nil coalescing](Optional%20boolean%20nil%20coalescing) 34 | 1. [Optionals boolean enum](Optional%20boolean%20enum) 35 | 1. [Exercise optional booleans](Exercise%20optional%20booleans) 36 | 1. [Answer optional booleans](Answer%20optional%20booleans) 37 | 1. [Implicitly Unwrapped Optionals initialization](Implicitly%20Unwrapped%20Optionals%20initialization) 38 | 1. [Exercise Implicitly Unwrapped Optionals](Exercise%20Implicitly%20Unwrapped%20Optionals) 39 | 40 | */ 41 | //: [Next](@next) 42 | 43 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch04-optionals/Optionals.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/Pages/Answers.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 2 | //: # Answers 3 | 4 | //: 1. No 5 | //: 2. 6 | 7 | struct Pancakes { 8 | 9 | enum SyrupType { 10 | case corn 11 | case molasses 12 | case maple 13 | } 14 | 15 | let syrupType: SyrupType 16 | let stackSize: Int 17 | 18 | } 19 | 20 | //: By making the custom initializer an extension you get both initializers. 21 | extension Pancakes { 22 | 23 | init(syrupType: SyrupType) { 24 | self.stackSize = 10 25 | self.syrupType = syrupType 26 | } 27 | 28 | } 29 | 30 | let pancakes = Pancakes(syrupType: .corn, stackSize: 8) 31 | let morePancakes = Pancakes(syrupType: .maple) 32 | 33 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 34 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/Pages/Custom initializer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import UIKit 3 | 4 | //: # A custom initializer 5 | 6 | enum Pawn: CaseIterable { 7 | case dog, car, ketchupBottle, iron, shoe, hat 8 | } 9 | 10 | struct Player { 11 | let name: String 12 | let pawn: Pawn 13 | 14 | init(name: String) { 15 | self.name = name 16 | self.pawn = Pawn.allCases.randomElement()! 17 | } 18 | } 19 | 20 | let player = Player(name: "SuperJeff") 21 | print(player.pawn) // shoe 22 | 23 | //: error: extra argument 'pawn' in call 24 | //let secondPlayer = Player(name: "Carl", pawn: .dog) 25 | 26 | 27 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 28 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/Pages/Exercises.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | //: # Exercises 3 | //: Given this struct 4 | struct Pancakes { 5 | 6 | enum SyrupType { 7 | case corn 8 | case molasses 9 | case maple 10 | } 11 | 12 | let syrupType: SyrupType 13 | let stackSize: Int 14 | 15 | init(syrupType: SyrupType) { 16 | self.stackSize = 10 17 | self.syrupType = syrupType 18 | } 19 | 20 | } 21 | 22 | //: 1. Will these work? 23 | //let pancakes = Pancakes(syrupType: .corn, stackSize: 8) 24 | //let morePancakes = Pancakes(syrupType: .maple) 25 | 26 | //: 2. If these initializers didn't work, can you make them work without adding another initializer? 27 | 28 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 29 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/Pages/Memberwise and custom initializer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import UIKit 3 | 4 | //: # A memberwise and custom initializer 5 | 6 | enum Pawn: CaseIterable { 7 | case dog, car, ketchupBottle, iron, shoe, hat 8 | } 9 | 10 | struct Player { 11 | let name: String 12 | let pawn: Pawn 13 | } 14 | 15 | extension Player { 16 | 17 | init(name: String) { 18 | self.name = name 19 | self.pawn = Pawn.allCases.randomElement()! 20 | } 21 | } 22 | 23 | let player = Player(name: "SuperJeff") 24 | let anotherPlayer = Player(name: "Mary", pawn: .dog) 25 | 26 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 27 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/Pages/Memberwise initializer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation 3 | 4 | //: # A memberwise initializer 5 | 6 | enum Pawn { 7 | case dog, car, ketchupBottle, iron, shoe, hat 8 | } 9 | 10 | struct Player { 11 | let name: String 12 | let pawn: Pawn 13 | } 14 | 15 | let player = Player(name: "SuperJeff", pawn: .shoe) 16 | 17 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 18 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Understanding initializers 4 | ## Struct initializer rules 5 | 6 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 7 | 8 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 9 | 10 | Published by [Manning][Manning] 11 | 12 | [Manning]: https://www.manning.com 13 | 14 | Press command + 1 to see all contents. 15 | 16 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 17 | 18 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 19 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 20 | 21 | **Written in Swift 5.0 using Xcode 11.3.1*** 22 | 23 | ## Table of Contents 24 | 25 | 1. [Memberwise initializer](Memberwise%20initializer) 26 | 1. [Memberwise and custom initializer](Memberwise%20and%20custom%20initializer) 27 | 1. [Custom initializer](Custom%20initializer) 28 | 1. [Exercises](Exercises) 29 | 1. [Answers](Answers) 30 | */ 31 | //: [Next](@next) 32 | 33 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch05-initializers/1. Struct initializer rules.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Answer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | //: # Answer 3 | 4 | class Device { 5 | 6 | var serialNumber: String 7 | var room: String 8 | 9 | init(serialNumber: String, room: String) { 10 | self.serialNumber = serialNumber 11 | self.room = room 12 | } 13 | 14 | convenience init() { 15 | self.init(serialNumber: "Unknown", room: "Unknown") 16 | } 17 | 18 | convenience init(serialNumber: String) { 19 | self.init(serialNumber: serialNumber, room: "Unknown") 20 | } 21 | 22 | convenience init(room: String) { 23 | self.init(serialNumber: "Unknown", room: room) 24 | } 25 | 26 | } 27 | 28 | class Television: Device { 29 | enum ScreenType { 30 | case led 31 | case oled 32 | case lcd 33 | case unknown 34 | } 35 | 36 | enum Resolution { 37 | case ultraHd 38 | case fullHd 39 | case hd 40 | case sd 41 | case unknown 42 | } 43 | 44 | let resolution: Resolution 45 | let screenType: ScreenType 46 | 47 | init(resolution: Resolution, screenType: ScreenType, serialNumber: String, room: String) { 48 | self.resolution = resolution 49 | self.screenType = screenType 50 | super.init(serialNumber: serialNumber, room: room) 51 | } 52 | 53 | //: We override a designated initializer from Device. 54 | override init(serialNumber: String, room: String) { 55 | self.resolution = .unknown 56 | self.screenType = .unknown 57 | super.init(serialNumber: serialNumber, room: room) 58 | } 59 | 60 | } 61 | 62 | //: Make the following line of code work by adding a single initializer somewhere. 63 | let firstTelevision = Television(room: "Lobby") 64 | let secondTelevision = Television(serialNumber: "abc") 65 | 66 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 67 | 68 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Boardgame.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Creating a BoardGame 6 | 7 | enum Pawn: CaseIterable { 8 | case dog, car, ketchupBottle, iron, shoe, hat 9 | } 10 | 11 | struct Player { 12 | let name: String 13 | let pawn: Pawn 14 | } 15 | 16 | extension Player { 17 | 18 | init(name: String) { 19 | self.name = name 20 | self.pawn = Pawn.allCases.randomElement()! 21 | } 22 | } 23 | 24 | //: ## The Boardgame class 25 | class BoardGame { 26 | let players: [Player] 27 | let numberOfTiles: Int 28 | 29 | init(players: [Player], numberOfTiles: Int) { 30 | self.players = players 31 | self.numberOfTiles = numberOfTiles 32 | } 33 | 34 | convenience init(players: [Player]) { 35 | self.init(players: players, numberOfTiles: 32) 36 | } 37 | 38 | convenience init(names: [String]) { 39 | var players = [Player]() 40 | for name in names { 41 | players.append(Player(name: name)) 42 | } 43 | self.init(players: players, numberOfTiles: 32) 44 | } 45 | } 46 | 47 | //: ## Initializing BoardGame. 48 | //: Uncomment the initializer you want to use. 49 | 50 | // Convenience initializer 51 | //let boardGame = BoardGame(names: ["Melissa", "SuperJeff", "Dave"]) 52 | 53 | 54 | let players = [ 55 | Player(name: "Melissa"), 56 | Player(name: "SuperJeff"), 57 | Player(name: "Dave") 58 | ] 59 | // Convenience initializer 60 | //let boardGame = BoardGame(players: players) 61 | 62 | // Designated initializer 63 | let boardGame = BoardGame(players: players, numberOfTiles: 32) 64 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 65 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Exercise.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | //: # Exercises 3 | // Given these classes: 4 | class Device { 5 | 6 | var serialNumber: String 7 | var room: String 8 | 9 | init(serialNumber: String, room: String) { 10 | self.serialNumber = serialNumber 11 | self.room = room 12 | } 13 | 14 | convenience init() { 15 | self.init(serialNumber: "Unknown", room: "Unknown") 16 | } 17 | 18 | convenience init(serialNumber: String) { 19 | self.init(serialNumber: serialNumber, room: "Unknown") 20 | } 21 | 22 | convenience init(room: String) { 23 | self.init(serialNumber: "Unknown", room: room) 24 | } 25 | 26 | } 27 | 28 | class Television: Device { 29 | enum ScreenType { 30 | case led 31 | case oled 32 | case lcd 33 | case unknown 34 | } 35 | 36 | enum Resolution { 37 | case ultraHd 38 | case fullHd 39 | case hd 40 | case sd 41 | case unknown 42 | } 43 | 44 | let resolution: Resolution 45 | let screenType: ScreenType 46 | 47 | init(resolution: Resolution, screenType: ScreenType, serialNumber: String, room: String) { 48 | self.resolution = resolution 49 | self.screenType = screenType 50 | super.init(serialNumber: serialNumber, room: room) 51 | } 52 | 53 | } 54 | 55 | //: Make the following line of code work by adding a single initializer somewhere. 56 | let firstTelevision = Television(room: "Lobby") 57 | let secondTelevision = Television(serialNumber: "abc") 58 | 59 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 60 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Mutability Land with broken initializer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation 3 | //: ## Mutability Land with broken initializer 4 | 5 | enum Pawn: CaseIterable { 6 | case dog, car, ketchupBottle, iron, shoe, hat 7 | } 8 | 9 | struct Player { 10 | let name: String 11 | let pawn: Pawn 12 | } 13 | 14 | extension Player { 15 | init(name: String) { 16 | self.name = name 17 | self.pawn = Pawn.allCases.randomElement()! 18 | } 19 | } 20 | 21 | class BoardGame { 22 | let players: [Player] 23 | let numberOfTiles: Int 24 | 25 | init(players: [Player], numberOfTiles: Int = 32) { 26 | self.players = players 27 | self.numberOfTiles = numberOfTiles 28 | } 29 | 30 | // convenience init(players: [Player]) { 31 | // self.init(players: players, numberOfTiles: 32) 32 | // } 33 | 34 | convenience init(names: [String]) { 35 | var players = [Player]() 36 | for name in names { 37 | players.append(Player(name: name)) 38 | } 39 | self.init(players: players, numberOfTiles: 32) 40 | } 41 | } 42 | 43 | class MutabilityLand: BoardGame { 44 | var scoreBoard = [String: Int]() 45 | var winner: Player? 46 | 47 | let instructions: String 48 | 49 | init(players: [Player], instructions: String, numberOfTiles: Int) { 50 | self.instructions = instructions 51 | super.init(players: players, numberOfTiles: numberOfTiles) 52 | } 53 | 54 | } 55 | 56 | /*: 57 | ## Initializer problem 58 | 59 | We can't initialize MutabilityLand here with the initializer from Boardgame. 60 | 61 | This is because BoardGame can't populate the 'instructions' property on MutabilityLand. 62 | */ 63 | 64 | // Doesn't work 65 | let mutabilityLand = MutabilityLand(names: ["Melissa", "SuperJeff", "Dave"]) 66 | 67 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 68 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Mutability Land with designated initializer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Mutability Land with a designated initializer 4 | 5 | import Foundation 6 | 7 | enum Pawn: CaseIterable { 8 | case dog, car, ketchupBottle, iron, shoe, hat 9 | } 10 | 11 | struct Player { 12 | let name: String 13 | let pawn: Pawn 14 | } 15 | 16 | extension Player { 17 | init(name: String) { 18 | self.name = name 19 | self.pawn = Pawn.allCases.randomElement()! 20 | } 21 | } 22 | 23 | class BoardGame { 24 | let players: [Player] 25 | let numberOfTiles: Int 26 | 27 | init(players: [Player], numberOfTiles: Int = 32) { 28 | self.players = players 29 | self.numberOfTiles = numberOfTiles 30 | } 31 | 32 | convenience init(players: [Player]) { 33 | self.init(players: players, numberOfTiles: 32) 34 | } 35 | 36 | convenience init(names: [String]) { 37 | var players = [Player]() 38 | for name in names { 39 | players.append(Player(name: name)) 40 | } 41 | self.init(players: players, numberOfTiles: 32) 42 | } 43 | } 44 | 45 | class MutabilityLand: BoardGame { 46 | var scoreBoard = [String: Int]() 47 | var winner: Player? 48 | 49 | let instructions: String 50 | 51 | init(players: [Player], instructions: String, numberOfTiles: Int) { 52 | self.instructions = instructions 53 | super.init(players: players, numberOfTiles: numberOfTiles) 54 | } 55 | } 56 | 57 | let players = [ 58 | Player(name: "Melissa"), 59 | Player(name: "SuperJeff"), 60 | Player(name: "Dave") 61 | ] 62 | 63 | /*: 64 | ## Initializer 65 | 66 | We can initialize MutabilityLand with its own initializer. 67 | 68 | */ 69 | 70 | // Designated initializer 71 | let mutabilityLand = MutabilityLand(players: players, instructions: "Just read the manual", numberOfTiles: 40) 72 | 73 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 74 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Mutability Land with designated override.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import UIKit 3 | //: ## Overriding a deisgnated initializer 4 | 5 | enum Pawn: CaseIterable { 6 | case dog, car, ketchupBottle, iron, shoe, hat 7 | } 8 | 9 | struct Player { 10 | let name: String 11 | let pawn: Pawn 12 | } 13 | 14 | extension Player { 15 | init(name: String) { 16 | self.name = name 17 | self.pawn = Pawn.allCases.randomElement()! 18 | } 19 | } 20 | 21 | class BoardGame { 22 | let players: [Player] 23 | let numberOfTiles: Int 24 | 25 | init(players: [Player], numberOfTiles: Int) { 26 | self.players = players 27 | self.numberOfTiles = numberOfTiles 28 | } 29 | 30 | convenience init(players: [Player]) { 31 | self.init(players: players, numberOfTiles: 32) 32 | } 33 | 34 | convenience init(names: [String]) { 35 | var players = [Player]() 36 | for name in names { 37 | players.append(Player(name: name)) 38 | } 39 | self.init(players: players, numberOfTiles: 32) 40 | } 41 | } 42 | /*: 43 | ## MutabilityLand override 44 | 45 | MutabilityLand now overrides the designated initializer from BoardGame. 46 | */ 47 | 48 | class MutabilityLand: BoardGame { 49 | var scoreBoard = [String: Int]() 50 | var winner: Player? 51 | 52 | let instructions: String 53 | 54 | init(players: [Player], instructions: String, numberOfTiles: Int) { 55 | self.instructions = instructions 56 | super.init(players: players, numberOfTiles: numberOfTiles) 57 | } 58 | 59 | override init(players: [Player], numberOfTiles: Int) { 60 | self.instructions = "Read the manual" 61 | super.init(players: players, numberOfTiles: numberOfTiles) 62 | } 63 | } 64 | 65 | let players = [ 66 | Player(name: "Melissa"), 67 | Player(name: "SuperJeff"), 68 | Player(name: "Dave") 69 | ] 70 | 71 | /*: 72 | ## Initializing MutabilityLand. 73 | 74 | MutabilityLand has access to many initializers now. 75 | 76 | Uncomment the initializer you want to use. 77 | */ 78 | 79 | 80 | // Designated initializer 81 | let mutabilityLand = MutabilityLand(players: players, instructions: "Just read the manual", numberOfTiles: 40) 82 | 83 | // BoardGame initializers all work again 84 | //let mutabilityLand = MutabilityLand(names: ["Melissa", "SuperJeff", "Dave"]) 85 | 86 | // Convenience initializer 87 | //let mutabilityLand = MutabilityLand(players: players) 88 | 89 | // Designated initializer 90 | //let mutabilityLand = MutabilityLand(players: players, numberOfTiles: 32) 91 | 92 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 93 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Mutability Land.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation 3 | 4 | //: # Creating Mutability land 5 | 6 | enum Pawn: CaseIterable { 7 | case dog, car, ketchupBottle, iron, shoe, hat 8 | } 9 | 10 | struct Player { 11 | let name: String 12 | let pawn: Pawn 13 | } 14 | 15 | extension Player { 16 | 17 | init(name: String) { 18 | self.name = name 19 | self.pawn = Pawn.allCases.randomElement()! 20 | } 21 | } 22 | 23 | //: BoardGame is a superclass now. 24 | 25 | class BoardGame { 26 | let players: [Player] 27 | let numberOfTiles: Int 28 | 29 | init(players: [Player], numberOfTiles: Int) { 30 | self.players = players 31 | self.numberOfTiles = numberOfTiles 32 | } 33 | 34 | convenience init(players: [Player]) { 35 | self.init(players: players, numberOfTiles: 32) 36 | } 37 | 38 | convenience init(names: [String]) { 39 | var players = [Player]() 40 | for name in names { 41 | players.append(Player(name: name)) 42 | } 43 | self.init(players: players, numberOfTiles: 32) 44 | } 45 | } 46 | 47 | //: ## We subclass BoardGame with the new MutabilityLand class 48 | 49 | class MutabilityLand: BoardGame { 50 | var scoreBoard = [String: Int]() 51 | var winner: Player? 52 | } 53 | 54 | //: ## Initializing MutabilityLand. 55 | //: Uncomment the initializer that you want to use. 56 | 57 | // Convenience initializer 58 | let mutabilityLand = MutabilityLand(names: ["Melissa", "SuperJeff", "Dave"]) 59 | 60 | 61 | let players = [ 62 | Player(name: "Melissa"), 63 | Player(name: "SuperJeff"), 64 | Player(name: "Dave") 65 | ] 66 | // Convenience initializer 67 | //let mutabilityLand = MutabilityLand(players: players) 68 | 69 | // Designated initializer 70 | //let mutabilityLand = MutabilityLand(players: players, numberOfTiles: 32) 71 | 72 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 73 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Understanding initializers 3 | ## Class initializers 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Boardgame](Boardgame) 25 | 1. [Mutability Land](Mutability%20Land) 26 | 1. [Mutability Land with broken initializer](Mutability%20Land%20with%20broken%20initializer) 27 | 1. [Mutability Land with designated initializer](Mutability%20Land%20with%20designated%20initializer) 28 | 1. [Mutability Land with designated override](Mutability%20Land%20with%20designated%20override) 29 | 1. [Exercise](Exercise) 30 | 1. [Answer](Answer) 31 | */ 32 | //: [Next](@next) 33 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch05-initializers/2. Class initializers.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch05-initializers/3. Minimizing initializers.playground/Pages/Answer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 2 | //: # Answer 3 | //: Given these classes: 4 | class Device { 5 | 6 | var serialNumber: String 7 | var room: String 8 | 9 | init(serialNumber: String, room: String) { 10 | self.serialNumber = serialNumber 11 | self.room = room 12 | } 13 | 14 | convenience init() { 15 | self.init(serialNumber: "Unknown", room: "Unknown") 16 | } 17 | 18 | convenience init(serialNumber: String) { 19 | self.init(serialNumber: serialNumber, room: "Unknown") 20 | } 21 | 22 | convenience init(room: String) { 23 | self.init(serialNumber: "Unknown", room: room) 24 | } 25 | 26 | } 27 | 28 | class Television: Device { 29 | enum ScreenType { 30 | case led 31 | case oled 32 | case lcd 33 | case unknown 34 | } 35 | 36 | enum Resolution { 37 | case ultraHd 38 | case fullHd 39 | case hd 40 | case sd 41 | case unknown 42 | } 43 | 44 | let resolution: Resolution 45 | let screenType: ScreenType 46 | 47 | init(resolution: Resolution, screenType: ScreenType, serialNumber: String, room: String) { 48 | self.resolution = resolution 49 | self.screenType = screenType 50 | super.init(serialNumber: serialNumber, room: room) 51 | } 52 | 53 | //: Add a convenience initializer to Television which overrides a designated initializer from Device. 54 | convenience override init(serialNumber: String, room: String) { 55 | self.init(resolution: .unknown, screenType: .unknown, serialNumber: serialNumber, room: room) 56 | } 57 | 58 | } 59 | 60 | class HandHeldTelevision: Television { 61 | let weight: Int 62 | 63 | init(weight: Int, resolution: Resolution, screenType: ScreenType, serialNumber: String, room: String) { 64 | self.weight = weight 65 | super.init(resolution: resolution, screenType: screenType, serialNumber: serialNumber, room: room) 66 | } 67 | 68 | //: Add a convenience initializer to HandHeldTelevision which overrides a designated initializer from Television. 69 | convenience override init(resolution: Resolution, screenType: ScreenType, serialNumber: String, room: String) { 70 | self.init(weight: 0, resolution: resolution, screenType: screenType, serialNumber: "Unknown", room: "UnKnown") 71 | } 72 | 73 | } 74 | 75 | //: Add two convenience override initializers in the subclassing hierarchy to make this initializer from the top-most superclass work. 76 | let handheldTelevision = HandHeldTelevision(serialNumber: "293nr30znNdjW") 77 | 78 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 79 | -------------------------------------------------------------------------------- /ch05-initializers/3. Minimizing initializers.playground/Pages/Convenience initializer override.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Convenience initializer override 4 | 5 | //: First we prepare the data types. 6 | 7 | import Foundation 8 | 9 | enum Pawn: CaseIterable { 10 | case dog, car, ketchupBottle, iron, shoe, hat 11 | } 12 | 13 | struct Player { 14 | let name: String 15 | let pawn: Pawn 16 | } 17 | 18 | extension Player { 19 | 20 | init(name: String) { 21 | self.name = name 22 | self.pawn = Pawn.allCases.randomElement()! 23 | } 24 | } 25 | 26 | class BoardGame { 27 | let players: [Player] 28 | let numberOfTiles: Int 29 | 30 | init(players: [Player], numberOfTiles: Int) { 31 | self.players = players 32 | self.numberOfTiles = numberOfTiles 33 | } 34 | 35 | convenience init(players: [Player]) { 36 | self.init(players: players, numberOfTiles: 32) 37 | } 38 | 39 | convenience init(names: [String]) { 40 | var players = [Player]() 41 | for name in names { 42 | players.append(Player(name: name)) 43 | } 44 | self.init(players: players, numberOfTiles: 32) 45 | } 46 | } 47 | 48 | /*: 49 | ## MutabilityLand changes 50 | 51 | Notice how MutabilityLand contains a convenience override, which points to "self.init" instead of "super.init" 52 | */ 53 | 54 | class MutabilityLand: BoardGame { 55 | var scoreBoard = [String: Int]() 56 | var winner: Player? 57 | 58 | let instructions: String 59 | 60 | init(players: [Player], instructions: String, numberOfTiles: Int) { 61 | self.instructions = instructions 62 | super.init(players: players, numberOfTiles: numberOfTiles) 63 | } 64 | 65 | convenience override init(players: [Player], numberOfTiles: Int) { 66 | self.init(players: players, instructions: "Read the manual", numberOfTiles: numberOfTiles) 67 | } 68 | } 69 | 70 | /*: 71 | ## Choose an initializer 72 | You can uncomment to see the initializers in action. 73 | */ 74 | 75 | 76 | let players = [ 77 | Player(name: "Melissa"), 78 | Player(name: "SuperJeff"), 79 | Player(name: "Dave") 80 | ] 81 | 82 | // Designated initializer 83 | let mutabilityLand = MutabilityLand(players: players, instructions: "Just read the manual", numberOfTiles: 40) 84 | 85 | // BoardGame initializers all work again 86 | //let mutabilityLand = MutabilityLand(names: ["Melissa", "SuperJeff", "Dave"]) 87 | 88 | // Convenience initializer 89 | //let mutabilityLand = MutabilityLand(players: players) 90 | 91 | // Designated initializer 92 | //let mutabilityLand = MutabilityLand(players: players, numberOfTiles: 32) 93 | 94 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 95 | -------------------------------------------------------------------------------- /ch05-initializers/3. Minimizing initializers.playground/Pages/Exercise.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | //: # Exercise 3 | //: Given these classes: 4 | class Device { 5 | 6 | var serialNumber: String 7 | var room: String 8 | 9 | init(serialNumber: String, room: String) { 10 | self.serialNumber = serialNumber 11 | self.room = room 12 | } 13 | 14 | convenience init() { 15 | self.init(serialNumber: "Unknown", room: "Unknown") 16 | } 17 | 18 | convenience init(serialNumber: String) { 19 | self.init(serialNumber: serialNumber, room: "Unknown") 20 | } 21 | 22 | convenience init(room: String) { 23 | self.init(serialNumber: "Unknown", room: room) 24 | } 25 | 26 | } 27 | 28 | class Television: Device { 29 | enum ScreenType { 30 | case led 31 | case oled 32 | case lcd 33 | case unknown 34 | } 35 | 36 | enum Resolution { 37 | case ultraHd 38 | case fullHd 39 | case hd 40 | case sd 41 | case unknown 42 | } 43 | 44 | let resolution: Resolution 45 | let screenType: ScreenType 46 | 47 | init(resolution: Resolution, screenType: ScreenType, serialNumber: String, room: String) { 48 | self.resolution = resolution 49 | self.screenType = screenType 50 | super.init(serialNumber: serialNumber, room: room) 51 | } 52 | 53 | } 54 | 55 | class HandHeldTelevision: Television { 56 | let weight: Int 57 | 58 | init(weight: Int, resolution: Resolution, screenType: ScreenType, serialNumber: String, room: String) { 59 | self.weight = weight 60 | super.init(resolution: resolution, screenType: screenType, serialNumber: serialNumber, room: room) 61 | } 62 | 63 | } 64 | 65 | //: Add two convenience override initializers in the subclassing hierarchy to make this initializer from the top-most superclass work. 66 | let handheldTelevision = HandHeldTelevision(serialNumber: "293nr30znNdjW") 67 | 68 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 69 | -------------------------------------------------------------------------------- /ch05-initializers/3. Minimizing initializers.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Understanding initializers 3 | ## Minimizing class initializers 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Boardgame](Boardgame) 25 | 1. [Convenience initializer override](Convenience%20initializer%20override) 26 | 1. [Convenience initializer subsub classes](Convenience%20override%20subsub%20classes) 27 | 1. [Exercise](Exercise) 28 | 1. [Answer](Answer) 29 | */ 30 | //: [Next](@next) 31 | 32 | -------------------------------------------------------------------------------- /ch05-initializers/3. Minimizing initializers.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ch05-initializers/3. Minimizing initializers.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch05-initializers/3. Minimizing initializers.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/Pages/Answers.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Answers 4 | //: 1. No, requires initializers are to enforce subclasses to implement an initializer. Structs can't be subclassed. 5 | //: 2. Factory methods and when a class adheres to a protocol with a declared initializer. 6 | 7 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/Pages/Exercises.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | //: # Exercises 3 | //: 1. Would required initializers make sense on structs, why or why not? 4 | //: 2. Can you name two reasons for needing required initializers? 5 | 6 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 7 | 8 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/Pages/Factory method.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation 3 | 4 | /*: 5 | # Factory methods 6 | This playground page demonstrates initializing via factory methods. 7 | 8 | First we prepare our data types. 9 | */ 10 | 11 | enum Pawn: CaseIterable { 12 | case dog, car, ketchupBottle, iron, shoe, hat 13 | } 14 | 15 | struct Player { 16 | let name: String 17 | let pawn: Pawn 18 | } 19 | 20 | extension Player { 21 | 22 | init(name: String) { 23 | self.name = name 24 | self.pawn = Pawn.allCases.randomElement()! 25 | } 26 | } 27 | 28 | /*: 29 | ## Introducing a factory method 30 | Notice how BoardGame gains the 'makeGame' factory method. 31 | 32 | Also notice how the initializer 'makeGame' points to, is now a required initialzer because of this. 33 | */ 34 | 35 | class BoardGame { 36 | let players: [Player] 37 | let numberOfTiles: Int 38 | 39 | required init(players: [Player], numberOfTiles: Int) { 40 | self.players = players 41 | self.numberOfTiles = numberOfTiles 42 | } 43 | 44 | convenience init(players: [Player]) { 45 | self.init(players: players, numberOfTiles: 32) 46 | } 47 | 48 | convenience init(names: [String]) { 49 | var players = [Player]() 50 | for name in names { 51 | players.append(Player(name: name)) 52 | } 53 | self.init(players: players, numberOfTiles: 32) 54 | } 55 | 56 | // This is a factory method. 57 | class func makeGame(players: [Player]) -> Self { 58 | return self.init(players: players, numberOfTiles: 32) 59 | } 60 | } 61 | 62 | class MutabilityLand: BoardGame { 63 | var scoreBoard = [String: Int]() 64 | var winner: Player? 65 | 66 | let instructions: String 67 | 68 | init(players: [Player], instructions: String, numberOfTiles: Int) { 69 | self.instructions = instructions 70 | super.init(players: players, numberOfTiles: numberOfTiles) 71 | } 72 | 73 | convenience required init(players: [Player], numberOfTiles: Int) { 74 | self.init(players: players, instructions: "Read the manual", numberOfTiles: numberOfTiles) 75 | } 76 | } 77 | 78 | /*: 79 | ## Initializing with a factory method 80 | Both superclasses and subclasses can now be initialized with a factory method 81 | */ 82 | 83 | let players = [ 84 | Player(name: "Melissa"), 85 | Player(name: "SuperJeff"), 86 | Player(name: "Dave") 87 | ] 88 | 89 | let boardGame = BoardGame.makeGame(players: players) 90 | let mutabilityLand = MutabilityLand.makeGame(players: players) 91 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 92 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/Pages/Final class.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation 3 | 4 | /*: 5 | ## Omitting the required keyword 6 | This playground page demonstrates how we don't need the required keyword when we make a class final. 7 | 8 | First we prepare our data types. 9 | */ 10 | 11 | enum Pawn: CaseIterable { 12 | case dog, car, ketchupBottle, iron, shoe, hat 13 | } 14 | 15 | struct Player { 16 | let name: String 17 | let pawn: Pawn 18 | } 19 | 20 | extension Player { 21 | 22 | init(name: String) { 23 | self.name = name 24 | self.pawn = Pawn.allCases.randomElement()! 25 | } 26 | } 27 | 28 | /*: 29 | ## BoardGame changes 30 | Notice how BoardGame adheres to the BoardGameType protocol, but also notice how Boardgame has a factory method called 'makeGame'. 31 | 32 | But BoardGame doesn't need required initializers anymore, because it can't be subclassed. 33 | 34 | The reason BoardGame can't be subclasses is because BoardGame is now a final class, indicated by the 'final' keyword. 35 | 36 | */ 37 | 38 | protocol BoardGameType { 39 | init(players: [Player], numberOfTiles: Int) 40 | } 41 | 42 | final class BoardGame: BoardGameType { 43 | let players: [Player] 44 | let numberOfTiles: Int 45 | 46 | class func makeGame(players: [Player]) -> Self { 47 | return self.init(players: players, numberOfTiles: 32) 48 | } 49 | 50 | // No need to make this required 51 | init(players: [Player], numberOfTiles: Int) { 52 | self.players = players 53 | self.numberOfTiles = numberOfTiles 54 | } 55 | 56 | convenience init(players: [Player]) { 57 | self.init(players: players, numberOfTiles: 32) 58 | } 59 | 60 | convenience init(names: [String]) { 61 | var players = [Player]() 62 | for name in names { 63 | players.append(Player(name: name)) 64 | } 65 | self.init(players: players, numberOfTiles: 32) 66 | } 67 | 68 | } 69 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 70 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/Pages/Protocol.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | import Foundation 3 | 4 | /*: 5 | ## Initializers required by a protocol 6 | This playground page demonstrates how initializers are required because of protocols. 7 | 8 | First we prepare our data types. 9 | */ 10 | 11 | enum Pawn: CaseIterable { 12 | case dog, car, ketchupBottle, iron, shoe, hat 13 | } 14 | 15 | struct Player { 16 | let name: String 17 | let pawn: Pawn 18 | } 19 | 20 | extension Player { 21 | 22 | init(name: String) { 23 | self.name = name 24 | self.pawn = Pawn.allCases.randomElement()! 25 | } 26 | } 27 | 28 | /*: 29 | ## Introducing a protocol 30 | 31 | The BoardGameType protocol has an initializer. 32 | 33 | Notice how the designated initializer in BoardGame is now made required. 34 | 35 | This is needed to satisfy the BoardGameType protocol (otherwise subclasses wouldn't adhere to the protocol) 36 | */ 37 | 38 | protocol BoardGameType { 39 | init(players: [Player], numberOfTiles: Int) 40 | } 41 | 42 | class BoardGame: BoardGameType { 43 | let players: [Player] 44 | let numberOfTiles: Int 45 | 46 | required init(players: [Player], numberOfTiles: Int) { 47 | self.players = players 48 | self.numberOfTiles = numberOfTiles 49 | } 50 | 51 | convenience init(players: [Player]) { 52 | self.init(players: players, numberOfTiles: 32) 53 | } 54 | 55 | convenience init(names: [String]) { 56 | var players = [Player]() 57 | for name in names { 58 | players.append(Player(name: name)) 59 | } 60 | self.init(players: players, numberOfTiles: 32) 61 | } 62 | } 63 | 64 | class MutabilityLand: BoardGame { 65 | var scoreBoard = [String: Int]() 66 | var winner: Player? 67 | 68 | let instructions: String 69 | 70 | init(players: [Player], instructions: String, numberOfTiles: Int) { 71 | self.instructions = instructions 72 | super.init(players: players, numberOfTiles: numberOfTiles) 73 | } 74 | 75 | convenience required init(players: [Player], numberOfTiles: Int) { 76 | self.init(players: players, instructions: "Read the manual", numberOfTiles: numberOfTiles) 77 | } 78 | } 79 | 80 | /*: 81 | ## Initializing 82 | Now we can initialize MutabilityLand and BoardGame as usual. 83 | */ 84 | 85 | let players = [ 86 | Player(name: "Melissa"), 87 | Player(name: "SuperJeff"), 88 | Player(name: "Dave") 89 | ] 90 | 91 | let boardGame = BoardGame(players: players, numberOfTiles: 32) 92 | let mutabilityLand = MutabilityLand(players: players) 93 | 94 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 95 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Understanding initializers 3 | ## Required initializers 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Factory method](Factory%20method) 25 | 1. [Protocol](Protocol) 26 | 1. [Final class](Final%20class) 27 | 1. [Exercises](Exercises) 28 | 1. [Answers](Answers) 29 | */ 30 | //: [Next](@next) 31 | 32 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch05-initializers/4. Required.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/Pages/Capturing validity within a type.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Capturing validity within a type 4 | 5 | import Foundation 6 | 7 | enum ValidationError: Error { 8 | case noEmptyValueAllowed 9 | case invalidPhoneNumber 10 | } 11 | func validatePhoneNumber(_ text: String) throws { 12 | guard !text.isEmpty else { 13 | throw ValidationError.noEmptyValueAllowed 14 | } 15 | 16 | let pattern = "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" 17 | if text.range(of: pattern, options: .regularExpression, range: nil, locale: nil) == nil { 18 | throw ValidationError.invalidPhoneNumber 19 | } 20 | } 21 | 22 | do { 23 | try validatePhoneNumber("(123) 123-1234") 24 | print("Phonenumber is valid") 25 | } catch { 26 | print(error) 27 | } 28 | 29 | struct PhoneNumber { 30 | 31 | let contents: String 32 | 33 | init(_ text: String) throws { 34 | guard !text.isEmpty else { 35 | throw ValidationError.noEmptyValueAllowed 36 | } 37 | 38 | let pattern = "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" 39 | if text.range(of: pattern, options: .regularExpression, range: nil, locale: nil) == nil { 40 | throw ValidationError.invalidPhoneNumber 41 | } 42 | self.contents = text 43 | } 44 | } 45 | 46 | do { 47 | let phoneNumber = try PhoneNumber("(123) 123-1234") 48 | print(phoneNumber.contents) // (123) 123-1234 49 | } catch { 50 | print(error) 51 | } 52 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 53 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/Pages/Errors.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Errors 6 | 7 | enum ParseLocationError: Error { 8 | case invalidData 9 | case locationDoesNotExist 10 | case middleOfTheOcean 11 | } 12 | 13 | struct MultipleParseLocationErrors: Error { 14 | let parsingErrors: [ParseLocationError] 15 | let isShownToUser: Bool 16 | } 17 | 18 | struct Location { 19 | let latitude: Double 20 | let longitude: Double 21 | } 22 | 23 | /// Turns two strings with a latitude and longitude value into a Location type 24 | /// 25 | /// - Parameters: 26 | /// - latitude: A string containing a latitude value 27 | /// - longitude: A string containing a longitude value 28 | /// - Returns: A Location struct 29 | /// - Throws: Will throw a ParseLocationError.invalidData if lat and long can't be converted to Double. 30 | func parseLocation(_ latitude: String, _ longitude: String) throws -> Location { 31 | guard let latitude = Double(latitude), let longitude = Double(longitude) else { 32 | throw ParseLocationError.invalidData 33 | } 34 | 35 | return Location(latitude: latitude, longitude: longitude) 36 | } 37 | 38 | do { 39 | try parseLocation("I am not a double", "110") 40 | } catch { 41 | print(error) // invalidData 42 | } 43 | 44 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 45 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/Pages/Exercises.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | import PlaygroundSupport 5 | 6 | PlaygroundPage.current.needsIndefiniteExecution = true 7 | 8 | //: # Questions and answers 9 | 10 | //: 1. Can you name one or more downsides of how Swift handles errors, and how to compensate for them? 11 | 12 | // Functions are marked as throwing, so it places the burden on the developer to handle it. Yet, functions don't reveal which errors are thrown. 13 | 14 | //: 2. Can you name three ways to make sure throwing functions return to their original state after throwing errors? 15 | 16 | //Use immutable functions 17 | //Work on copies or temporary values 18 | //Use defer to reverse mutation 19 | 20 | //: 3. What's the downside of passing messages for the user inside an error? 21 | // Solution: Because then it's harder to differentiate between technical information and information to display to the user. 22 | 23 | //: 4. The following code will not compile. What two changes to loadFile can you make to make the code compile? (without resorting to try! and try?) 24 | 25 | //enum LoadError { 26 | // case couldntLoadFile 27 | //} 28 | // 29 | //func loadFile(name: String) -> Data? { 30 | // let url = playgroundSharedDataDirectory.appendingPathComponent(name) 31 | // do { 32 | // return try Data(contentsOf: url) 33 | // } catch let error as LoadError { 34 | // print("Can't load file named \(name)") 35 | // return nil 36 | // } 37 | //} 38 | 39 | //: Answer: Make loadFile catch all errors and not just a specific one. Or make loadFile throwing. 40 | 41 | //: 5. Can you name at least three ways to make throwing API's easier to use for developers? 42 | 43 | //: Capture an error when creating a type, so an error is handled only on creation of a type and not passing of a value. 44 | 45 | //: Return an optional instead of throwing an error when there is a single failing reason. 46 | 47 | //: Prevent propagation with the `try?` keyword. 48 | 49 | //: Prevent propagation with the `try!` keyword. 50 | 51 | //: [Next](@next) 52 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/Pages/Keeping predictable state.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Keeping predictable state 4 | 5 | import Foundation 6 | import PlaygroundSupport 7 | 8 | enum ListError: Error { 9 | case invalidValue 10 | } 11 | 12 | struct TodoList { 13 | 14 | private var values = [String]() 15 | 16 | //: This method does maintain a predictable state by working on a temporary value 17 | 18 | mutating func append(strings: [String]) throws { 19 | var trimmedStrings = [String]() 20 | for string in strings { 21 | let trimmedString = string.trimmingCharacters(in: .whitespacesAndNewlines) 22 | 23 | if trimmedString.isEmpty { 24 | throw ListError.invalidValue 25 | } else { 26 | trimmedStrings.append(trimmedString) 27 | } 28 | } 29 | 30 | // Only when everything succeeds, do we start mutating the struct. 31 | values.append(contentsOf: trimmedStrings) 32 | } 33 | 34 | //: This method doesn't maintain a predictable state. 35 | 36 | // mutating func append(strings: [String]) throws { 37 | // for string in strings { 38 | // let trimmedString = string.trimmingCharacters(in: .whitespacesAndNewlines) 39 | // 40 | // if trimmedString.isEmpty { 41 | // throw ListError.invalidValue 42 | // } else { 43 | // values.append(trimmedString) 44 | // } 45 | // } 46 | // } 47 | 48 | } 49 | 50 | //: We can use defer to revert mutation that occured halfway a process. 51 | func writeToFiles(data: [URL: String]) throws { 52 | var storedUrls = [URL]() 53 | defer { 54 | if storedUrls.count != data.count { 55 | for url in storedUrls { 56 | try! FileManager.default.removeItem(at: url) 57 | } 58 | } 59 | } 60 | 61 | for (url, contents) in data { 62 | try contents.write(to: url, atomically: true, encoding: String.Encoding.utf8) 63 | storedUrls.append(url) 64 | } 65 | } 66 | 67 | let url = playgroundSharedDataDirectory.appendingPathComponent("somefile.txt") 68 | 69 | //: Before running this, you need a to create a "Shared Playground Data" folder to your ~/Documents directory 70 | do { 71 | try writeToFiles(data: [url: "Hello there"]) 72 | } catch { 73 | print(error) 74 | } 75 | 76 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 77 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/Pages/Optionals.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Optionals 4 | 5 | import Foundation 6 | import PlaygroundSupport 7 | import UIKit 8 | 9 | //: Instead of throwing an error, we can return an optional 10 | 11 | func loadFile(name: String) -> Data? { 12 | let url = playgroundSharedDataDirectory.appendingPathComponent(name) 13 | return try? Data(contentsOf: url) 14 | } 15 | 16 | 17 | @discardableResult func storeData(image: UIImage, url: URL) -> Bool { 18 | guard let data = image.pngData() else { 19 | return false 20 | } 21 | 22 | do { 23 | try data.write(to: url) 24 | return true 25 | } catch { 26 | return false 27 | } 28 | } 29 | 30 | let image = UIImage() 31 | let url = playgroundSharedDataDirectory.appendingPathComponent("image") 32 | storeData(image: image, url: url) 33 | 34 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 35 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Effortless error handling 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Errors](Errors) 25 | 1. [Keeping predictable state](Keeping%20predictable%20state) 26 | 1. [Propagation](Propagation) 27 | 1. [Capturing validity within a type](Capturing%20validity%20within%20a%20type) 28 | 1. [Optionals](Optionals) 29 | 1. [Exercises](Exercises) 30 | 31 | */ 32 | //: [Next](@next) 33 | 34 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch06-error-handling/ErrorHandling.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Conforming to Comparable.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 2 | 3 | //: # Conforming to Comparable 4 | 5 | enum RoyalRank: Comparable { 6 | case emperor 7 | case king 8 | case duke 9 | 10 | static func <(lhs: RoyalRank, rhs: RoyalRank) -> Bool { 11 | switch (lhs, rhs) { 12 | case (king, emperor): return true 13 | case (duke, emperor): return true 14 | case (duke, king): return true 15 | default: return false 16 | } 17 | } 18 | 19 | //: Not needed, but we can manually implement the == method. 20 | // static func ==(lhs: RoyalRank, rhs: RoyalRank) -> Bool { 21 | // switch (lhs, rhs) { 22 | // case (emperor, emperor): return true 23 | // case (king, king): return true 24 | // case (duke, duke): return true 25 | // default: return false 26 | // } 27 | // } 28 | } 29 | 30 | func lowest(_ array: [T]) -> T? { 31 | return array.sorted().first 32 | } 33 | 34 | let king = RoyalRank.king 35 | let duke = RoyalRank.duke 36 | 37 | duke < king // true 38 | duke > king // false 39 | duke == king // false 40 | 41 | let ranks: [RoyalRank] = [.emperor, .king, .duke] 42 | lowest(ranks) // .duke 43 | 44 | 45 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 46 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Generic constrained function.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Generic constrained function 6 | 7 | //: Expanded function body 8 | 9 | /// Returns the lowest value inside an array, determined by comparable. 10 | /// 11 | /// - Parameter array: AN array of values 12 | /// - Returns: The lowest value 13 | //func lowest(_ array: [T]) -> T? { 14 | // let sortedArray = array.sorted { (lhs, rhs) -> Bool in 15 | // return lhs < rhs 16 | // } 17 | // return sortedArray.first 18 | //} 19 | 20 | //: Short function body 21 | 22 | func lowest(_ array: [T]) -> T? { 23 | return array.sorted().first 24 | } 25 | 26 | lowest([1,2,3]) 27 | lowest(["b","c","a"]) 28 | lowest([3.0, 3000, 2.3]) 29 | 30 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 31 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Generic function.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | func firstLast(array: [Int]) -> (Int, Int) { 6 | return (array[0], array[array.count-1]) 7 | } 8 | 9 | let (first, last) = firstLast(array: [1,2,3,4,5]) 10 | print(first) // 1 11 | print(last) // 5 12 | 13 | func firstLast(array: [String]) -> (String, String) { 14 | return (array[0], array[array.count-1]) 15 | } 16 | 17 | 18 | //: ## Generic function 19 | 20 | func firstLast(array: [T]) -> (T, T) { 21 | return (array[0], array[array.count-1]) 22 | } 23 | 24 | let (firstString, lastString) = firstLast(array: ["pineapple", "cherry", "steam locomotive"]) 25 | print(firstString) // "pineapple" 26 | print(lastString) // "steam locomotive" 27 | 28 | struct Waffle { 29 | let size: String 30 | } 31 | 32 | let (firstWaffle, lastWaffle) = firstLast(array: [ 33 | Waffle(size: "large"), 34 | Waffle(size: "extra-large"), 35 | Waffle(size: "snack-size") 36 | ]) 37 | 38 | print(firstWaffle) // Waffle(size: "large") 39 | print(lastWaffle) // Waffle(size: "snack-size") 40 | 41 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 42 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Invariance.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 2 | 3 | import Foundation 4 | 5 | class OnlineCourse { 6 | func start() { 7 | print("Starting online course.") 8 | } 9 | } 10 | class SwiftOnTheServer: OnlineCourse { 11 | override func start() { 12 | print("Starting Swift course.") 13 | } 14 | } 15 | 16 | //: Subclassing showcases subtyping 17 | 18 | var swiftCourse: SwiftOnTheServer = SwiftOnTheServer() 19 | var course: OnlineCourse = swiftCourse // is allowed 20 | course.start() // Starting Swift course. Type is still OnlineCourse. 21 | 22 | //: Our generic types are invariant 23 | 24 | struct Container {} 25 | 26 | var containerSwiftCourse: Container = Container() 27 | //var containerOnlineCourse: Container = containerSwiftCourse // error: cannot convert value of type 'Container' to specified type 'Container' 28 | 29 | struct Cache { 30 | // methods omitted 31 | } 32 | func refreshCache(_ cache: Cache) { 33 | 34 | } 35 | refreshCache(Cache()) // is allowed 36 | //refreshCache(Cache()) // error: cannot convert value of type 'Cache' to expected argument type 'Cache' 37 | 38 | 39 | //: Swift types are covariant 40 | 41 | func readOptionalCourse(_ value: Optional) { 42 | // ... snip 43 | } 44 | 45 | readOptionalCourse(OnlineCourse()) // is allowed 46 | readOptionalCourse(SwiftOnTheServer()) // is allowed, Optional has special privileges 47 | 48 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) 49 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Multiple constraints.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Multiple constraints 6 | //: We can try the multiple constraints on two ways 7 | 8 | //func lowestOccurrences(values: [T]) -> [T: Int] { 9 | // // ... snip 10 | //} 11 | 12 | //: But, a where clause is just the same. 13 | func lowestOccurrences(values: [T]) -> [T: Int] 14 | where T: Comparable & Hashable { 15 | // ... snip 16 | return [:] 17 | } 18 | 19 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Pair with conditional conformance.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Pair type with conditional conformance 6 | 7 | struct Pair: Hashable { 8 | let left: T 9 | let right: U 10 | 11 | init(_ left: T, _ right: U) { 12 | self.left = left 13 | self.right = right 14 | } 15 | 16 | } 17 | 18 | let pair = Pair(10, 20) 19 | print(pair.hashValue) //5280472796840031924 20 | 21 | let set: Set = [ 22 | Pair("Laurel", "Hardy"), 23 | Pair("Harry", "Lloyd") 24 | ] 25 | 26 | 27 | 28 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 29 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Pair with manual Hashable implementation.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Pair with manual Hashable implementation 6 | 7 | struct Pair: Hashable { 8 | 9 | let left: T 10 | let right: U 11 | 12 | init(_ left: T, _ right: U) { 13 | self.left = left 14 | self.right = right 15 | } 16 | 17 | func hash(into hasher: inout Hasher) { 18 | hasher.combine(left) 19 | hasher.combine(right) 20 | } 21 | 22 | static func ==(lhs: Pair, rhs: Pair) -> Bool { 23 | return lhs.left == rhs.left && lhs.right == rhs.right 24 | } 25 | 26 | } 27 | 28 | let pair = Pair("Madonna", "Cher") 29 | 30 | var hasher = Hasher() 31 | hasher.combine(pair) 32 | // alternatively: pair.hash(into: &hasher) 33 | let hash = hasher.finalize() 34 | print(hash) // 4922525492756211419 35 | 36 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 37 | 38 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Generics 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Generic function](Generic%20function) 25 | 1. [Generic constrained function](Generic%20constrained%20function) 26 | 1. [Conforming to Comparable](Conforming%20to%20Comparable) 27 | 1. [Multiple constraints](Multiple%20constraints) 28 | 1. [Pair with conditional conformance](Pair%20with%20conditional%20conformance) 29 | 1. [Pair with manual Hashable implementation](Pair%20with%20manual%20Hashable%20implementation) 30 | 1. [Invariance](Invariance) 31 | 1. [Exercises and answers](Exercises%20and%20answers) 32 | */ 33 | //: [Next](@next) 34 | 35 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch07-generics/Generics.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch08-protocols/Putting the pro in protocol-oriented programming.playground/Pages/A portfolio without generics.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # A portfolio without generics 4 | 5 | import Foundation 6 | 7 | protocol CryptoCurrency { 8 | var name: String { get } 9 | var symbol: String { get } 10 | var holdings: Double { get set } 11 | var price: NSDecimalNumber? { get set } 12 | } 13 | 14 | struct Bitcoin: CryptoCurrency { 15 | let name = "Bitcoin" 16 | let symbol = "BTC" 17 | var holdings: Double 18 | var price: NSDecimalNumber? 19 | } 20 | 21 | struct Ethereum: CryptoCurrency { 22 | let name = "Ethereum" 23 | let symbol = "ETH" 24 | var holdings: Double 25 | var price: NSDecimalNumber? 26 | } 27 | 28 | final class Portfolio { 29 | var coins: [CryptoCurrency] 30 | 31 | init(coins: [CryptoCurrency]) { 32 | self.coins = coins 33 | } 34 | 35 | func addCoin(_ newCoin: CryptoCurrency) { 36 | coins.append(newCoin) 37 | } 38 | 39 | // Helper functions as an example. E.g. we could calculate total value. 40 | func calculateSum() -> NSDecimalNumber { 41 | var value = NSDecimalNumber(value: 0) 42 | for coin in coins { 43 | let amount = coin.holdings * (coin.price?.doubleValue ?? 0) 44 | value = value.adding(NSDecimalNumber(value: amount)) 45 | } 46 | return value 47 | } 48 | } 49 | 50 | //: No need to specify what goes inside of portfolio. 51 | let portfolio = Portfolio(coins: []) 52 | 53 | //: Now we can mix coins. 54 | let coins: [CryptoCurrency] = [ 55 | Ethereum(holdings: 4, price: NSDecimalNumber(value: 500)), 56 | Bitcoin(holdings: 4, price: NSDecimalNumber(value: 6000)) 57 | ] 58 | 59 | portfolio.coins = coins 60 | 61 | print(type(of: portfolio)) // Portfolio 62 | let retrievedCoins = portfolio.coins 63 | print(type(of: retrievedCoins)) // Array 64 | 65 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 66 | 67 | -------------------------------------------------------------------------------- /ch08-protocols/Putting the pro in protocol-oriented programming.playground/Pages/Building a crypto portfolio.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Building a crypto portfolio 4 | 5 | import Foundation 6 | 7 | protocol CryptoCurrency { 8 | var name: String { get } 9 | var symbol: String { get } 10 | var holdings: Double { get set } 11 | var price: NSDecimalNumber? { get set } 12 | } 13 | 14 | struct Bitcoin: CryptoCurrency { 15 | let name = "Bitcoin" 16 | let symbol = "BTC" 17 | var holdings: Double 18 | var price: NSDecimalNumber? 19 | } 20 | 21 | struct Ethereum: CryptoCurrency { 22 | let name = "Ethereum" 23 | let symbol = "ETH" 24 | var holdings: Double 25 | var price: NSDecimalNumber? 26 | } 27 | 28 | final class Portfolio { 29 | var coins: [Coin] 30 | 31 | init(coins: [Coin]) { 32 | self.coins = coins 33 | } 34 | 35 | func addCoin(_ newCoin: Coin) { 36 | coins.append(newCoin) 37 | } 38 | 39 | func calculateSum() -> NSDecimalNumber { 40 | var value = NSDecimalNumber(value: 0) 41 | for coin in coins { 42 | let amount = coin.holdings * (coin.price?.doubleValue ?? 0) 43 | value = value.adding(NSDecimalNumber(value: amount)) 44 | } 45 | return value 46 | } 47 | } 48 | 49 | let coins = [ 50 | Ethereum(holdings: 4, price: NSDecimalNumber(value: 500)), 51 | // Bitcoin(holdings: 4, price: NSDecimalNumber(value: 6000)) // We can't mix 52 | ] 53 | let portfolio = Portfolio(coins: coins) 54 | 55 | //portfolio.addCoin(Bitcoin()) 56 | // error: cannot convert value of type 'Bitcoin' to expected argument type 'Ethereum' 57 | print(type(of: portfolio.coins)) // Array 58 | 59 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 60 | -------------------------------------------------------------------------------- /ch08-protocols/Putting the pro in protocol-oriented programming.playground/Pages/Runtime vs compiletime.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Runtime vs compiletime 4 | 5 | import Foundation 6 | 7 | protocol CryptoCurrency { 8 | var name: String { get } 9 | var symbol: String { get } 10 | var holdings: Double { get set } 11 | var price: NSDecimalNumber? { get set } 12 | } 13 | 14 | struct Bitcoin: CryptoCurrency { 15 | let name = "Bitcoin" 16 | let symbol = "BTC" 17 | var holdings: Double 18 | var price: NSDecimalNumber? 19 | } 20 | 21 | struct Ethereum: CryptoCurrency { 22 | let name = "Ethereum" 23 | let symbol = "ETH" 24 | var holdings: Double 25 | var price: NSDecimalNumber? 26 | } 27 | 28 | func retrievePriceRunTime(coin: CryptoCurrency, completion: ((CryptoCurrency) -> Void) ) { 29 | // ... snip. Server returns coin with most-recent price. 30 | var copy = coin 31 | copy.price = 6000 32 | completion(copy) 33 | } 34 | 35 | 36 | func retrievePriceCompileTime(coin: Coin, completion: ((Coin) -> Void)) { 37 | // ... snip. Server returns coin with most-recent price. 38 | var copy = coin 39 | copy.price = 6000 40 | completion(copy) 41 | } 42 | 43 | let btc = Bitcoin(holdings: 3, price: nil) 44 | retrievePriceRunTime(coin: btc) { (updatedCoin: CryptoCurrency) in 45 | print("Updated value runtime is \(updatedCoin.price?.doubleValue ?? 0)") 46 | } 47 | 48 | retrievePriceCompileTime(coin: btc) { (updatedCoin: Bitcoin) in 49 | print("Updated value compiletime is \(updatedCoin.price?.doubleValue ?? 0)") 50 | } 51 | 52 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 53 | -------------------------------------------------------------------------------- /ch08-protocols/Putting the pro in protocol-oriented programming.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Putting the pro in protocol-oriented programming 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Building a crypto portfolio](Building%20a%20crypto%20portfolio) 25 | 1. [A portfolio without generics](A%20portfolio%20without%20generics) 26 | 1. [Runtime vs compiletime](Runtime%20vs%20compiletime) 27 | 1. [Passing protocols with associated types](Passing%20protocols%20with%20associated%20types) 28 | 1. [Exercises](Exercises) 29 | 1. [Answers](Answers) 30 | 31 | */ 32 | //: [Next](@next) 33 | 34 | -------------------------------------------------------------------------------- /ch08-protocols/Putting the pro in protocol-oriented programming.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ch08-protocols/Putting the pro in protocol-oriented programming.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch08-protocols/Putting the pro in protocol-oriented programming.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch09-iterators-sequences-collections/iterators_sequences_collections.playground/Pages/IteratorProtocol.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # IteratorProtocol 4 | 5 | let cheeses = ["Gouda", "Camembert", "Brie"] 6 | 7 | for cheese in cheeses { 8 | print(cheese) 9 | } 10 | 11 | var cheeseIterator = cheeses.makeIterator() 12 | 13 | while let cheese = cheeseIterator.next() { 14 | print(cheese) 15 | } 16 | 17 | // Prints: 18 | //"Gouda" 19 | //"Camembert" 20 | //"Brie" 21 | 22 | // 23 | //for element in values { 24 | // // do something with character 25 | //} 26 | 27 | // Under the hood 28 | 29 | // Taking a closer look at iterator 30 | 31 | let groceries = ["Flour", "Eggs", "Sugar"] 32 | var groceriesIterator: IndexingIterator<[String]> = groceries.makeIterator() 33 | print(groceriesIterator.next()) // Optional("Flour") 34 | print(groceriesIterator.next()) // Optional("Eggs") 35 | print(groceriesIterator.next()) // Optional("Sugar") 36 | print(groceriesIterator.next()) // nil 37 | print(groceriesIterator.next()) // nil 38 | 39 | //print(iterator.next()) // Optional("a") 40 | //print(iterator.next()) // Optional("b") 41 | //print(iterator.next()) // Optional("c") 42 | //print(iterator.next()) // nil 43 | //print(iterator.next()) // nil 44 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 45 | -------------------------------------------------------------------------------- /ch09-iterators-sequences-collections/iterators_sequences_collections.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Iterators, Sequences, and Collections 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [IteratorProtocol](IteratorProtocol) 25 | 1. [Useful methods on Sequence](Useful%20methods%20on%20Sequence) 26 | 1. [The Bag datatype](The%20Bag%20datatype) 27 | 1. [Exploring collection types](Exploring%20collection%20types) 28 | 1. [Implementing Collection](Implementing%20Collection) 29 | 1. [Exercises and Answers](Exercises%20and%20Answers) 30 | 31 | */ 32 | //: [Next](@next) 33 | -------------------------------------------------------------------------------- /ch09-iterators-sequences-collections/iterators_sequences_collections.playground/Pages/Useful methods on Sequence.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | 5 | //: # Useful methods on Sequence 6 | 7 | //: ## forEach 8 | 9 | ["file_one.txt", "file_two.txt"].forEach { path in 10 | deleteFile(path: path) 11 | } 12 | 13 | ["file_one.txt", "file_two.txt"].forEach(deleteFile) 14 | 15 | func deleteFile(path: String) { 16 | // deleting file .... 17 | } 18 | 19 | //: ## enumerated 20 | 21 | ["First line", "Second line", "Third line"] 22 | .enumerated() 23 | .forEach { (index: Int, element: String) in 24 | print("\(index+1): \(element)") 25 | } 26 | 27 | //: ## Lazy 28 | 29 | let bigRange = 0.. Bool in 32 | return int % 2 == 0 33 | } 34 | 35 | // We want the last three elements. But still, nothing happens yet 36 | let lastThree = filtered.suffix(3) 37 | 38 | // Now the lazy code is evaluated 39 | for value in lastThree { 40 | print(value) // values are consumed 41 | } 42 | 43 | //: ## Reduce 44 | 45 | let text = "It's hard to come up with fresh exercises.\nOver and over again.'\nAnd again." 46 | let startValue = 0 47 | let numberOfLineBreaks = text.reduce(startValue) { (accumulation: Int, char: Character) in 48 | if char == "\n" { 49 | return accumulation + 1 50 | } else { 51 | return accumulation 52 | } 53 | } 54 | 55 | print(numberOfLineBreaks) // 2 56 | 57 | //: ## Reduce into 58 | 59 | let grades = [3.2, 4.2, 2.6, 4.1] 60 | let slowResults = grades.reduce([:]) { (results: [Character: Int], grade: Double) in 61 | var copy = results 62 | switch grade { 63 | case 1..<2: copy["D", default: 0] += 1 64 | case 2..<3: copy["C", default: 0] += 1 65 | case 3..<4: copy["B", default: 0] += 1 66 | case 4...: copy["A", default: 0] += 1 67 | default: break 68 | } 69 | 70 | return copy 71 | } 72 | 73 | print(slowResults) // ["C": 1, "B": 1, "A": 2] 74 | 75 | let results = grades.reduce(into: [:]) { (results: inout [Character: Int], grade: Double) in 76 | switch grade { 77 | case 1..<2: results["D", default: 0] += 1 78 | case 2..<3: results["C", default: 0] += 1 79 | case 3..<4: results["B", default: 0] += 1 80 | case 4...: results["A", default: 0] += 1 81 | default: break 82 | } 83 | } 84 | 85 | print(results) // ["C": 1, "B": 1, "A": 2] 86 | 87 | //: ## zip 88 | for (integer, string) in zip(0..<10, ["a", "b", "c"]) { 89 | print("\(integer): \(string)") 90 | } 91 | // Output: 92 | // 0: a 93 | // 1: b 94 | // 2: c 95 | 96 | 97 | //: [Next](@next) 98 | -------------------------------------------------------------------------------- /ch09-iterators-sequences-collections/iterators_sequences_collections.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ch09-iterators-sequences-collections/iterators_sequences_collections.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch09-iterators-sequences-collections/iterators_sequences_collections.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/Map is an abstraction.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | 6 | /// Removed emoji from a string 7 | /// 8 | /// - Parameter string: a target string to filter emoji's from 9 | /// - Returns: The string without emoji's 10 | func removeEmojis(_ string: String) -> String { 11 | var scalars = string.unicodeScalars 12 | scalars.removeAll(where: { $0.properties.isEmoji }) 13 | return String(scalars) 14 | } 15 | //: Map works on multiple types 16 | 17 | let omgBabies: String? = "❤️ OMG Cute ⭐️⭐️babypics⭐️⭐️! 😍❤️🍼👶" 18 | print(omgBabies.map(removeEmojis)) // Optional(" OMG Cute babypics! ") 19 | 20 | let food = ["Favorite Meal": "🍕 Pizza", "Favorite Drink": "☕️ Coffee"] 21 | print(food.mapValues(removeEmojis)) // ["Favorite Meal": " Pizza", "Favorite Drink": " Coffee"] 22 | 23 | let set: Set = ["Great job 👍🏻", "Excellent 🙌"] 24 | print(set.map(removeEmojis)) // ["Great job ", "Great job "] 25 | 26 | // let 27 | //.mapValues(removeEmojis) 28 | //[1000: "Some value"].mapValues(removeEmojis) 29 | 30 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 31 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/Mapping over optionals.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | import UIKit 5 | 6 | //: # Mapping over optionals 7 | 8 | //: Removing emojis from a string. 9 | 10 | /// Removed emoji from a string 11 | /// 12 | /// - Parameter string: a target string to filter emoji's from 13 | /// - Returns: The string without emoji's 14 | func removeEmojis(_ string: String) -> String { 15 | var scalars = string.unicodeScalars 16 | scalars.removeAll(where: { $0.properties.isEmoji }) 17 | return String(scalars) 18 | } 19 | 20 | //: ## Cover without map 21 | 22 | class Cover { 23 | let image: UIImage 24 | let title: String? 25 | 26 | init(image: UIImage, title: String?) { 27 | self.image = image 28 | 29 | var cleanedTitle: String? = nil 30 | if let title = title { 31 | cleanedTitle = removeEmojis(title) 32 | } 33 | self.title = cleanedTitle 34 | } 35 | } 36 | 37 | //: ## Cover with elaborate map 38 | 39 | //struct Cover { 40 | // let image: UIImage 41 | // let title: String? 42 | // 43 | // init(image: UIImage, title: String?) { 44 | // self.image = image 45 | // 46 | // self.title = title.map { (string: String) -> String in 47 | // return removeEmojis(string) 48 | // } 49 | // } 50 | //} 51 | 52 | //: ## Cover with short map 53 | 54 | //struct Cover { 55 | // let image: UIImage 56 | // let title: String? 57 | // 58 | // init(image: UIImage, title: String?) { 59 | // self.image = image 60 | // self.title = title.map(removeEmojis) 61 | // } 62 | //} 63 | 64 | //: ## Cover with two mapping operations chained 65 | 66 | //struct Cover { 67 | // let image: UIImage 68 | // let title: String? 69 | // 70 | // init(image: UIImage, title: String?) { 71 | // self.image = image 72 | // self.title = 73 | // title 74 | // .map(removeEmojis) 75 | // .map { $0.trimmingCharacters(in: .whitespaces) } 76 | // } 77 | //} 78 | // 79 | let image = UIImage() 80 | let cover = Cover(image: image, title: "❤️ OMG Cute ⭐️⭐️babypics⭐️⭐️! 😍❤️🍼👶") 81 | print(cover.title) // Optional("OMG Cute babypics!") 82 | 83 | print(removeEmojis("Hi💩😬🎉🐑🚙✋😇😴🚁🛀")) // "Hi" 84 | 85 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 86 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/Mapping over sequences.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Mapping over sequences 6 | 7 | let names = [ 8 | "John", 9 | "Mary", 10 | "Elizabeth" 11 | ] 12 | let nameCount = names.count 13 | 14 | let generatedNames = (0..<5).map { index in 15 | return names[index % nameCount] 16 | } 17 | 18 | print(generatedNames) // ["John", "Mary", "Elizabeth", "John", "Mary"] 19 | 20 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 21 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/Optional flatMap.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Optional flatMap 6 | 7 | //: ## Double-nested optional 8 | //: When mapping over an string to turn it into a url, we end up with a double-nested optional. 9 | 10 | // 11 | //let receivedData = ["url" : "https://www.clubpenguinisland.com"] 12 | // 13 | //let path: String? = receivedData["url"] 14 | // 15 | //let url = path.map { (string: String) -> URL? in 16 | // let url = URL(string: string) // Optional(https://www.clubpenguinisland.com) 17 | // return url // We return an optional string 18 | //} 19 | // 20 | //print(url) // Optional(Optional(http://www.clubpenguinisland.com)) 21 | 22 | //: ## Removing double-nesting with a force unwrap 23 | //: When mapping over an string to turn it into a url, we end up with a double-nested optional. We can prevent this with a force unwrap, but doing this is crash-sensitive! 24 | // 25 | //let receivedData = ["url" : "https://www.clubpenguinisland.com"] 26 | // 27 | //let path: String? = receivedData["url"] 28 | // 29 | //let url = path.map { (string: String) -> URL in 30 | // return URL(string: string)! // We force unwrap, dangerous! 31 | //} 32 | // 33 | //print(url) // Optional(http://www.clubpenguinisland.com) 34 | 35 | //: ## Removing double-nesting with flatMap 36 | //: We can safely remove double-nested optionals with flatMap 37 | 38 | let receivedData = ["url" : "https://www.clubpenguinisland.com"] 39 | 40 | let path: String? = receivedData["url"] 41 | 42 | let url = path.flatMap { (string: String) -> URL? in // We return URL? again 43 | return URL(string: string) // We return an optional URL 44 | } 45 | 46 | print(url) // Optional(http://www.clubpenguinisland.com) // The optional is not double-nested 47 | 48 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 49 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/Pyramid of doom.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Pyramid of doom 6 | 7 | func half(_ int: Int) -> Int? { // Only half even numbers 8 | guard int % 2 == 0 else { return nil } 9 | return int / 2 10 | } 11 | print(half(4)) // Optional(2) 12 | print(half(5)) // nil 13 | 14 | var value: Int? = nil 15 | let startValue = 80 16 | 17 | //: We can continuously unwrap optional operations, ending up with a pyramid of domo. 18 | 19 | if let halvedValue = half(startValue) { 20 | print(halvedValue) // 40 21 | value = halvedValue 22 | 23 | if let halvedValue = half(halvedValue) { 24 | print(halvedValue) // 20 25 | value = halvedValue 26 | 27 | if let halvedValue = half(halvedValue) { 28 | print(halvedValue) // 10 29 | if let halvedValue = half(halvedValue) { 30 | value = halvedValue 31 | } else { 32 | value = nil 33 | } 34 | 35 | } else { 36 | value = nil 37 | } 38 | } else { 39 | value = nil 40 | } 41 | } 42 | 43 | print(value) // Optional(5) 44 | 45 | //: A pyramid of doom can be prevented when stacking if let unwraps. 46 | 47 | var endValue: Int? = nil 48 | 49 | if 50 | let firstHalf = half(startValue), 51 | let secondHalf = half(firstHalf), 52 | let thirdHalf = half(secondHalf), 53 | let fourthHalf = half(thirdHalf) { 54 | endValue = fourthHalf 55 | } 56 | print(endValue) // Optional(5) 57 | 58 | //: But, alternatively we can use flatMap, which is useful when we don't have clean one-liner functions to use in if-let statements. Also we don't have to bind new constants. 59 | 60 | let endResult = 61 | half(startValue) 62 | .flatMap(half) 63 | .flatMap(half) 64 | .flatMap(half) 65 | 66 | print(endResult) // Optional(5) 67 | 68 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 69 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Map, flatMap, and compactMap 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | Press command + 1 to see all contents. 8 | 9 | Go to Editor > Show Rendered Markup, for a readable presentation. 10 | 11 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 12 | 13 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 14 | 15 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 16 | 17 | Published by [Manning][Manning] 18 | 19 | [Manning]: https://www.manning.com 20 | 21 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 22 | 23 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 24 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 25 | 26 | **Written in Swift 5.0 using Xcode 11.3.1*** 27 | 28 | ## Table of Contents 29 | 30 | 1. [Mapping over collections](Mapping%20over%20collections) 31 | 1. [Mapping over sequences](Mapping%20over%20sequences) 32 | 1. [Mapping over optionals](Mapping%20over%20optionals) 33 | 1. [Optional flatMap](Optional%20flatMap) 34 | 1. [Map is an abstraction](Map%20is%20an%20abstraction) 35 | 1. [Pyramid of doom](Pyramid%20of%20doom) 36 | 1. [flatMapping over collections](flatMapping%20over%20collections) 37 | 1. [compactMap](compactMap) 38 | 1. [Exercises and answers](Exercises%20and%20answers) 39 | */ 40 | //: [Next](@next) 41 | 42 | 43 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/compactMap.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # compactMap 6 | 7 | 8 | let wrongUrl = URL(string: "OMG SHOES") 9 | print(wrongUrl) // nil 10 | let properUrl = URL(string: "https://www.swift.org") 11 | print(properUrl) // Optional(https://www.swift.org) 12 | 13 | let strings = [ 14 | "https://www.duckduckgo.com", 15 | "https://www.twitter.com", 16 | "OMG SHOES", 17 | "https://www.swift.org" 18 | ] 19 | 20 | //: Notice how compactMap filters out "OMG SHOES", and no values are optional 21 | //let optionalUrls = strings.map { URL(string: $0) } 22 | //print(optionalUrls) // [Optional(https://www.duckduckgo.com), Optional(https://www.twitter.com), nil, Optional(https://www.swift.org)] 23 | let urls = strings.compactMap(URL.init) 24 | print(urls) // [https://www.duckduckgo.com, https://www.twitter.com, https://www.swift.org] 25 | 26 | //: If you don't like compactMap, you can use a for loop 27 | 28 | let optionalUrls: [URL?] = [ 29 | URL(string: "https://www.duckduckgo.com"), 30 | URL(string: "Bananaphone"), 31 | URL(string: "https://www.twitter.com"), 32 | URL(string: "https://www.swift.org") 33 | ] 34 | for case let url? in optionalUrls { 35 | print("The url is \(url)") // url is unwrapped here 36 | } 37 | 38 | //: flatMap can be either chained or nested. 39 | func half(_ int: Int) -> Int? { // Only half even numbers 40 | guard int % 2 == 0 else { return nil } 41 | return int / 2 42 | } 43 | 44 | let value = Optional(40) 45 | let lhs = value.flatMap(half).flatMap(half) 46 | let rhs = value.flatMap { int in half(int).flatMap(half) } 47 | lhs == rhs // true 48 | print(rhs) // Optional(10) 49 | 50 | //: You can nest flatMaps for special operations. 51 | 52 | let string = "abc" 53 | let results = string.flatMap { a -> [(Character, Character)] in 54 | string.compactMap { b -> (Character, Character)? in 55 | if a == b { 56 | return nil 57 | } else { 58 | return (a, b) 59 | } 60 | } 61 | } 62 | print(results) // [("a", "b"), ("a", "c"), ("b", "a"), ("b", "c"), ("c", "a"), ("c", "b")] 63 | 64 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 65 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/Pages/flatMapping over collections.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # flatMapping over collections 6 | 7 | //: We can create a new array inside flatMap, which will instantly be flattened. 8 | 9 | let repeated = [2, 3].flatMap { (value: Int) -> [Int] in 10 | return [value, value] 11 | } 12 | 13 | print(repeated) // [2, 2, 3, 3] 14 | 15 | //: We can flatten an existed nested array with flatMap, if we don't perform any actions inside the flatMap closure. 16 | 17 | let stringsArr = [["I", "just"], ["want", "to"], ["learn", "about"], ["protocols"]] 18 | let flattenedArray = stringsArr.flatMap { $0 } 19 | print(flattenedArray) // ["I", "just", "want", "to", "learn", "about", "protocols"] 20 | 21 | 22 | //: Strings are collections, which can also be flatMapped. 23 | 24 | extension String { 25 | func interspersed(_ element: Character) -> String { 26 | let characters = self.flatMap { return [$0, element] }.dropLast() 27 | return String(characters) 28 | } 29 | } 30 | 31 | let interspersedString = "Swift".interspersed("-") 32 | print(interspersedString) // S-w-i-f-t 33 | 34 | 35 | //: You can combine all values of two sequences with flatMap and map. 36 | 37 | let suits = ["Hearts", "Clubs", "Diamonds", "Spades"] 38 | let faces = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"] 39 | 40 | var deckOfCards = suits.flatMap { suit in 41 | faces.map { face in 42 | (suit, face) 43 | } 44 | } 45 | deckOfCards.shuffle() 46 | print(deckOfCards) // [("Diamonds", "5"), ("Hearts", "8"), ("Hearts", "K"), ("Clubs", "3"), ("Diamonds", "10"), ("Spades", "A"), ... 47 | 48 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 49 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch10-mapflatmap/Map flatMap compactMap.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/Error recovery with Result.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Error recovery with Result 4 | 5 | import Foundation 6 | import PlaygroundSupport 7 | 8 | struct Account: Equatable { 9 | // Empty for educational purposes 10 | } 11 | 12 | enum PaymentError: Error { 13 | case amountTooLow 14 | } 15 | 16 | enum AccountError: Error { 17 | case invalidAccount 18 | } 19 | 20 | enum MoneyError: Error { 21 | case transactionFailed 22 | } 23 | 24 | final class MoneyApi { 25 | func transfer(amount: Int, from: Account, to: Account) throws { 26 | throw MoneyError.transactionFailed 27 | } 28 | } 29 | 30 | let moneyAPI = MoneyApi() 31 | 32 | func processPayment(fromAccount: Account, toAccount: Account, 33 | amountInCents:Int) -> Result<(), Error> { 34 | 35 | guard amountInCents > 0 else { 36 | return Result.failure(PaymentError.amountTooLow) 37 | } 38 | 39 | guard fromAccount != toAccount else { 40 | return Result.failure(AccountError.invalidAccount) 41 | } 42 | 43 | do { 44 | try moneyAPI.transfer(amount: amountInCents, from: fromAccount, to: 45 | toAccount) 46 | return Result.success(()) 47 | } catch { 48 | return Result.failure(error) 49 | } 50 | 51 | } 52 | 53 | processPayment(fromAccount: Account(), toAccount: Account(), amountInCents: -20) // amountTooLow 54 | processPayment(fromAccount: Account(), toAccount: Account(), amountInCents: 20) // invalidAccount 55 | 56 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/Mixing Result with throwing functions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Mixing Result with throwing functions 4 | 5 | import Foundation 6 | import PlaygroundSupport 7 | 8 | struct Account: Equatable { 9 | // Empty for educational purposes 10 | } 11 | 12 | enum PaymentError: Error { 13 | case amountTooLow 14 | } 15 | 16 | enum AccountError: Error { 17 | case invalidAccount 18 | } 19 | 20 | enum MoneyError: Error { 21 | case transactionFailed 22 | } 23 | 24 | final class MoneyApi { 25 | func transfer(amount: Int, from: Account, to: Account) throws { 26 | throw MoneyError.transactionFailed 27 | } 28 | } 29 | 30 | let moneyAPI = MoneyApi() 31 | 32 | func processPayment(fromAccount: Account, toAccount: Account, 33 | amountInCents:Int) -> Result<(), Error> { 34 | 35 | guard amountInCents > 0 else { 36 | return Result.failure(PaymentError.amountTooLow) 37 | } 38 | 39 | guard fromAccount != toAccount else { 40 | return Result.failure(AccountError.invalidAccount) 41 | } 42 | 43 | // This is changed to the Result initializer 44 | return Result(catching: { 45 | try moneyAPI.transfer(amount: amountInCents, from: fromAccount, to: 46 | toAccount) 47 | }) 48 | } 49 | 50 | processPayment(fromAccount: Account(), toAccount: Account(), amountInCents: -20) // amountTooLow 51 | processPayment(fromAccount: Account(), toAccount: Account(), amountInCents: 20) // invalidAccount 52 | func logPaymentResult(_ result: Result) { 53 | do { 54 | let string = try result.get() 55 | print("Payment succeeded: \(string)") 56 | } catch { 57 | print("Received error: \(error)") 58 | } 59 | } 60 | 61 | // Will print: "Received error: amountTooLow 62 | logPaymentResult(.failure(PaymentError.amountTooLow)) 63 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/Mixing Result with throwing functions.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/Never.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | 5 | 6 | protocol Service { 7 | associatedtype Value 8 | associatedtype Err: Error 9 | func load(complete: @escaping (Result) -> Void) 10 | } 11 | 12 | struct Subscription {} 13 | /* 14 | enum BogusError: Error {} 15 | 16 | final class SubscriptionsLoader: Service { 17 | func load(complete: @escaping (Result<[Subscription], BogusError>) -> Void) { 18 | // ... load data. Always succeeds 19 | let subscriptions = [Subscription(), Subscription()] 20 | complete(Result(subscriptions)) 21 | } 22 | } 23 | 24 | let subscriptionsLoader = SubscriptionsLoader() 25 | subscriptionsLoader.load { (result: Result<[Subscription], BogusError>) in 26 | switch result { 27 | case .success(let subscriptions): print(subscriptions) 28 | // We don't need .failure 29 | } 30 | } 31 | */ 32 | //: Using the Never type 33 | 34 | final class SubscriptionsLoader: Service { 35 | func load(complete: @escaping (Result<[Subscription], Never>) -> Void) { 36 | // ... load data. Always succeeds 37 | let subscriptions = [Subscription(), Subscription()] 38 | complete(Result.success(subscriptions)) 39 | } 40 | } 41 | 42 | let subscriptionsLoader = SubscriptionsLoader() 43 | subscriptionsLoader.load { (result: Result<[Subscription], Never>) in 44 | switch result { 45 | case .success(let subscriptions): print(subscriptions) 46 | // We don't need .failure 47 | } 48 | } 49 | 50 | //: [Next](@next) 51 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Asynchronous error handling with Result 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [iTunes api cocoa touch style](iTunes%20api%20cocoa%20touch%20style) 25 | 1. [iTunes api with Result](iTunes%20api%20with%20Result) 26 | 1. [iTunes api convert to Result](iTunes%20api%20convert%20to%20Result) 27 | 1. [iTunes api add search](iTunes%20api%20add%20search) 28 | 1. [iTunes api mapping](iTunes%20api%20mapping) 29 | 1. [iTunes api flatMap](iTunes%20api%20flatMap) 30 | 1. [Returning multiple error types](Returning%20multiple%20error%20types) 31 | 1. [Mixing Result with throwing functions](Mixing%20Result%20with%20throwing%20functions) 32 | 1. [Error recovery with Result](Error%20recovery%20with%20Result) 33 | 1. [Never](Never) 34 | 1. [Exercises 1 2 and 3](Exercises%201%202%20and%203) 35 | 1. [Exercise 4](Exercise%204) 36 | 37 | */ 38 | //: [Next](@next) 39 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/iTunes api cocoa touch style.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # iTunes api step 1: Cocoa Touch style 4 | 5 | import Foundation 6 | import PlaygroundSupport 7 | 8 | PlaygroundPage.current.needsIndefiniteExecution = true 9 | 10 | func callURL(with url: URL, completionHandler: @escaping (Data?, Error?) 11 | -> Void) { 12 | let task = URLSession.shared.dataTask(with: url, completionHandler: 13 | { (data, response, error) -> Void in 14 | completionHandler(data, error) 15 | }) 16 | 17 | task.resume() 18 | } 19 | 20 | let url = URL(string: "https://itunes.apple.com/search?term=iron%20man")! 21 | 22 | callURL(with: url) { (data, error) in 23 | if let error = error { 24 | print(error) 25 | } else if let data = data { 26 | let value = String(data: data, encoding: .utf8) 27 | print(value) 28 | } else { 29 | // What goes here? 30 | } 31 | } 32 | 33 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 34 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/iTunes api convert to Result.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # iTunes api step 2: call url with Result 4 | 5 | import Foundation 6 | import PlaygroundSupport 7 | 8 | PlaygroundPage.current.needsIndefiniteExecution = true 9 | 10 | extension Swift.Result { 11 | // ... snip 12 | 13 | init(value: Success?, error: Failure?) { 14 | if let error = error { 15 | self = .failure(error) 16 | } else if let value = value { 17 | self = .success(value) 18 | } else { 19 | fatalError("Could not create Result") 20 | } 21 | } 22 | } 23 | 24 | enum NetworkError: Error { 25 | case fetchFailed(Error) 26 | } 27 | 28 | let url = URL(string: "https://itunes.apple.com/search?term=iron%20man")! 29 | 30 | func callURL(with url: URL, completionHandler: @escaping (Result) -> Void) { 32 | let task = URLSession.shared.dataTask(with: url, completionHandler: 33 | { (data, response, error) -> Void in 34 | let dataTaskError = error.map { NetworkError.fetchFailed($0)} 35 | let result = Swift.Result(value: data, error: 36 | dataTaskError) 37 | completionHandler(result) 38 | }) 39 | 40 | task.resume() 41 | } 42 | 43 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 44 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/Pages/iTunes api with result.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # iTunes api: call url with Result 4 | 5 | import Foundation 6 | import PlaygroundSupport 7 | 8 | PlaygroundPage.current.needsIndefiniteExecution = true 9 | 10 | enum NetworkError: Error { 11 | case fetchFailed(Error) 12 | } 13 | 14 | func callURL(with url: URL, completionHandler: @escaping (Result) -> Void) { 16 | let task = URLSession.shared.dataTask(with: url, completionHandler: { 17 | (data, response, error) -> Void in 18 | // ... details will be filled in shortly 19 | }) 20 | 21 | task.resume() 22 | } 23 | 24 | let url = URL(string: "https://itunes.apple.com/search?term=iron%20man")! 25 | 26 | callURL(with: url) { (result: Result) in 27 | switch result { 28 | case .success(let data): 29 | let value = String(data: data, encoding: .utf8) 30 | print(value) 31 | case .failure(let error): 32 | print(error) 33 | } 34 | } 35 | 36 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 37 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch11-async-error-handling/ErrorHandlingResult.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/A mailer using protocol composition.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # A mailer using protocol composition 4 | 5 | import Foundation 6 | 7 | struct MailAdress { 8 | let value: String 9 | } 10 | 11 | struct Email { 12 | let subject: String 13 | let body: String 14 | let to: [MailAdress] 15 | let from: MailAdress 16 | } 17 | 18 | protocol Mailer { 19 | func send(email: Email) throws 20 | } 21 | 22 | extension Mailer { 23 | func send(email: Email) throws { 24 | // Connect to server 25 | // Submit email 26 | } 27 | } 28 | 29 | protocol MailValidator { 30 | func validate(email: Email) throws 31 | } 32 | 33 | extension MailValidator { 34 | func validate(email: Email) throws { 35 | // Check email address, and if subject is missing. 36 | } 37 | } 38 | 39 | extension MailValidator where Self: Mailer { 40 | func send(email: Email) throws { 41 | try validate(email: email) 42 | // Connect to server 43 | // Submit email 44 | print("Email validated and sent.") 45 | } 46 | 47 | //: We can add bonus methods. 48 | //: E.g. a delayed email which we need to validate beforehand. 49 | func send(email: Email, at: Date) throws { 50 | try validate(email: email) 51 | // Connect to server 52 | // Add email to delayed queued. 53 | print("Email validated and stored.") 54 | } 55 | } 56 | 57 | //: Now, SMTPClient gets a validating send method. 58 | //: The downside is that SMTPClient needs to adhere to two protocols. 59 | struct SMTPClient: Mailer, MailValidator { 60 | 61 | } 62 | 63 | let client = SMTPClient() 64 | let email = Email(subject: "Learn Swift", 65 | body: "Lorem ipsum", 66 | to: [MailAdress(value: "john@appleseed.com")], 67 | from: MailAdress(value: "stranger@somewhere.com")) 68 | 69 | try? client.send(email: email) // Email validated and sent. 70 | 71 | //: We also can get bonus methods that only unlock once a type adheres to both Mailer and MailValidator 72 | try? client.send(email: email, at: Date(timeIntervalSinceNow: 3600)) // Email validated and queued. 73 | 74 | func submitEmail(sender: T, email: Email) where T: Mailer, T: MailValidator { 75 | try? sender.send(email: email, at: Date(timeIntervalSinceNow: 3600)) 76 | } 77 | 78 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 79 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/A mailer using protocol inheritance.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # A mailer using protocol inheritance 4 | 5 | import Foundation 6 | 7 | struct MailAdress { 8 | let value: String 9 | } 10 | 11 | struct Email { 12 | let subject: String 13 | let body: String 14 | let to: [MailAdress] 15 | let from: MailAdress 16 | } 17 | 18 | protocol Mailer { 19 | func send(email: Email) throws 20 | } 21 | 22 | extension Mailer { 23 | func send(email: Email) throws { 24 | // Omitted: Connect to server 25 | // Omitted: Submit email 26 | } 27 | } 28 | 29 | protocol ValidatingMailer: Mailer { 30 | func send(email: Email) throws // Override send 31 | func validate(email: Email) throws 32 | } 33 | 34 | //: Now the send method is a throwing method that validates. 35 | extension ValidatingMailer { 36 | func send(email: Email) throws { 37 | try validate(email: email) 38 | // Connect to server 39 | // Submit email 40 | print("Email validated and sent.") 41 | } 42 | 43 | func validate(email: Email) throws { 44 | // Check email address, and if subject is missing. 45 | } 46 | } 47 | 48 | struct SMTPClient: ValidatingMailer { 49 | // Implementation omitted. 50 | } 51 | 52 | let client = SMTPClient() 53 | 54 | try? client.send(email: Email(subject: "Learn Swift", 55 | body: "Lorem ipsum", 56 | to: [MailAdress(value: "john@appleseed.com")], 57 | from: MailAdress(value: "stranger@somewhere.com"))) 58 | 59 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 60 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/Extending Sequence.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Extending Sequence 4 | 5 | import Foundation 6 | 7 | //: ## Inspect 8 | 9 | ["C", "B", "A", "D"] 10 | .sorted() 11 | .inspect { (string) in 12 | print("Inspecting: \(string)") 13 | }.filter { (string) -> Bool in 14 | string < "C" 15 | }.forEach { 16 | print("Result: \($0)") 17 | } 18 | 19 | // Output: 20 | // Inspecting: A 21 | // Inspecting: B 22 | // Inspecting: C 23 | // Inspecting: D 24 | // Result A 25 | // Result B 26 | 27 | extension Sequence { 28 | public func inspect( 29 | _ body: (Element) throws -> Void 30 | ) rethrows -> Self { 31 | for element in self { 32 | try body(element) 33 | } 34 | return self 35 | } 36 | } 37 | 38 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 39 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/Extending in two directions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Extending in two directions 4 | 5 | import UIKit 6 | 7 | protocol AnalyticsProtocol { 8 | func track(event: String, parameters: [String: Any]) 9 | } 10 | 11 | // Not like this: 12 | //extension UIViewController: AnalyticsProtocol { 13 | // func track(event: String, parameters: [String: Any]) { // ... snip } 14 | //} 15 | 16 | // But as follows: 17 | extension AnalyticsProtocol where Self: UIViewController { 18 | func track(event: String, parameters: [String: Any]) { 19 | print("Tracked \(event) and \(parameters)") 20 | } 21 | } 22 | 23 | final class NewsViewController: UIViewController, AnalyticsProtocol { 24 | 25 | override func viewDidAppear(_ animated: Bool) { 26 | super.viewDidAppear(animated) 27 | track(event: "News.appear", parameters: [:]) 28 | } 29 | } 30 | 31 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 32 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/Extending with associated types.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Extending with associated types 4 | 5 | import Foundation 6 | 7 | 8 | //: ## Extending Array 9 | 10 | extension Array where Element: Equatable { 11 | func unique() -> [Element] { 12 | var uniqueValues = [Element]() 13 | for element in self { 14 | if !uniqueValues.contains(element) { 15 | uniqueValues.append(element) 16 | } 17 | } 18 | return uniqueValues 19 | } 20 | } 21 | 22 | //: ## Extending Collection 23 | 24 | //: This time we're extending Collection instead of Array 25 | 26 | extension Collection where Element: Equatable { 27 | func unique() -> [Element] { 28 | var uniqueValues = [Element]() 29 | for element in self { 30 | if !uniqueValues.contains(element) { 31 | uniqueValues.append(element) 32 | } 33 | } 34 | return uniqueValues 35 | } 36 | } 37 | 38 | //: Trying out our extensions 39 | 40 | // Array still has unique() 41 | [3, 2, 1, 1, 2, 3].unique() // [3, 2, 1] 42 | 43 | // Strings can be unique() now, too 44 | "aaaaaaabcdef".unique() // ["a", "b", "c", "d", "e", "f"] 45 | 46 | // Or a Dictionary's values 47 | let uniqueValues = [1: "Waffle", 48 | 2: "Banana", 49 | 3: "Pancake", 50 | 4: "Pancake", 51 | 5: "Pancake" 52 | ].values.unique() 53 | 54 | print(uniqueValues) // ["Banana", "Pancake", "Waffle"] 55 | 56 | //: ## Extending with Hashable types 57 | 58 | // This extension is an addition, it is NOT replacing the other extension. 59 | extension Collection where Element: Hashable { 60 | func unique() -> [Element] { 61 | var set = Set() 62 | var uniqueValues = [Element]() 63 | for element in self { 64 | if !set.contains(element) { 65 | uniqueValues.append(element) 66 | set.insert(element) 67 | } 68 | } 69 | return uniqueValues 70 | } 71 | } 72 | 73 | //: ## Extending Set 74 | 75 | extension Set { 76 | func unique() -> [Element] { 77 | return Array(self) 78 | } 79 | } 80 | 81 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 82 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/Extending with concrete constraints.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Extending with concrete constraints 4 | 5 | import UIKit 6 | 7 | struct Article: Hashable { 8 | let viewCount: Int 9 | } 10 | 11 | extension Collection where Element == Article { 12 | var totalViewCount: Int { 13 | var count = 0 14 | for article in self { 15 | count += article.viewCount 16 | } 17 | return count 18 | } 19 | } 20 | 21 | let articleOne = Article(viewCount: 30) 22 | let articleTwo = Article(viewCount: 200) 23 | 24 | // Getting the total count on an Array. 25 | let articlesArray = [articleOne, articleTwo] 26 | articlesArray.totalViewCount // 230 27 | 28 | // Getting the total count on a Set. 29 | let articlesSet: Set
= [articleOne, articleTwo] 30 | articlesSet.totalViewCount // 230 31 | 32 | 33 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 34 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/Horizontal modeling.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Horizontal modeling 4 | 5 | import Foundation 6 | 7 | protocol RequestBuilder { 8 | var baseURL: URL { get } 9 | func makeRequest(path: String) -> URLRequest 10 | } 11 | 12 | extension RequestBuilder { 13 | func makeRequest(path: String) -> URLRequest { 14 | let url = baseURL.appendingPathComponent(path) 15 | var request = URLRequest(url: url) 16 | request.httpShouldHandleCookies = false 17 | request.timeoutInterval = 30 18 | return request 19 | } 20 | } 21 | 22 | struct BikeRequestBuilder: RequestBuilder { 23 | let baseURL: URL = URL(string: "https://www.biketriptracker.com")! 24 | } 25 | 26 | let bikeRequestBuilder = BikeRequestBuilder() 27 | let request = bikeRequestBuilder.makeRequest(path: "/trips/all") 28 | print(request) // https://www.biketriptracker.com/trips/all 29 | 30 | //: ## Multiple extensions 31 | 32 | enum ResponseError: Error { 33 | case invalidResponse 34 | } 35 | protocol ResponseHandler { 36 | func decrypt(data: Data) throws -> Data 37 | func validate(response: URLResponse) throws 38 | } 39 | 40 | extension ResponseHandler { 41 | func validate(response: URLResponse) throws { 42 | guard let httpresponse = response as? HTTPURLResponse else { 43 | throw ResponseError.invalidResponse 44 | } 45 | } 46 | } 47 | 48 | class BikeAPI: RequestBuilder, ResponseHandler { 49 | let baseURL: URL = URL(string: "https://www.biketriptracker.com")! 50 | 51 | func decrypt(data: Data) throws -> Data { 52 | return data 53 | } 54 | } 55 | 56 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 57 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/Protocol overrides.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Protocol overrides 4 | 5 | import Foundation 6 | 7 | protocol Plant { 8 | func grow() 9 | } 10 | 11 | extension Plant { 12 | func grow() { 13 | print("Growing a plant") 14 | } 15 | } 16 | 17 | protocol Tree: Plant {} 18 | 19 | extension Tree { 20 | func grow() { 21 | print("Growing a tree") 22 | } 23 | } 24 | 25 | struct Oak: Tree { 26 | func grow() { 27 | print("The mighty oak is growing") 28 | } 29 | } 30 | 31 | struct CherryTree: Tree {} 32 | 33 | struct KiwiPlant: Plant {} 34 | 35 | func growPlant(_ plant: P) { 36 | plant.grow() 37 | } 38 | 39 | growPlant(Oak()) // The mighty oak is growing 40 | growPlant(CherryTree()) // Growing a tree 41 | growPlant(KiwiPlant()) // Growing a plant 42 | 43 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 44 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Protocol Extensions 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Horizontal modeling](Horizontal%20modeling) 25 | 1. [Mailer using protocol inheritance](A%20mailer%20using%20protocol%20inheritance) 26 | 1. [Mailer using protocol composition](A%20mailer%20using%20protocol%20composition) 27 | 1. [Protocol overrides](Protocol%20overrides) 28 | 1. [Extending in two directions](Extending%20in%20two%20directions) 29 | 1. [Extending with associated types](Extending%20with%20associated%20types) 30 | 1. [Extending with concrete constraints](Extending%20with%20concrete%20constraints) 31 | 1. [Extending Sequence](Extending%20Sequence) 32 | 1. [Exercises and Answers](Exercises%20and%20Answers) 33 | */ 34 | //: [Next](@next) 35 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch12-extensions/Extensions.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Conditional Conformance CachedValue.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Conditional Conformance CachedValue 4 | 5 | import Foundation 6 | 7 | final class CachedValue { 8 | private let load: () -> T 9 | private var lastLoaded: Date 10 | 11 | private var timeToLive: Double 12 | private var currentValue: T 13 | 14 | public var value: T { 15 | let needsRefresh = abs(lastLoaded.timeIntervalSinceNow) > timeToLive 16 | if needsRefresh { 17 | currentValue = load() 18 | lastLoaded = Date() 19 | } 20 | return currentValue 21 | } 22 | 23 | init(timeToLive: Double, load: @escaping (() -> T)) { 24 | self.timeToLive = timeToLive 25 | self.load = load 26 | self.currentValue = load() 27 | self.lastLoaded = Date() 28 | } 29 | } 30 | 31 | //: Applying Conditional Conformance to CachedValue 32 | 33 | extension CachedValue: Equatable where T: Equatable { 34 | static func == (lhs: CachedValue, rhs: CachedValue) -> Bool { 35 | return lhs.value == rhs.value 36 | } 37 | } 38 | 39 | // Conforming to Hashable 40 | extension CachedValue: Hashable where T: Hashable { 41 | func hash(into hasher: inout Hasher) { 42 | hasher.combine(value) 43 | } 44 | } 45 | 46 | // Conforming to Comparable 47 | extension CachedValue: Comparable where T: Comparable { 48 | static func <(lhs: CachedValue, rhs: CachedValue) -> Bool { 49 | return lhs.value < rhs.value 50 | } 51 | 52 | static func ==(lhs: CachedValue, rhs: CachedValue) -> Bool { 53 | return lhs.value == rhs.value 54 | } 55 | } 56 | 57 | let cachedValueOne = CachedValue(timeToLive: 60) { 58 | // Perform expensive operation 59 | // E.g. Calculate the purpose of life 60 | return 42 61 | } 62 | 63 | let cachedValueTwo = CachedValue(timeToLive: 120) { 64 | // Perform another expensive operation 65 | return 1000 66 | } 67 | 68 | cachedValueOne == cachedValueTwo // Equatable: We can check for equality. 69 | cachedValueOne > cachedValueTwo // Comparable: We can compare two cached values. 70 | 71 | let set = Set(arrayLiteral: cachedValueOne, cachedValueTwo) // Hashable: We can store CachedValue in a set 72 | 73 | let simplecache = CachedValue(timeToLive: 2, load: { () -> String in 74 | print("I am being refreshed!") 75 | return "I am the value inside CachedValue" 76 | }) 77 | 78 | // Prints: "I am being refreshed!" 79 | simplecache.value // "I am the value inside CachedValue" <3> 80 | simplecache.value // "I am the value inside CachedValue" <3> 81 | 82 | sleep(3) // wait 3 seconds <4> 83 | 84 | // Prints: "I am being refreshed!" <4> 85 | simplecache.value // "I am the value inside CachedValue" 86 | 87 | 88 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 89 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Conditional Conformance part 2.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Conditional Conformance part 2 6 | 7 | //: AudioTrack with Conditional Conformance 8 | 9 | protocol Track { 10 | func play() 11 | } 12 | 13 | struct AudioTrack: Track { 14 | let file: URL 15 | func play() { 16 | print("playing audio at \(file)") 17 | } 18 | } 19 | 20 | extension Array: Track where Element: Track { 21 | func play() { 22 | for element in self { 23 | element.play() 24 | } 25 | } 26 | } 27 | 28 | //: ## Conditional Conformance with Optional 29 | 30 | extension Optional: Track where Wrapped: Track { 31 | func play() { 32 | switch self { 33 | case .some(let track): 34 | track.play() 35 | case nil: 36 | break // do nothing 37 | } 38 | } 39 | } 40 | 41 | let audio: AudioTrack? = AudioTrack(file: URL(fileURLWithPath: "1.mp3")) 42 | audio?.play() // Now an optional can play audio too, if there is any `Track` type inside. 43 | 44 | func playDelayed(_ track: T, delay: Double) { 45 | // ... snip 46 | } 47 | 48 | playDelayed(audio, delay: 3.0) 49 | 50 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 51 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Conditional Conformance.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | import Foundation 4 | 5 | //: # Conditional Conformance 6 | 7 | //: Equatable is synthesized on structs 8 | 9 | struct Movie: Equatable { 10 | let title: String 11 | let rating: Float 12 | } 13 | 14 | let movie = Movie(title: "The princess bride", rating: 9.7) 15 | 16 | movie == movie // true. We can already compare without implementing Equatable 17 | 18 | 19 | //: ## AudioTrack without Conditional Conformance 20 | 21 | protocol Track { 22 | func play() 23 | } 24 | 25 | struct AudioTrack: Track { 26 | let file: URL 27 | func play() { 28 | print("playing audio at \(file)") 29 | } 30 | } 31 | 32 | extension Array where Element: Track { 33 | func play() { 34 | for element in self { 35 | element.play() 36 | } 37 | } 38 | } 39 | 40 | let tracks = [ 41 | AudioTrack(file: URL(fileURLWithPath: "1.mp3")), 42 | AudioTrack(file: URL(fileURLWithPath: "2.mp3")) 43 | ] 44 | tracks.play() // We use the play() method 45 | 46 | // If an Array is nested, we can't call play() any more. 47 | //[tracks, tracks].play() // error: type of expression is ambiguous without more context 48 | 49 | // Or we can't pass an array if anything expects the Track protocol. 50 | func playDelayed(_ track: T, delay: Double) { 51 | // ... snip 52 | } 53 | 54 | //playDelayed(tracks, delay: 2.0) // argument type '[AudioTrack]' does not conform to expected type 'Track' 55 | 56 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 57 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Poker with an enum.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Poker with an enum 4 | 5 | //: We can opt to make PokerGame an enum instead of a protocol. Then we're working with a concrete type again, and satisfy the compiler. 6 | 7 | import Foundation 8 | 9 | enum PokerGame: Hashable { 10 | case studPoker(StudPoker) 11 | case texasHoldem(TexasHoldem) 12 | } 13 | 14 | struct StudPoker: Hashable { 15 | // ... Implementation omitted 16 | } 17 | struct TexasHoldem: Hashable { 18 | // ... Implementation omitted 19 | } 20 | 21 | var numberOfPlayers = [PokerGame: Int]() 22 | 23 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 24 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Poker with type erasure.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Poker with type erasure 4 | 5 | //: We can apply type erasure to get runtime benefits when using a protocol with associated types or Self requirements. 6 | 7 | import Foundation 8 | 9 | protocol PokerGame: Hashable { 10 | func start() 11 | } 12 | 13 | struct StudPoker: PokerGame { 14 | func start() { print("Starting a Stud poker game") } 15 | } 16 | struct TexasHoldem: PokerGame { 17 | func start() { print("Starting a Texas Holdem game") } 18 | } 19 | 20 | struct AnyPokerGame: PokerGame { 21 | 22 | private let _start: () -> Void 23 | private let _hashable: AnyHashable 24 | 25 | init(_ pokerGame: Game) { 26 | _start = pokerGame.start 27 | _hashable = AnyHashable(pokerGame) 28 | } 29 | 30 | func start() { 31 | _start() 32 | } 33 | } 34 | 35 | extension AnyPokerGame: Hashable { 36 | 37 | func hash(into hasher: inout Hasher) { 38 | _hashable.hash(into: &hasher) 39 | } 40 | 41 | static func ==(lhs: AnyPokerGame, rhs: AnyPokerGame) -> Bool { 42 | return lhs._hashable == rhs._hashable 43 | } 44 | } 45 | 46 | let studPoker = StudPoker() 47 | let holdEm = TexasHoldem() 48 | 49 | //: Now we can mix multiple poker games inside an array. 50 | 51 | let games: [AnyPokerGame] = [ 52 | AnyPokerGame(studPoker), 53 | AnyPokerGame(holdEm) 54 | ] 55 | 56 | games.forEach { (pokerGame: AnyPokerGame) in 57 | pokerGame.start() 58 | } 59 | 60 | //: We can even use poker games as keys! 61 | 62 | var numberOfPlayers = [ 63 | AnyPokerGame(studPoker): 300, 64 | AnyPokerGame(holdEm): 400 65 | ] 66 | 67 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 68 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Poker.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Poker 4 | 5 | import Foundation 6 | 7 | protocol PokerGame: Hashable { 8 | func start() 9 | } 10 | 11 | struct StudPoker: PokerGame { 12 | func start() { 13 | print("Starting StudPoker") 14 | } 15 | } 16 | struct TexasHoldem: PokerGame { 17 | func start() { 18 | print("Starting Texas Holdem") 19 | } 20 | } 21 | 22 | //: This line below will throw an error becuase we can't use a protocol with Self requirements as a concrete type. 23 | //let numberOfPlayers = [PokerGame: Int]() 24 | 25 | //error: Patterns.playground:10:23: error: using 'PokerGame' as a concrete type conforming to protocol 'Hashable' is not supported 26 | 27 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 28 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Table of contents.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Swift patterns 4 | 5 | Code listings and exercises for [Swift in Depth][Swift in Depth]. 6 | 7 | By [Tjeerd in 't Veen][Tjeerd in 't Veen] 8 | 9 | Published by [Manning][Manning] 10 | 11 | [Manning]: https://www.manning.com 12 | 13 | Press command + 1 to see all contents. 14 | 15 | Xcode: Go to Editor > Show Raw Markup, to switch between presentation styles. 16 | 17 | [Tjeerd in 't Veen]: https://twitter.com/tjeerdintveen 18 | [Swift in Depth]: https://www.manning.com/books/swift-in-depth 19 | 20 | **Written in Swift 5.0 using Xcode 11.3.1*** 21 | 22 | ## Table of Contents 23 | 24 | 1. [Mocking with protocols](Mocking%20with%20protocols) 25 | 1. [Conditional Conformance](Conditional%20Conformance) 26 | 1. [Conditional Conformance part 2](Conditional%20Conformance%20part%202) 27 | 1. [Conditional Conformance CachedValue](Conditional%20Conformance%20CachedValue) 28 | 1. [Poker](Poker) 29 | 1. [Poker with an enum](Poker%20with%20an%20enum) 30 | 1. [Poker with type erasure](Poker%20with%20type%20erasure) 31 | 1. [Validator as a generic struct](Validator%20as%20a%20generic%20struct) 32 | 1. [Exercises](Exercises) 33 | 1. [Answers](Answers) 34 | */ 35 | //: [Next](@next) 36 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/Pages/Validator as a generic struct.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 2 | 3 | //: # Validator as a generic struct 4 | 5 | import Foundation 6 | 7 | struct Validator { 8 | 9 | let validate: (T) -> Bool 10 | 11 | init(validate: @escaping (T) -> Bool) { 12 | self.validate = validate 13 | } 14 | } 15 | 16 | let notEmpty = Validator(validate: { string -> Bool in 17 | return !string.isEmpty 18 | }) 19 | 20 | notEmpty.validate("") // false 21 | notEmpty.validate("Still reading this book huh? That's cool!") // true 22 | 23 | extension Validator { 24 | func combine(_ other: Validator) -> Validator { 25 | let combinedValidator = Validator(validate: { (value: T) -> Bool in 26 | let ownResult = self.validate(value) 27 | let otherResult = other.validate(value) 28 | return ownResult && otherResult 29 | }) 30 | 31 | return combinedValidator 32 | } 33 | } 34 | 35 | let maxTenChars = Validator(validate: { string -> Bool in 36 | return string.count <= 10 37 | }) 38 | 39 | let combinedValidator: Validator = notEmpty.combine(maxTenChars) 40 | combinedValidator.validate("") // false 41 | combinedValidator.validate("Hi") // true 42 | combinedValidator.validate("This one is way too long") // false 43 | 44 | //: [Table of contents](Table%20of%20contents) - [Previous page](@previous) - [Next page](@next) 45 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch13-patterns-with-protocols/Patterns.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /swiftindepth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjeerdintveen/manning-swift-in-depth/4e44789d659a992ec2723d782847096ea066375a/swiftindepth.png --------------------------------------------------------------------------------