SwiftProgrammingLanguage57 (2).pdf

Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

Full Transcript

Welcome to Swift PDF conversion courtesy of www.appsdissected.com About Swift Swift is a fantastic way to write software, whether it’s for phones, desktops, servers, or anything else that runs code. It’s a safe, fast, and interactive programming language that combines the best in modern lan...

Welcome to Swift PDF conversion courtesy of www.appsdissected.com About Swift Swift is a fantastic way to write software, whether it’s for phones, desktops, servers, or anything else that runs code. It’s a safe, fast, and interactive programming language that combines the best in modern language thinking with wisdom from the wider Apple engineering culture and the diverse contributions from its open- source community. The compiler is optimized for performance and the language is optimized for development, without compromising on either. Swift is friendly to new programmers. It’s an industrial-quality programming language that’s as expressive and enjoyable as a scripting language. Writing Swift code in a playground lets you experiment with code and see the results immediately, without the overhead of building and running an app. Swift defines away large classes of common programming errors by adopting modern programming patterns: Variables are always initialized before use. Array indices are checked for out-of-bounds errors. Integers are checked for overflow. Optionals ensure that nil values are handled explicitly. Memory is managed automatically. Error handling allows controlled recovery from unexpected failures. Swift code is compiled and optimized to get the most out of modern hardware. The syntax and standard library have been designed based on the guiding principle that the obvious way to write your code PDF conversion courtesy of www.appsdissected.com should also perform the best. Its combination of safety and speed make Swift an excellent choice for everything from “Hello, world!” to an entire operating system. Swift combines powerful type inference and pattern matching with a modern, lightweight syntax, allowing complex ideas to be expressed in a clear and concise manner. As a result, code is not just easier to write, but easier to read and maintain as well. Swift has been years in the making, and it continues to evolve with new features and capabilities. Our goals for Swift are ambitious. We can’t wait to see what you create with it. PDF conversion courtesy of www.appsdissected.com Version Compatibility This book describes Swift 5.7, the default version of Swift that’s included in Xcode 14. You can use Xcode 14 to build targets that are written in either Swift 5.7, Swift 4.2, or Swift 4. When you use Xcode 14 to build Swift 4 and Swift 4.2 code, most Swift 5.7 functionality is available. That said, the following changes are available only to code that uses Swift 5.7 or later: Functions that return an opaque type require the Swift 5.1 runtime. The try? expression doesn’t introduce an extra level of optionality to expressions that already return optionals. Large integer literal initialization expressions are inferred to be of the correct integer type. For example, UInt64(0xffff_ffff_ffff_ffff) evaluates to the correct value rather than overflowing. Concurrency requires Swift 5.7 or later, and a version of the Swift standard library that provides the corresponding concurrency types. On Apple platforms, set a deployment target of at least iOS 15, macOS 12, tvOS 15, or watchOS 8.0. A target written in Swift 5.7 can depend on a target that’s written in Swift 4.2 or Swift 4, and vice versa. This means, if you have a large project that’s divided into multiple frameworks, you can migrate your code from Swift 4 to Swift 5.7 one framework at a time. PDF conversion courtesy of www.appsdissected.com A Swift Tour Tradition suggests that the first program in a new language should print the words “Hello, world!” on the screen. In Swift, this can be done in a single line: 1 print("Hello, world!") 2 // Prints "Hello, world!" If you have written code in C or Objective-C, this syntax looks familiar to you—in Swift, this line of code is a complete program. You don’t need to import a separate library for functionality like input/output or string handling. Code written at global scope is used as the entry point for the program, so you don’t need a main() function. You also don’t need to write semicolons at the end of every statement. This tour gives you enough information to start writing code in Swift by showing you how to accomplish a variety of programming tasks. Don’t worry if you don’t understand something—everything introduced in this tour is explained in detail in the rest of this book. NOTE On a Mac with Xcode installed, or on an iPad with Swift Playgrounds, you can open this chapter as a playground. Playgrounds allow you to edit the code listings and see the result immediately. Download Playground Simple Values PDF conversion courtesy of www.appsdissected.com Use let to make a constant and var to make a variable. The value of a constant doesn’t need to be known at compile time, but you must assign it a value exactly once. This means you can use constants to name a value that you determine once but use in many places. 1 var myVariable = 42 2 myVariable = 50 3 let myConstant = 42 A constant or variable must have the same type as the value you want to assign to it. However, you don’t always have to write the type explicitly. Providing a value when you create a constant or variable lets the compiler infer its type. In the example above, the compiler infers that myVariable is an integer because its initial value is an integer. If the initial value doesn’t provide enough information (or if there isn’t an initial value), specify the type by writing it after the variable, separated by a colon. 1 let implicitInteger = 70 2 let implicitDouble = 70.0 3 let explicitDouble: Double = 70 EXPERIMENT Create a constant with an explicit type of Float and a value of 4. Values are never implicitly converted to another type. If you need to convert a value to a different type, explicitly make an instance of the desired type. PDF conversion courtesy of www.appsdissected.com 1 let label = "The width is " 2 let width = 94 3 let widthLabel = label + String(width) EXPERIMENT Try removing the conversion to String from the last line. What error do you get? There’s an even simpler way to include values in strings: Write the value in parentheses, and write a backslash (\) before the parentheses. For example: 1 let apples = 3 2 let oranges = 5 3 let appleSummary = "I have \(apples) apples." 4 let fruitSummary = "I have \(apples + oranges) pieces of fruit." EXPERIMENT Use \() to include a floating-point calculation in a string and to include someone’s name in a greeting. Use three double quotation marks (""") for strings that take up multiple lines. Indentation at the start of each quoted line is removed, as long as it matches the indentation of the closing quotation marks. For example: PDF conversion courtesy of www.appsdissected.com 1 let quotation = """ 2 I said "I have \(apples) apples." 3 And then I said "I have \(apples + oranges) pieces of fruit." 4 """ Create arrays and dictionaries using brackets ([]), and access their elements by writing the index or key in brackets. A comma is allowed after the last element. 1 var fruits = ["strawberries", "limes", "tangerines"] 2 fruits = "grapes" 3 4 var occupations = [ 5 "Malcolm": "Captain", 6 "Kaylee": "Mechanic", 7 ] 8 occupations["Jayne"] = "Public Relations" Arrays automatically grow as you add elements. 1 fruits.append("blueberries") 2 print(fruits) To create an empty array or dictionary, use the initializer syntax. 1 let emptyArray: [String] = [] 2 let emptyDictionary: [String: Float] = [:] PDF conversion courtesy of www.appsdissected.com If type information can be inferred, you can write an empty array as [] and an empty dictionary as [:]—for example, when you set a new value for a variable or pass an argument to a function. 1 fruits = [] 2 occupations = [:] Control Flow Use if and switch to make conditionals, and use for-in, while, and repeat-while to make loops. Parentheses around the condition or loop variable are optional. Braces around the body are required. 1 let individualScores = [75, 43, 103, 87, 12] 2 var teamScore = 0 3 for score in individualScores { 4 if score > 50 { 5 teamScore += 3 6 } else { 7 teamScore += 1 8 } 9 } 10 print(teamScore) 11 // Prints "11" In an if statement, the conditional must be a Boolean expression— this means that code such as if score {... } is an error, not an PDF conversion courtesy of www.appsdissected.com implicit comparison to zero. You can use if and let together to work with values that might be missing. These values are represented as optionals. An optional value either contains a value or contains nil to indicate that a value is missing. Write a question mark (?) after the type of a value to mark the value as optional. 1 var optionalString: String? = "Hello" 2 print(optionalString == nil) 3 // Prints "false" 4 5 var optionalName: String? = "John Appleseed" 6 var greeting = "Hello!" 7 if let name = optionalName { 8 greeting = "Hello, \(name)" 9 } EXPERIMENT Change optionalName to nil. What greeting do you get? Add an else clause that sets a different greeting if optionalName is nil. If the optional value is nil, the conditional is false and the code in braces is skipped. Otherwise, the optional value is unwrapped and assigned to the constant after let, which makes the unwrapped value available inside the block of code. Another way to handle optional values is to provide a default value using the ?? operator. If the optional value is missing, the default value is used instead. PDF conversion courtesy of www.appsdissected.com 1 let nickname: String? = nil 2 let fullName: String = "John Appleseed" 3 let informalGreeting = "Hi \(nickname ?? fullName)" You can use a shorter spelling to unwrap a value, using the same name for that unwrapped value. 1 if let nickname { 2 print("Hey, \(nickname)") 3 } Switches support any kind of data and a wide variety of comparison operations—they aren’t limited to integers and tests for equality. 1 let vegetable = "red pepper" 2 switch vegetable { 3 case "celery": 4 print("Add some raisins and make ants on a log.") 5 case "cucumber", "watercress": 6 print("That would make a good tea sandwich.") 7 case let x where x.hasSuffix("pepper"): 8 print("Is it a spicy \(x)?") 9 default: 10 print("Everything tastes good in soup.") 11 } 12 // Prints "Is it a spicy red pepper?" PDF conversion courtesy of www.appsdissected.com EXPERIMENT Try removing the default case. What error do you get? Notice how let can be used in a pattern to assign the value that matched the pattern to a constant. After executing the code inside the switch case that matched, the program exits from the switch statement. Execution doesn’t continue to the next case, so you don’t need to explicitly break out of the switch at the end of each case’s code. You use for-in to iterate over items in a dictionary by providing a pair of names to use for each key-value pair. Dictionaries are an unordered collection, so their keys and values are iterated over in an arbitrary order. PDF conversion courtesy of www.appsdissected.com 1 let interestingNumbers = [ 2 "Prime": [2, 3, 5, 7, 11, 13], 3 "Fibonacci": [1, 1, 2, 3, 5, 8], 4 "Square": [1, 4, 9, 16, 25], 5 ] 6 var largest = 0 7 for (_, numbers) in interestingNumbers { 8 for number in numbers { 9 if number > largest { 10 largest = number 11 } 12 } 13 } 14 print(largest) 15 // Prints "25" EXPERIMENT Replace the _ with a variable name, and keep track of which kind of number was the largest. Use while to repeat a block of code until a condition changes. The condition of a loop can be at the end instead, ensuring that the loop is run at least once. PDF conversion courtesy of www.appsdissected.com 1 var n = 2 2 while n < 100 { 3 n *= 2 4 } 5 print(n) 6 // Prints "128" 7 8 var m = 2 9 repeat { 10 m *= 2 11 } while m < 100 12 print(m) 13 // Prints "128" You can keep an index in a loop by using..< to make a range of indexes. 1 var total = 0 2 for i in 0.. to separate the parameter names and types from the function’s return type. 1 func greet(person: String, day: String) -> String { 2 return "Hello \(person), today is \(day)." 3 } 4 greet(person: "Bob", day: "Tuesday") EXPERIMENT Remove the day parameter. Add a parameter to include today’s lunch special in the greeting. By default, functions use their parameter names as labels for their arguments. Write a custom argument label before the parameter name, or write _ to use no argument label. 1 func greet(_ person: String, on day: String) -> String { 2 return "Hello \(person), today is \(day)." 3 } 4 greet("John", on: "Wednesday") Use a tuple to make a compound value—for example, to return multiple values from a function. The elements of a tuple can be referred to either by name or by number. PDF conversion courtesy of www.appsdissected.com 1 func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) { 2 var min = scores 3 var max = scores 4 var sum = 0 5 6 for score in scores { 7 if score > max { 8 max = score 9 } else if score < min { 10 min = score 11 } 12 sum += score 13 } 14 15 return (min, max, sum) 16 } 17 let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9]) 18 print(statistics.sum) 19 // Prints "120" 20 print(statistics.2) 21 // Prints "120" Functions can be nested. Nested functions have access to variables that were declared in the outer function. You can use nested functions to organize the code in a function that’s long or complex. PDF conversion courtesy of www.appsdissected.com 1 func returnFifteen() -> Int { 2 var y = 10 3 func add() { 4 y += 5 5 } 6 add() 7 return y 8 } 9 returnFifteen() Functions are a first-class type. This means that a function can return another function as its value. 1 func makeIncrementer() -> ((Int) -> Int) { 2 func addOne(number: Int) -> Int { 3 return 1 + number 4 } 5 return addOne 6 } 7 var increment = makeIncrementer() 8 increment(7) A function can take another function as one of its arguments. PDF conversion courtesy of www.appsdissected.com 1 func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool { 2 for item in list { 3 if condition(item) { 4 return true 5 } 6 } 7 return false 8 } 9 func lessThanTen(number: Int) -> Bool { 10 return number < 10 11 } 12 var numbers = [20, 19, 7, 12] 13 hasAnyMatches(list: numbers, condition: lessThanTen) Functions are actually a special case of closures: blocks of code that can be called later. The code in a closure has access to things like variables and functions that were available in the scope where the closure was created, even if the closure is in a different scope when it’s executed—you saw an example of this already with nested functions. You can write a closure without a name by surrounding code with braces ({}). Use in to separate the arguments and return type from the body. 1 numbers.map({ (number: Int) -> Int in 2 let result = 3 * number 3 return result 4 }) PDF conversion courtesy of www.appsdissected.com EXPERIMENT Rewrite the closure to return zero for all odd numbers. You have several options for writing closures more concisely. When a closure’s type is already known, such as the callback for a delegate, you can omit the type of its parameters, its return type, or both. Single statement closures implicitly return the value of their only statement. 1 let mappedNumbers = numbers.map({ number in 3 * number }) 2 print(mappedNumbers) 3 // Prints "[60, 57, 21, 36]" You can refer to parameters by number instead of by name—this approach is especially useful in very short closures. A closure passed as the last argument to a function can appear immediately after the parentheses. When a closure is the only argument to a function, you can omit the parentheses entirely. 1 let sortedNumbers = numbers.sorted { $0 > $1 } 2 print(sortedNumbers) 3 // Prints "[20, 19, 12, 7]" Objects and Classes Use class followed by the class’s name to create a class. A property declaration in a class is written the same way as a constant or variable declaration, except that it’s in the context of a class. Likewise, method and function declarations are written the same way. PDF conversion courtesy of www.appsdissected.com 1 class Shape { 2 var numberOfSides = 0 3 func simpleDescription() -> String { 4 return "A shape with \(numberOfSides) sides." 5 } 6 } EXPERIMENT Add a constant property with let, and add another method that takes an argument. Create an instance of a class by putting parentheses after the class name. Use dot syntax to access the properties and methods of the instance. 1 var shape = Shape() 2 shape.numberOfSides = 7 3 var shapeDescription = shape.simpleDescription() This version of the Shape class is missing something important: an initializer to set up the class when an instance is created. Use init to create one. PDF conversion courtesy of www.appsdissected.com 1 class NamedShape { 2 var numberOfSides: Int = 0 3 var name: String 4 5 init(name: String) { 6 self.name = name 7 } 8 9 func simpleDescription() -> String { 10 return "A shape with \(numberOfSides) sides." 11 } 12 } Notice how self is used to distinguish the name property from the name argument to the initializer. The arguments to the initializer are passed like a function call when you create an instance of the class. Every property needs a value assigned—either in its declaration (as with numberOfSides) or in the initializer (as with name). Use deinit to create a deinitializer if you need to perform some cleanup before the object is deallocated. Subclasses include their superclass name after their class name, separated by a colon. There’s no requirement for classes to subclass any standard root class, so you can include or omit a superclass as needed. Methods on a subclass that override the superclass’s implementation are marked with override—overriding a method by accident, without override, is detected by the compiler as an error. The compiler also PDF conversion courtesy of www.appsdissected.com detects methods with override that don’t actually override any method in the superclass. 1 class Square: NamedShape { 2 var sideLength: Double 3 4 init(sideLength: Double, name: String) { 5 self.sideLength = sideLength 6 super.init(name: name) 7 numberOfSides = 4 8 } 9 10 func area() -> Double { 11 return sideLength * sideLength 12 } 13 14 override func simpleDescription() -> String { 15 return "A square with sides of length \ (sideLength)." 16 } 17 } 18 let test = Square(sideLength: 5.2, name: "my test square") 19 test.area() 20 test.simpleDescription() PDF conversion courtesy of www.appsdissected.com EXPERIMENT Make another subclass of NamedShape called Circle that takes a radius and a name as arguments to its initializer. Implement an area() and a simpleDescription() method on the Circle class. In addition to simple properties that are stored, properties can have a getter and a setter. PDF conversion courtesy of www.appsdissected.com 1 class EquilateralTriangle: NamedShape { 2 var sideLength: Double = 0.0 3 4 init(sideLength: Double, name: String) { 5 self.sideLength = sideLength 6 super.init(name: name) 7 numberOfSides = 3 8 } 9 10 var perimeter: Double { 11 get { 12 return 3.0 * sideLength 13 } 14 set { 15 sideLength = newValue / 3.0 16 } 17 } 18 19 override func simpleDescription() -> String { 20 return "An equilateral triangle with sides of length \(sideLength)." 21 } 22 } 23 var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle") 24 print(triangle.perimeter) PDF conversion courtesy of www.appsdissected.com 25 // Prints "9.3" 26 triangle.perimeter = 9.9 27 print(triangle.sideLength) 28 // Prints "3.3000000000000003" In the setter for perimeter, the new value has the implicit name newValue. You can provide an explicit name in parentheses after set. Notice that the initializer for the EquilateralTriangle class has three different steps: 1. Setting the value of properties that the subclass declares. 2. Calling the superclass’s initializer. 3. Changing the value of properties defined by the superclass. Any additional setup work that uses methods, getters, or setters can also be done at this point. If you don’t need to compute the property but still need to provide code that’s run before and after setting a new value, use willSet and didSet. The code you provide is run any time the value changes outside of an initializer. For example, the class below ensures that the side length of its triangle is always the same as the side length of its square. PDF conversion courtesy of www.appsdissected.com 1 class TriangleAndSquare { 2 var triangle: EquilateralTriangle { 3 willSet { 4 square.sideLength = newValue.sideLength 5 } 6 } 7 var square: Square { 8 willSet { 9 triangle.sideLength = newValue.sideLength 10 } 11 } 12 init(size: Double, name: String) { 13 square = Square(sideLength: size, name: name) 14 triangle = EquilateralTriangle(sideLength: size, name: name) 15 } 16 } 17 var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape") 18 print(triangleAndSquare.square.sideLength) 19 // Prints "10.0" 20 print(triangleAndSquare.triangle.sideLength) 21 // Prints "10.0" PDF conversion courtesy of www.appsdissected.com 22 triangleAndSquare.square = Square(sideLength: 50, name: "larger square") 23 print(triangleAndSquare.triangle.sideLength) 24 // Prints "50.0" When working with optional values, you can write ? before operations like methods, properties, and subscripting. If the value before the ? is nil, everything after the ? is ignored and the value of the whole expression is nil. Otherwise, the optional value is unwrapped, and everything after the ? acts on the unwrapped value. In both cases, the value of the whole expression is an optional value. 1 let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 2 let sideLength = optionalSquare?.sideLength Enumerations and Structures Use enum to create an enumeration. Like classes and all other named types, enumerations can have methods associated with them. PDF conversion courtesy of www.appsdissected.com 1 enum Rank: Int { 2 case ace = 1 3 case two, three, four, five, six, seven, eight, nine, ten 4 case jack, queen, king 5 6 func simpleDescription() -> String { 7 switch self { 8 case.ace: 9 return "ace" 10 case.jack: 11 return "jack" 12 case.queen: 13 return "queen" 14 case.king: 15 return "king" 16 default: 17 return String(self.rawValue) 18 } 19 } 20 } 21 let ace = Rank.ace 22 let aceRawValue = ace.rawValue PDF conversion courtesy of www.appsdissected.com EXPERIMENT Write a function that compares two Rank values by comparing their raw values. By default, Swift assigns the raw values starting at zero and incrementing by one each time, but you can change this behavior by explicitly specifying values. In the example above, Ace is explicitly given a raw value of 1, and the rest of the raw values are assigned in order. You can also use strings or floating-point numbers as the raw type of an enumeration. Use the rawValue property to access the raw value of an enumeration case. Use the init?(rawValue:) initializer to make an instance of an enumeration from a raw value. It returns either the enumeration case matching the raw value or nil if there’s no matching Rank. 1 if let convertedRank = Rank(rawValue: 3) { 2 let threeDescription = convertedRank.simpleDescription() 3 } The case values of an enumeration are actual values, not just another way of writing their raw values. In fact, in cases where there isn’t a meaningful raw value, you don’t have to provide one. PDF conversion courtesy of www.appsdissected.com 1 enum Suit { 2 case spades, hearts, diamonds, clubs 3 4 func simpleDescription() -> String { 5 switch self { 6 case.spades: 7 return "spades" 8 case.hearts: 9 return "hearts" 10 case.diamonds: 11 return "diamonds" 12 case.clubs: 13 return "clubs" 14 } 15 } 16 } 17 let hearts = Suit.hearts 18 let heartsDescription = hearts.simpleDescription() EXPERIMENT Add a color() method to Suit that returns “black” for spades and clubs, and returns “red” for hearts and diamonds. Notice the two ways that the hearts case of the enumeration is referred to above: When assigning a value to the hearts constant, the enumeration case Suit.hearts is referred to by its full name because the constant doesn’t have an explicit type specified. Inside the switch, the enumeration case is referred to by the abbreviated form.hearts PDF conversion courtesy of www.appsdissected.com because the value of self is already known to be a suit. You can use the abbreviated form anytime the value’s type is already known. If an enumeration has raw values, those values are determined as part of the declaration, which means every instance of a particular enumeration case always has the same raw value. Another choice for enumeration cases is to have values associated with the case— these values are determined when you make the instance, and they can be different for each instance of an enumeration case. You can think of the associated values as behaving like stored properties of the enumeration case instance. For example, consider the case of requesting the sunrise and sunset times from a server. The server either responds with the requested information, or it responds with a description of what went wrong. PDF conversion courtesy of www.appsdissected.com 1 enum ServerResponse { 2 case result(String, String) 3 case failure(String) 4 } 5 6 let success = ServerResponse.result("6:00 am", "8:09 pm") 7 let failure = ServerResponse.failure("Out of cheese.") 8 9 switch success { 10 case let.result(sunrise, sunset): 11 print("Sunrise is at \(sunrise) and sunset is at \(sunset).") 12 case let.failure(message): 13 print("Failure... \(message)") 14 } 15 // Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm." EXPERIMENT Add a third case to ServerResponse and to the switch. Notice how the sunrise and sunset times are extracted from the ServerResponse value as part of matching the value against the switch cases. PDF conversion courtesy of www.appsdissected.com Use struct to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they’re passed around in your code, but classes are passed by reference. 1 struct Card { 2 var rank: Rank 3 var suit: Suit 4 func simpleDescription() -> String { 5 return "The \(rank.simpleDescription()) of \ (suit.simpleDescription())" 6 } 7 } 8 let threeOfSpades = Card(rank:.three, suit:.spades) 9 let threeOfSpadesDescription = threeOfSpades.simpleDescription() EXPERIMENT Write a function that returns an array containing a full deck of cards, with one card of each combination of rank and suit. Concurrency Use async to mark a function that runs asynchronously. PDF conversion courtesy of www.appsdissected.com 1 func fetchUserID(from server: String) async -> Int { 2 if server == "primary" { 3 return 97 4 } 5 return 501 6 } You mark a call to an asynchronous function by writing await in front of it. 1 func fetchUsername(from server: String) async -> String { 2 let userID = await fetchUserID(from: server) 3 if userID == 501 { 4 return "John Appleseed" 5 } 6 return "Guest" 7 } Use async let to call an asynchronous function, letting it run in parallel with other asynchronous code. When you use the value it returns, write await. PDF conversion courtesy of www.appsdissected.com 1 func connectUser(to server: String) async { 2 async let userID = fetchUserID(from: server) 3 async let username = fetchUsername(from: server) 4 let greeting = await "Hello \(username), user ID \(userID)" 5 print(greeting) 6 } Use Task to call asynchronous functions from synchronous code, without waiting for them to return. 1 Task { 2 await connectUser(to: "primary") 3 } 4 // Prints "Hello Guest, user ID 97" Protocols and Extensions Use protocol to declare a protocol. 1 protocol ExampleProtocol { 2 var simpleDescription: String { get } 3 mutating func adjust() 4 } Classes, enumerations, and structures can all adopt protocols. PDF conversion courtesy of www.appsdissected.com 1 class SimpleClass: ExampleProtocol { 2 var simpleDescription: String = "A very simple class." 3 var anotherProperty: Int = 69105 4 func adjust() { 5 simpleDescription += " Now 100% adjusted." 6 } 7 } 8 var a = SimpleClass() 9 a.adjust() 10 let aDescription = a.simpleDescription 11 12 struct SimpleStructure: ExampleProtocol { 13 var simpleDescription: String = "A simple structure" 14 mutating func adjust() { 15 simpleDescription += " (adjusted)" 16 } 17 } 18 var b = SimpleStructure() 19 b.adjust() 20 let bDescription = b.simpleDescription EXPERIMENT Add another requirement to ExampleProtocol. What changes do you need to make to SimpleClass and SimpleStructure so that they still conform to the protocol? PDF conversion courtesy of www.appsdissected.com Notice the use of the mutating keyword in the declaration of SimpleStructure to mark a method that modifies the structure. The declaration of SimpleClass doesn’t need any of its methods marked as mutating because methods on a class can always modify the class. Use extension to add functionality to an existing type, such as new methods and computed properties. You can use an extension to add protocol conformance to a type that’s declared elsewhere, or even to a type that you imported from a library or framework. 1 extension Int: ExampleProtocol { 2 var simpleDescription: String { 3 return "The number \(self)" 4 } 5 mutating func adjust() { 6 self += 42 7 } 8 } 9 print(7.simpleDescription) 10 // Prints "The number 7" EXPERIMENT Write an extension for the Double type that adds an absoluteValue property. You can use a protocol name just like any other named type—for example, to create a collection of objects that have different types but that all conform to a single protocol. When you work with values whose type is a protocol type, methods outside the protocol definition aren’t available. PDF conversion courtesy of www.appsdissected.com 1 let protocolValue: ExampleProtocol = a 2 print(protocolValue.simpleDescription) 3 // Prints "A very simple class. Now 100% adjusted." 4 // print(protocolValue.anotherProperty) // Uncomment to see the error Even though the variable protocolValue has a runtime type of SimpleClass, the compiler treats it as the given type of ExampleProtocol. This means that you can’t accidentally access methods or properties that the class implements in addition to its protocol conformance. Error Handling You represent errors using any type that adopts the Error protocol. 1 enum PrinterError: Error { 2 case outOfPaper 3 case noToner 4 case onFire 5 } Use throw to throw an error and throws to mark a function that can throw an error. If you throw an error in a function, the function returns immediately and the code that called the function handles the error. PDF conversion courtesy of www.appsdissected.com 1 func send(job: Int, toPrinter printerName: String) throws -> String { 2 if printerName == "Never Has Toner" { 3 throw PrinterError.noToner 4 } 5 return "Job sent" 6 } There are several ways to handle errors. One way is to use do-catch. Inside the do block, you mark code that can throw an error by writing try in front of it. Inside the catch block, the error is automatically given the name error unless you give it a different name. 1 do { 2 let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng") 3 print(printerResponse) 4 } catch { 5 print(error) 6 } 7 // Prints "Job sent" EXPERIMENT Change the printer name to "Never Has Toner", so that the send(job:toPrinter:) function throws an error. You can provide multiple catch blocks that handle specific errors. You write a pattern after catch just as you do after case in a switch. PDF conversion courtesy of www.appsdissected.com 1 do { 2 let printerResponse = try send(job: 1440, toPrinter: "Gutenberg") 3 print(printerResponse) 4 } catch PrinterError.onFire { 5 print("I'll just put this over here, with the rest of the fire.") 6 } catch let printerError as PrinterError { 7 print("Printer error: \(printerError).") 8 } catch { 9 print(error) 10 } 11 // Prints "Job sent" EXPERIMENT Add code to throw an error inside the do block. What kind of error do you need to throw so that the error is handled by the first catch block? What about the second and third blocks? Another way to handle errors is to use try? to convert the result to an optional. If the function throws an error, the specific error is discarded and the result is nil. Otherwise, the result is an optional containing the value that the function returned. 1 let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler") 2 let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner") PDF conversion courtesy of www.appsdissected.com Use defer to write a block of code that’s executed after all other code in the function, just before the function returns. The code is executed regardless of whether the function throws an error. You can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times. 1 var fridgeIsOpen = false 2 let fridgeContent = ["milk", "eggs", "leftovers"] 3 4 func fridgeContains(_ food: String) -> Bool { 5 fridgeIsOpen = true 6 defer { 7 fridgeIsOpen = false 8 } 9 10 let result = fridgeContent.contains(food) 11 return result 12 } 13 fridgeContains("banana") 14 print(fridgeIsOpen) 15 // Prints "false" Generics Write a name inside angle brackets to make a generic function or type. PDF conversion courtesy of www.appsdissected.com 1 func makeArray(repeating item: Item, numberOfTimes: Int) -> [Item] { 2 var result: [Item] = [] 3 for _ in 0.. oldValue { 8 print("Added \(totalSteps - oldValue) steps") 9 } 10 } 11 } 12 } 13 let stepCounter = StepCounter() 14 stepCounter.totalSteps = 200 15 // About to set totalSteps to 200 16 // Added 200 steps 17 stepCounter.totalSteps = 360 18 // About to set totalSteps to 360 19 // Added 160 steps 20 stepCounter.totalSteps = 896 21 // About to set totalSteps to 896 22 // Added 536 steps PDF conversion courtesy of www.appsdissected.com The StepCounter class declares a totalSteps property of type Int. This is a stored property with willSet and didSet observers. The willSet and didSet observers for totalSteps are called whenever the property is assigned a new value. This is true even if the new value is the same as the current value. This example’s willSet observer uses a custom parameter name of newTotalSteps for the upcoming new value. In this example, it simply prints out the value that’s about to be set. The didSet observer is called after the value of totalSteps is updated. It compares the new value of totalSteps against the old value. If the total number of steps has increased, a message is printed to indicate how many new steps have been taken. The didSet observer doesn’t provide a custom parameter name for the old value, and the default name of oldValue is used instead. NOTE If you pass a property that has observers to a function as an in-out parameter, the willSet and didSet observers are always called. This is because of the copy-in copy-out memory model for in-out parameters: The value is always written back to the property at the end of the function. For a detailed discussion of the behavior of in-out parameters, see In-Out Parameters. Property Wrappers A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property. For example, if you have properties that provide thread- safety checks or store their underlying data in a database, you have to write that code on every property. When you use a property wrapper, you write the management code once when you define the PDF conversion courtesy of www.appsdissected.com wrapper, and then reuse that management code by applying it to multiple properties. To define a property wrapper, you make a structure, enumeration, or class that defines a wrappedValue property. In the code below, the TwelveOrLess structure ensures that the value it wraps always contains a number less than or equal to 12. If you ask it to store a larger number, it stores 12 instead. 1 @propertyWrapper 2 struct TwelveOrLess { 3 private var number = 0 4 var wrappedValue: Int { 5 get { return number } 6 set { number = min(newValue, 12) } 7 } 8 } The setter ensures that new values are less than or equal to 12, and the getter returns the stored value. NOTE The declaration for number in the example above marks the variable as private, which ensures number is used only in the implementation of TwelveOrLess. Code that’s written anywhere else accesses the value using the getter and setter for wrappedValue, and can’t use number directly. For information about private, see Access Control. You apply a wrapper to a property by writing the wrapper’s name before the property as an attribute. Here’s a structure that stores a rectangle that uses the TwelveOrLess property wrapper to ensure its dimensions are always 12 or less: PDF conversion courtesy of www.appsdissected.com 1 struct SmallRectangle { 2 @TwelveOrLess var height: Int 3 @TwelveOrLess var width: Int 4 } 5 6 var rectangle = SmallRectangle() 7 print(rectangle.height) 8 // Prints "0" 9 10 rectangle.height = 10 11 print(rectangle.height) 12 // Prints "10" 13 14 rectangle.height = 24 15 print(rectangle.height) 16 // Prints "12" The height and width properties get their initial values from the definition of TwelveOrLess, which sets TwelveOrLess.number to zero. The setter in TwelveOrLess treats 10 as a valid value so storing the number 10 in rectangle.height proceeds as written. However, 24 is larger than TwelveOrLess allows, so trying to store 24 end up setting rectangle.height to 12 instead, the largest allowed value. When you apply a wrapper to a property, the compiler synthesizes code that provides storage for the wrapper and code that provides access to the property through the wrapper. (The property wrapper is responsible for storing the wrapped value, so there’s no synthesized code for that.) You could write code that uses the behavior of a property wrapper, without taking advantage of the special attribute PDF conversion courtesy of www.appsdissected.com syntax. For example, here’s a version of SmallRectangle from the previous code listing that wraps its properties in the TwelveOrLess structure explicitly, instead of writing @TwelveOrLess as an attribute: 1 struct SmallRectangle { 2 private var _height = TwelveOrLess() 3 private var _width = TwelveOrLess() 4 var height: Int { 5 get { return _height.wrappedValue } 6 set { _height.wrappedValue = newValue } 7 } 8 var width: Int { 9 get { return _width.wrappedValue } 10 set { _width.wrappedValue = newValue } 11 } 12 } The _height and _width properties store an instance of the property wrapper, TwelveOrLess. The getter and setter for height and width wrap access to the wrappedValue property. Setting Initial Values for Wrapped Properties The code in the examples above sets the initial value for the wrapped property by giving number an initial value in the definition of TwelveOrLess. Code that uses this property wrapper can’t specify a different initial value for a property that’s wrapped by TwelveOrLess— for example, the definition of SmallRectangle can’t give height or width initial values. To support setting an initial value or other customization, the property wrapper needs to add an initializer. PDF conversion courtesy of www.appsdissected.com Here’s an expanded version of TwelveOrLess called SmallNumber that defines initializers that set the wrapped and maximum value: 1 @propertyWrapper 2 struct SmallNumber { 3 private var maximum: Int 4 private var number: Int 5 6 var wrappedValue: Int { 7 get { return number } 8 set { number = min(newValue, maximum) } 9 } 10 11 init() { 12 maximum = 12 13 number = 0 14 } 15 init(wrappedValue: Int) { 16 maximum = 12 17 number = min(wrappedValue, maximum) 18 } 19 init(wrappedValue: Int, maximum: Int) { 20 self.maximum = maximum 21 number = min(wrappedValue, maximum) 22 } 23 } PDF conversion courtesy of www.appsdissected.com The definition of SmallNumber includes three initializers—init(), init(wrappedValue:), and init(wrappedValue:maximum:)—which the examples below use to set the wrapped value and the maximum value. For information about initialization and initializer syntax, see Initialization. When you apply a wrapper to a property and you don’t specify an initial value, Swift uses the init() initializer to set up the wrapper. For example: 1 struct ZeroRectangle { 2 @SmallNumber var height: Int 3 @SmallNumber var width: Int 4 } 5 6 var zeroRectangle = ZeroRectangle() 7 print(zeroRectangle.height, zeroRectangle.width) 8 // Prints "0 0" The instances of SmallNumber that wrap height and width are created by calling SmallNumber(). The code inside that initializer sets the initial wrapped value and the initial maximum value, using the default values of zero and 12. The property wrapper still provides all of the initial values, like the earlier example that used TwelveOrLess in SmallRectangle. Unlike that example, SmallNumber also supports writing those initial values as part of declaring the property. When you specify an initial value for the property, Swift uses the init(wrappedValue:) initializer to set up the wrapper. For example: PDF conversion courtesy of www.appsdissected.com 1 struct UnitRectangle { 2 @SmallNumber var height: Int = 1 3 @SmallNumber var width: Int = 1 4 } 5 6 var unitRectangle = UnitRectangle() 7 print(unitRectangle.height, unitRectangle.width) 8 // Prints "1 1" When you write = 1 on a property with a wrapper, that’s translated into a call to the init(wrappedValue:) initializer. The instances of SmallNumber that wrap height and width are created by calling SmallNumber(wrappedValue: 1). The initializer uses the wrapped value that’s specified here, and it uses the default maximum value of 12. When you write arguments in parentheses after the custom attribute, Swift uses the initializer that accepts those arguments to set up the wrapper. For example, if you provide an initial value and a maximum value, Swift uses the init(wrappedValue:maximum:) initializer: PDF conversion courtesy of www.appsdissected.com 1 struct NarrowRectangle { 2 @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int 3 @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int 4 } 5 6 var narrowRectangle = NarrowRectangle() 7 print(narrowRectangle.height, narrowRectangle.width) 8 // Prints "2 3" 9 10 narrowRectangle.height = 100 11 narrowRectangle.width = 100 12 print(narrowRectangle.height, narrowRectangle.width) 13 // Prints "5 4" The instance of SmallNumber that wraps height is created by calling SmallNumber(wrappedValue: 2, maximum: 5), and the instance that wraps width is created by calling SmallNumber(wrappedValue: 3, maximum: 4). By including arguments to the property wrapper, you can set up the initial state in the wrapper or pass other options to the wrapper when it’s created. This syntax is the most general way to use a property wrapper. You can provide whatever arguments you need to the attribute, and they’re passed to the initializer. When you include property wrapper arguments, you can also specify an initial value using assignment. Swift treats the assignment like a PDF conversion courtesy of www.appsdissected.com wrappedValue argument and uses the initializer that accepts the arguments you include. For example: 1 struct MixedRectangle { 2 @SmallNumber var height: Int = 1 3 @SmallNumber(maximum: 9) var width: Int = 2 4 } 5 6 var mixedRectangle = MixedRectangle() 7 print(mixedRectangle.height) 8 // Prints "1" 9 10 mixedRectangle.height = 20 11 print(mixedRectangle.height) 12 // Prints "12" The instance of SmallNumber that wraps height is created by calling SmallNumber(wrappedValue: 1), which uses the default maximum value of 12. The instance that wraps width is created by calling SmallNumber(wrappedValue: 2, maximum: 9). Projecting a Value From a Property Wrapper In addition to the wrapped value, a property wrapper can expose additional functionality by defining a projected value—for example, a property wrapper that manages access to a database can expose a flushDatabaseConnection() method on its projected value. The name of the projected value is the same as the wrapped value, except it begins with a dollar sign ($). Because your code can’t define PDF conversion courtesy of www.appsdissected.com properties that start with $ the projected value never interferes with properties you define. In the SmallNumber example above, if you try to set the property to a number that’s too large, the property wrapper adjusts the number before storing it. The code below adds a projectedValue property to the SmallNumber structure to keep track of whether the property wrapper adjusted the new value for the property before storing that new value. PDF conversion courtesy of www.appsdissected.com 1 @propertyWrapper 2 struct SmallNumber { 3 private var number: Int 4 private(set) var projectedValue: Bool 5 6 var wrappedValue: Int { 7 get { return number } 8 set { 9 if newValue > 12 { 10 number = 12 11 projectedValue = true 12 } else { 13 number = newValue 14 projectedValue = false 15 } 16 } 17 } 18 19 init() { 20 self.number = 0 21 self.projectedValue = false 22 } 23 } 24 struct SomeStructure { 25 @SmallNumber var someNumber: Int 26 } PDF conversion courtesy of www.appsdissected.com 27 var someStructure = SomeStructure() 28 29 someStructure.someNumber = 4 30 print(someStructure.$someNumber) 31 // Prints "false" 32 33 someStructure.someNumber = 55 34 print(someStructure.$someNumber) 35 // Prints "true" Writing someStructure.$someNumber accesses the wrapper’s projected value. After storing a small number like four, the value of someStructure.$someNumber is false. However, the projected value is true after trying to store a number that’s too large, like 55. A property wrapper can return a value of any type as its projected value. In this example, the property wrapper exposes only one piece of information—whether the number was adjusted—so it exposes that Boolean value as its projected value. A wrapper that needs to expose more information can return an instance of some other data type, or it can return self to expose the instance of the wrapper as its projected value. When you access a projected value from code that’s part of the type, like a property getter or an instance method, you can omit self. before the property name, just like accessing other properties. The code in the following example refers to the projected value of the wrapper around height and width as $height and $width: PDF conversion courtesy of www.appsdissected.com 1 enum Size { 2 case small, large 3 } 4 5 struct SizedRectangle { 6 @SmallNumber var height: Int 7 @SmallNumber var width: Int 8 9 mutating func resize(to size: Size) -> Bool { 10 switch size { 11 case.small: 12 height = 10 13 width = 20 14 case.large: 15 height = 100 16 width = 100 17 } 18 return $height || $width 19 } 20 } Because property wrapper syntax is just syntactic sugar for a property with a getter and a setter, accessing height and width behaves the same as accessing any other property. For example, the code in resize(to:) accesses height and width using their property wrapper. If you call resize(to:.large), the switch case for.large sets the rectangle’s height and width to 100. The wrapper prevents the value of those properties from being larger than 12, and it sets the PDF conversion courtesy of www.appsdissected.com projected value to true, to record the fact that it adjusted their values. At the end of resize(to:), the return statement checks $height and $width to determine whether the property wrapper adjusted either height or width. Global and Local Variables The capabilities described above for computing and observing properties are also available to global variables and local variables. Global variables are variables that are defined outside of any function, method, closure, or type context. Local variables are variables that are defined within a function, method, or closure context. The global and local variables you have encountered in previous chapters have all been stored variables. Stored variables, like stored properties, provide storage for a value of a certain type and allow that value to be set and retrieved. However, you can also define computed variables and define observers for stored variables, in either a global or local scope. Computed variables calculate their value, rather than storing it, and they’re written in the same way as computed properties. NOTE Global constants and variables are always computed lazily, in a similar manner to Lazy Stored Properties. Unlike lazy stored properties, global constants and variables don’t need to be marked with the lazy modifier. Local constants and variables are never computed lazily. You can apply a property wrapper to a local stored variable, but not to a global variable or a computed variable. For example, in the code PDF conversion courtesy of www.appsdissected.com below, myNumber uses SmallNumber as a property wrapper. 1 func someFunction() { 2 @SmallNumber var myNumber: Int = 0 3 4 myNumber = 10 5 // now myNumber is 10 6 7 myNumber = 24 8 // now myNumber is 12 9 } Like when you apply SmallNumber to a property, setting the value of myNumber to 10 is valid. Because the property wrapper doesn’t allow values higher than 12, it sets myNumber to 12 instead of 24. Type Properties Instance properties are properties that belong to an instance of a particular type. Every time you create a new instance of that type, it has its own set of property values, separate from any other instance. You can also define properties that belong to the type itself, not to any one instance of that type. There will only ever be one copy of these properties, no matter how many instances of that type you create. These kinds of properties are called type properties. Type properties are useful for defining values that are universal to all instances of a particular type, such as a constant property that all PDF conversion courtesy of www.appsdissected.com instances can use (like a static constant in C), or a variable property that stores a value that’s global to all instances of that type (like a static variable in C). Stored type properties can be variables or constants. Computed type properties are always declared as variable properties, in the same way as computed instance properties. NOTE Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself doesn’t have an initializer that can assign a value to a stored type property at initialization time. Stored type properties are lazily initialized on their first access. They’re guaranteed to be initialized only once, even when accessed by multiple threads simultaneously, and they don’t need to be marked with the lazy modifier. Type Property Syntax In C and Objective-C, you define static constants and variables associated with a type as global static variables. In Swift, however, type properties are written as part of the type’s definition, within the type’s outer curly braces, and each type property is explicitly scoped to the type it supports. You define type properties with the static keyword. For computed type properties for class types, you can use the class keyword instead to allow subclasses to override the superclass’s implementation. The example below shows the syntax for stored and computed type properties: PDF conversion courtesy of www.appsdissected.com 1 struct SomeStructure { 2 static var storedTypeProperty = "Some value." 3 static var computedTypeProperty: Int { 4 return 1 5 } 6 } 7 enum SomeEnumeration { 8 static var storedTypeProperty = "Some value." 9 static var computedTypeProperty: Int { 10 return 6 11 } 12 } 13 class SomeClass { 14 static var storedTypeProperty = "Some value." 15 static var computedTypeProperty: Int { 16 return 27 17 } 18 class var overrideableComputedTypeProperty: Int { 19 return 107 20 } 21 } NOTE The computed type property examples above are for read-only computed type properties, but you can also define read-write computed type properties with the same syntax as for computed instance properties. PDF conversion courtesy of www.appsdissected.com Querying and Setting Type Properties Type properties are queried and set with dot syntax, just like instance properties. However, type properties are queried and set on the type, not on an instance of that type. For example: 1 print(SomeStructure.storedTypeProperty) 2 // Prints "Some value." 3 SomeStructure.storedTypeProperty = "Another value." 4 print(SomeStructure.storedTypeProperty) 5 // Prints "Another value." 6 print(SomeEnumeration.computedTypeProperty) 7 // Prints "6" 8 print(SomeClass.computedTypeProperty) 9 // Prints "27" The examples that follow use two stored type properties as part of a structure that models an audio level meter for a number of audio channels. Each channel has an integer audio level between 0 and 10 inclusive. The figure below illustrates how two of these audio channels can be combined to model a stereo audio level meter. When a channel’s audio level is 0, none of the lights for that channel are lit. When the audio level is 10, all of the lights for that channel are lit. In this figure, the left channel has a current level of 9, and the right channel has a current level of 7: PDF conversion courtesy of www.appsdissected.com The audio channels described above are represented by instances of the AudioChannel structure: PDF conversion courtesy of www.appsdissected.com 1 struct AudioChannel { 2 static let thresholdLevel = 10 3 static var maxInputLevelForAllChannels = 0 4 var currentLevel: Int = 0 { 5 didSet { 6 if currentLevel > AudioChannel.thresholdLevel { 7 // cap the new audio level to the threshold level 8 currentLevel = AudioChannel.thresholdLevel 9 } 10 if currentLevel > AudioChannel.maxInputLevelForAllChannels { 11 // store this as the new overall maximum input level 12 AudioChannel.maxInputLevelForAllChannels = currentLevel 13 } 14 } 15 } 16 } The AudioChannel structure defines two stored type properties to support its functionality. The first, thresholdLevel, defines the maximum threshold value an audio level can take. This is a constant PDF conversion courtesy of www.appsdissected.com value of 10 for all AudioChannel instances. If an audio signal comes in with a higher value than 10, it will be capped to this threshold value (as described below). The second type property is a variable stored property called maxInputLevelForAllChannels. This keeps track of the maximum input value that has been received by any AudioChannel instance. It starts with an initial value of 0. The AudioChannel structure also defines a stored instance property called currentLevel, which represents the channel’s current audio level on a scale of 0 to 10. The currentLevel property has a didSet property observer to check the value of currentLevel whenever it’s set. This observer performs two checks: If the new value of currentLevel is greater than the allowed thresholdLevel, the property observer caps currentLevel to thresholdLevel. If the new value of currentLevel (after any capping) is higher than any value previously received by any AudioChannel instance, the property observer stores the new currentLevel value in the maxInputLevelForAllChannels type property. NOTE In the first of these two checks, the didSet observer sets currentLevel to a different value. This doesn’t, however, cause the observer to be called again. You can use the AudioChannel structure to create two new audio channels called leftChannel and rightChannel, to represent the audio levels of a stereo sound system: PDF conversion courtesy of www.appsdissected.com 1 var leftChannel = AudioChannel() 2 var rightChannel = AudioChannel() If you set the currentLevel of the left channel to 7, you can see that the maxInputLevelForAllChannels type property is updated to equal 7: 1 leftChannel.currentLevel = 7 2 print(leftChannel.currentLevel) 3 // Prints "7" 4 print(AudioChannel.maxInputLevelForAllChannels) 5 // Prints "7" If you try to set the currentLevel of the right channel to 11, you can see that the right channel’s currentLevel property is capped to the maximum value of 10, and the maxInputLevelForAllChannels type property is updated to equal 10: 1 rightChannel.currentLevel = 11 2 print(rightChannel.currentLevel) 3 // Prints "10" 4 print(AudioChannel.maxInputLevelForAllChannels) 5 // Prints "10" PDF conversion courtesy of www.appsdissected.com Methods Methods are functions that are associated with a particular type. Classes, structures, and enumerations can all define instance methods, which encapsulate specific tasks and functionality for working with an instance of a given type. Classes, structures, and enumerations can also define type methods, which are associated with the type itself. Type methods are similar to class methods in Objective-C. The fact that structures and enumerations can define methods in Swift is a major difference from C and Objective-C. In Objective-C, classes are the only types that can define methods. In Swift, you can choose whether to define a class, structure, or enumeration, and still have the flexibility to define methods on the type you create. Instance Methods Instance methods are functions that belong to instances of a particular class, structure, or enumeration. They support the functionality of those instances, either by providing ways to access and modify instance properties, or by providing functionality related to the instance’s purpose. Instance methods have exactly the same syntax as functions, as described in Functions. You write an instance method within the opening and closing braces of the type it belongs to. An instance method has implicit access to all other instance methods and properties of that type. An instance method can be called only on a specific instance of the type it belongs to. It can’t be called in isolation without an existing instance. PDF conversion courtesy of www.appsdissected.com Here’s an example that defines a simple Counter class, which can be used to count the number of times an action occurs: 1 class Counter { 2 var count = 0 3 func increment() { 4 count += 1 5 } 6 func increment(by amount: Int) { 7 count += amount 8 } 9 func reset() { 10 count = 0 11 } 12 } The Counter class defines three instance methods: increment() increments the counter by 1. increment(by: Int) increments the counter by a specified integer amount. reset() resets the counter to zero. The Counter class also declares a variable property, count, to keep track of the current counter value. You call instance methods with the same dot syntax as properties: PDF conversion courtesy of www.appsdissected.com 1 let counter = Counter() 2 // the initial counter value is 0 3 counter.increment() 4 // the counter's value is now 1 5 counter.increment(by: 5) 6 // the counter's value is now 6 7 counter.reset() 8 // the counter's value is now 0 Function parameters can have both a name (for use within the function’s body) and an argument label (for use when calling the function), as described in Function Argument Labels and Parameter Names. The same is true for method parameters, because methods are just functions that are associated with a type. The self Property Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself. You use the self property to refer to the current instance within its own instance methods. The increment() method in the example above could have been written like this: 1 func increment() { 2 self.count += 1 3 } In practice, you don’t need to write self in your code very often. If you don’t explicitly write self, Swift assumes that you are referring to a property or method of the current instance whenever you use a PDF conversion courtesy of www.appsdissected.com known property or method name within a method. This assumption is demonstrated by the use of count (rather than self.count) inside the three instance methods for Counter. The main exception to this rule occurs when a parameter name for an instance method has the same name as a property of that instance. In this situation, the parameter name takes precedence, and it becomes necessary to refer to the property in a more qualified way. You use the self property to distinguish between the parameter name and the property name. Here, self disambiguates between a method parameter called x and an instance property that’s also called x: 1 struct Point { 2 var x = 0.0, y = 0.0 3 func isToTheRightOf(x: Double) -> Bool { 4 return self.x > x 5 } 6 } 7 let somePoint = Point(x: 4.0, y: 5.0) 8 if somePoint.isToTheRightOf(x: 1.0) { 9 print("This point is to the right of the line where x == 1.0") 10 } 11 // Prints "This point is to the right of the line where x == 1.0" Without the self prefix, Swift would assume that both uses of x referred to the method parameter called x. PDF conversion courtesy of www.appsdissected.com Modifying Value Types from Within Instance Methods Structures and enumerations are value types. By default, the properties of a value type can’t be modified from within its instance methods. However, if you need to modify the properties of your structure or enumeration within a particular method, you can opt in to mutating behavior for that method. The method can then mutate (that is, change) its properties from within the method, and any changes that it makes are written back to the original structure when the method ends. The method can also assign a completely new instance to its implicit self property, and this new instance will replace the existing one when the method ends. You can opt in to this behavior by placing the mutating keyword before the func keyword for that method: 1 struct Point { 2 var x = 0.0, y = 0.0 3 mutating func moveBy(x deltaX: Double, y deltaY: Double) { 4 x += deltaX 5 y += deltaY 6 } 7 } 8 var somePoint = Point(x: 1.0, y: 1.0) 9 somePoint.moveBy(x: 2.0, y: 3.0) 10 print("The point is now at (\(somePoint.x), \ (somePoint.y))") 11 // Prints "The point is now at (3.0, 4.0)" PDF conversion courtesy of www.appsdissected.com The Point structure above defines a mutating moveBy(x:y:) method, which moves a Point instance by a certain amount. Instead of returning a new point, this method actually modifies the point on which it’s called. The mutating keyword is added to its definition to enable it to modify its properties. Note that you can’t call a mutating method on a constant of structure type, because its properties can’t be changed, even if they’re variable properties, as described in Stored Properties of Constant Structure Instances: 1 let fixedPoint = Point(x: 3.0, y: 3.0) 2 fixedPoint.moveBy(x: 2.0, y: 3.0) 3 // this will report an error Assigning to self Within a Mutating Method Mutating methods can assign an entirely new instance to the implicit self property. The Point example shown above could have been written in the following way instead: 1 struct Point { 2 var x = 0.0, y = 0.0 3 mutating func moveBy(x deltaX: Double, y deltaY: Double) { 4 self = Point(x: x + deltaX, y: y + deltaY) 5 } 6 } This version of the mutating moveBy(x:y:) method creates a new structure whose x and y values are set to the target location. The end PDF conversion courtesy of www.appsdissected.com result of calling this alternative version of the method will be exactly the same as for calling the earlier version. Mutating methods for enumerations can set the implicit self parameter to be a different case from the same enumeration: 1 enum TriStateSwitch { 2 case off, low, high 3 mutating func next() { 4 switch self { 5 case.off: 6 self =.low 7 case.low: 8 self =.high 9 case.high: 10 self =.off 11 } 12 } 13 } 14 var ovenLight = TriStateSwitch.low 15 ovenLight.next() 16 // ovenLight is now equal to.high 17 ovenLight.next() 18 // ovenLight is now equal to.off This example defines an enumeration for a three-state switch. The switch cycles between three different power states (off, low and high) every time its next() method is called. PDF conversion courtesy of www.appsdissected.com Type Methods Instance methods, as described above, are methods that you call on an instance of a particular type. You can also define methods that are called on the type itself. These kinds of methods are called type methods. You indicate type methods by writing the static keyword before the method’s func keyword. Classes can use the class keyword instead, to allow subclasses to override the superclass’s implementation of that method. NOTE In Objective-C, you can define type-level methods only for Objective-C classes. In Swift, you can define type-level methods for all classes, structures, and enumerations. Each type method is explicitly scoped to the type it supports. Type methods are called with dot syntax, like instance methods. However, you call type methods on the type, not on an instance of that type. Here’s how you call a type method on a class called SomeClass: 1 class SomeClass { 2 class func someTypeMethod() { 3 // type method implementation goes here 4 } 5 } 6 SomeClass.someTypeMethod() Within the body of a type method, the implicit self property refers to the type itself, rather than an instance of that type. This means that you can use self to disambiguate between type properties and type method parameters, just as you do for instance properties and instance method parameters. PDF conversion courtesy of www.appsdissected.com More generally, any unqualified method and property names that you use within the body of a type method will refer to other type-level methods and properties. A type method can call another type method with the other method’s name, without needing to prefix it with the type name. Similarly, type methods on structures and enumerations can access type properties by using the type property’s name without a type name prefix. The example below defines a structure called LevelTracker, which tracks a player’s progress through the different levels or stages of a game. It’s a single-player game, but can store information for multiple players on a single device. All of the game’s levels (apart from level one) are locked when the game is first played. Every time a player finishes a level, that level is unlocked for all players on the device. The LevelTracker structure uses type properties and methods to keep track of which levels of the game have been unlocked. It also tracks the current level for an individual player. PDF conversion courtesy of www.appsdissected.com 1 struct LevelTracker { 2 static var highestUnlockedLevel = 1 3 var currentLevel = 1 4 5 static func unlock(_ level: Int) { 6 if level > highestUnlockedLevel { highestUnlockedLevel = level } 7 } 8 9 static func isUnlocked(_ level: Int) -> Bool { 10 return level Bool { 15 if LevelTracker.isUnlocked(level) { 16 currentLevel = level 17 return true 18 } else { 19 return false 20 } 21 } 22 } The LevelTracker structure keeps track of the highest level that any player has unlocked. This value is stored in a type property called highestUnlockedLevel. PDF conversion courtesy of www.appsdissected.com LevelTracker also defines two type functions to work with the highestUnlockedLevel property. The first is a type function called unlock(_:), which updates the value of highestUnlockedLevel whenever a new level is unlocked. The second is a convenience type function called isUnlocked(_:), which returns true if a particular level number is already unlocked. (Note that these type methods can access the highestUnlockedLevel type property without your needing to write it as LevelTracker.highestUnlockedLevel.) In addition to its type property and type methods, LevelTracker tracks an individual player’s progress through the game. It uses an instance property called currentLevel to track the level that a player is currently playing. To help manage the currentLevel property, LevelTracker defines an instance method called advance(to:). Before updating currentLevel, this method checks whether the requested new level is already unlocked. The advance(to:) method returns a Boolean value to indicate whether or not it was actually able to set currentLevel. Because it’s not necessarily a mistake for code that calls the advance(to:) method to ignore the return value, this function is marked with the @discardableResult attribute. For more information about this attribute, see Attributes. The LevelTracker structure is used with the Player class, shown below, to track and update the progress of an individual player: PDF conversion courtesy of www.appsdissected.com 1 class Player { 2 var tracker = LevelTracker() 3 let playerName: String 4 func complete(level: Int) { 5 LevelTracker.unlock(level + 1) 6 tracker.advance(to: level + 1) 7 } 8 init(name: String) { 9 playerName = name 10 } 11 } The Player class creates a new instance of LevelTracker to track that player’s progress. It also provides a method called complete(level:), which is called whenever a player completes a particular level. This method unlocks the next level for all players and updates the player’s progress to move them to the next level. (The Boolean return value of advance(to:) is ignored, because the level is known to have been unlocked by the call to LevelTracker.unlock(_:) on the previous line.) You can create an instance of the Player class for a new player, and see what happens when the player completes level one: 1 var player = Player(name: "Argyrios") 2 player.complete(level: 1) 3 print("highest unlocked level is now \ (LevelTracker.highestUnlockedLevel)") 4 // Prints "highest unlocked level is now 2" PDF conversion courtesy of www.appsdissected.com If you create a second player, whom you try to move to a level that’s not yet unlocked by any player in the game, the attempt to set the player’s current level fails: 1 player = Player(name: "Beto") 2 if player.tracker.advance(to: 6) { 3 print("player is now on level 6") 4 } else { 5 print("level 6 hasn't yet been unlocked") 6 } 7 // Prints "level 6 hasn't yet been unlocked" PDF conversion courtesy of www.appsdissected.com Subscripts Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection, list, or sequence. You use subscripts to set and retrieve values by index without needing separate methods for setting and retrieval. For example, you access elements in an Array instance as someArray[index] and elements in a Dictionary instance as someDictionary[key]. You can define multiple subscripts for a single type, and the appropriate subscript overload to use is selected based on the type of index value you pass to the subscript. Subscripts aren’t limited to a single dimension, and you can define subscripts with multiple input parameters to suit your custom type’s needs. Subscript Syntax Subscripts enable you to query instances of a type by writing one or more values in square brackets after the instance name. Their syntax is similar to both instance method syntax and computed property syntax. You write subscript definitions with the subscript keyword, and specify one or more input parameters and a return type, in the same way as instance methods. Unlike instance methods, subscripts can be read-write or read-only. This behavior is communicated by a getter and setter in the same way as for computed properties: PDF conversion courtesy of www.appsdissected.com 1 subscript(index: Int) -> Int { 2 get { 3 // Return an appropriate subscript value here. 4 } 5 set(newValue) { 6 // Perform a suitable setting action here. 7 } 8 } The type of newValue is the same as the return value of the subscript. As with computed properties, you can choose not to specify the setter’s (newValue) parameter. A default parameter called newValue is provided to your setter if you don’t provide one yourself. As with read-only computed properties, you can simplify the declaration of a read-only subscript by removing the get keyword and its braces: 1 subscript(index: Int) -> Int { 2 // Return an appropriate subscript value here. 3 } Here’s an example of a read-only subscript implementation, which defines a TimesTable structure to represent an n-times-table of integers: PDF conversion courtesy of www.appsdissected.com 1 struct TimesTable { 2 let multiplier: Int 3 subscript(index: Int) -> Int { 4 return multiplier * index 5 } 6 } 7 let threeTimesTable = TimesTable(multiplier: 3) 8 print("six times three is \(threeTimesTable)") 9 // Prints "six times three is 18" In this example, a new instance of TimesTable is created to represent the three-times-table. This is indicated by passing a value of 3 to the structure’s initializer as the value to use for the instance’s multiplier parameter. You can query the threeTimesTable instance by calling its subscript, as shown in the call to threeTimesTable. This requests the sixth entry in the three-times-table, which returns a value of 18, or 3 times 6. NOTE An n-times-table is based on a fixed mathematical rule. It isn’t appropriate to set threeTimesTable[someIndex] to a new value, and so the subscript for TimesTable is defined as a read-only subscript. Subscript Usage The exact meaning of “subscript” depends on the context in which it’s used. Subscripts are typically used as a shortcut for accessing the PDF conversion courtesy of www.appsdissected.com member elements in a collection, list, or sequence. You are free to implement subscripts in the most appropriate way for your particular class or structure’s functionality. For example, Swift’s Dictionary type implements a subscript to set and retrieve the values stored in a Dictionary instance. You can set a value in a dictionary by providing a key of the dictionary’s key type within subscript brackets, and assigning a value of the dictionary’s value type to the subscript: 1 var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] 2 numberOfLegs["bird"] = 2 The example above defines a variable called numberOfLegs and initializes it with a dictionary literal containing three key-value pairs. The type of the numberOfLegs dictionary is inferred to be [String: Int]. After creating the dictionary, this example uses subscript assignment to add a String key of "bird" and an Int value of 2 to the dictionary. For more information about Dictionary subscripting, see Accessing and Modifying a Dictionary. NOTE Swift’s Dictionary type implements its key-value subscripting as a subscript that takes and returns an optional type. For the numberOfLegs dictionary above, the key-value subscript takes and returns a value of type Int?, or “optional int”. The Dictionary type uses an optional subscript type to model the fact that not every key will have a value, and to give a way to delete a value for a key by assigning a nil value for that key. Subscript Options PDF conversion courtesy of www.appsdissected.com Subscripts can take any number of input parameters, and these input parameters can be of any type. Subscripts can also return a value of any type. Like functions, subscripts can take a varying number of parameters and provide default values for their parameters, as discussed in Variadic Parameters and Default Parameter Values. However, unlike functions, subscripts can’t use in-out parameters. A class or structure can provide as many subscript implementations as it needs, and the appropriate subscript to be used will be inferred based on the types of the value or values that are contained within the subscript brackets at the point that the subscript is used. This definition of multiple subscripts is known as subscript overloading. While it’s most common for a subscript to take a single parameter, you can also define a subscript with multiple parameters if it’s appropriate for your type. The following example defines a Matrix structure, which represents a two-dimensional matrix of Double values. The Matrix structure’s subscript takes two integer parameters: PDF conversion courtesy of www.appsdissected.com 1 struct Matrix { 2 let rows: Int, columns: Int 3 var grid: [Double] 4 init(rows: Int, columns: Int) { 5 self.rows = rows 6 self.columns = columns 7 grid = Array(repeating: 0.0, count: rows * columns) 8 } 9 func indexIsValid(row: Int, column: Int) -> Bool { 10 return row >= 0 && row < rows && column >= 0 && column < columns 11 } 12 subscript(row: Int, column: Int) -> Double { 13 get { 14 assert(indexIsValid(row: row, column: column), "Index out of range") 15 return grid[(row * columns) + column] 16 } 17 set { 18 assert(indexIsValid(row: row, column: column), "Index out of range") 19 grid[(row * columns) + column] = newValue 20 } PDF conversion courtesy of www.appsdissected.com 21 } 22 } Matrix provides an initializer that takes two parameters called rows and columns, and creates an array that’s large enough to store rows * columns values of type Double. Each position in the matrix is given an initial value of 0.0. To achieve this, the array’s size, and an initial cell value of 0.0, are passed to an array initializer that creates and initializes a new array of the correct size. This initializer is described in more detail in Creating an Array with a Default Value. You can construct a new Matrix instance by passing an appropriate row and column count to its initializer: var matrix = Matrix(rows: 2, columns: 2) The example above creates a new Matrix instance with two rows and two columns. The grid array for this Matrix instance is effectively a flattened version of the matrix, as read from top left to bottom right: PDF conversion courtesy of www.appsdissected.com Values in the matrix can be set by passing row and column values into the subscript, separated by a comma: 1 matrix[0, 1] = 1.5 2 matrix[1, 0] = 3.2 These two statements call the subscript’s setter to set a value of 1.5 in the top right position of the matrix (where row is 0 and column is 1), and 3.2 in the bottom left position (where row is 1 and column is 0): The Matrix subscript’s getter and setter both contain an assertion to check that the subscript’s row and column values are valid. To assist with these assertions, Matrix includes a convenience method called indexIsValid(row:column:), which checks whether the requested row and column are inside the bounds of the matrix: 1 func indexIsValid(row: Int, column: Int) -> Bool { 2 return row >= 0 && row < rows && column >= 0 && column < columns 3 } An assertion is triggered if you try to access a subscript that’s outside of the matrix bounds: PDF conversion courtesy of www.appsdissected.com 1 let someValue = matrix[2, 2] 2 // This triggers an assert, because [2, 2] is outside of the matrix bounds. Type Subscripts Instance subscripts, as described above, are subscripts that you call on an instance of a particular type. You can also define subscripts that are called on the type itself. This kind of subscript is called a type subscript. You indicate a type subscript by writing the static keyword before the subscript keyword. Classes can use the class keyword instead, to allow subclasses to override the superclass’s implementation of that subscript. The example below shows how you define and call a type subscript: 1 enum Planet: Int { 2 case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune 3 static subscript(n: Int) -> Planet { 4 return Planet(rawValue: n)! 5 } 6 } 7 let mars = Planet 8 print(mars) PDF conversion courtesy of www.appsdissected.com Inheritance A class can inherit methods, properties, and other characteristics from another class. When one class inherits from another, the inheriting class is known as a subclass, and the class it inherits from is known as its superclass. Inheritance is a fundamental behavior that differentiates classes from other types in Swift. Classes in Swift can call and access methods, properties, and subscripts belonging to their superclass and can provide their own overriding versions of those methods, properties, and subscripts to refine or modify their behavior. Swift helps to ensure your overrides are correct by checking that the override definition has a matching superclass definition. Classes can also add property observers to inherited properties in order to be notified when the value of a property changes. Property observers can be added to any property, regardless of whether it was originally defined as a stored or computed property. Defining a Base Class Any class that doesn’t inherit from another class is known as a base class. NOTE Swift classes don’t inherit from a universal base class. Classes you define without specifying a superclass automatically become base classes for you to build upon. PDF conversion courtesy of www.appsdissected.com The example below defines a base class called Vehicle. This base class defines a stored property called currentSpeed, with a default value of 0.0 (inferring a property type of Double). The currentSpeed property’s value is used by a read-only computed String property called description to create a description of the vehicle. The Vehicle base class also defines a method called makeNoise. This method doesn’t actually do anything for a base Vehicle instance, but will be customized by subclasses of Vehicle later on: 1 class Vehicle { 2 var currentSpeed = 0.0 3 var description: String { 4 return "traveling at \(currentSpeed) miles per hour" 5 } 6 func makeNoise() { 7 // do nothing - an arbitrary vehicle doesn't necessarily make a noise 8 } 9 } You create a new instance of Vehicle with initializer syntax, which is written as a type name followed by empty parentheses: let someVehicle = Vehicle() Having created a new Vehicle instance, you can access its description property to print a human-readable description of the vehicle’s current speed: PDF conversion courtesy of www.appsdissected.com 1 print("Vehicle: \(someVehicle.description)") 2 // Vehicle: traveling at 0.0 miles per hour The Vehicle class defines common characteristics for an arbitrary vehicle, but isn’t much use in itself. To make it more useful, you need to refine it to describe more specific kinds of vehicles. Subclassing Subclassing is the act of basing a new class on an existing class. The subclass inherits characteristics from the existing class, which you can then refine. You can also add new characteristics to the subclass. To indicate that a subclass has a superclass, write the subclass name before the superclass name, separated by a colon: 1 class SomeSubclass: SomeSuperclass { 2 // subclass definition goes here 3 } The following example defines a subclass called Bicycle, with a superclass of Vehicle: 1 class Bicycle: Vehicle { 2 var hasBasket = false 3 } The new Bicycle class automatically gains all of the characteristics of Vehicle, such as its currentSpeed and description properties and its PDF conversion courtesy of www.appsdissected.com makeNoise() method. In addition to the characteristics it inherits, the Bicycle class defines a new stored property, hasBasket, with a default value of false (inferring a type of Bool for the property). By default, any new Bicycle instance you create will not have a basket. You can set the hasBasket property to true for a particular Bicycle instance after that instance is created: 1 let bicycle = Bicycle() 2 bicycle.hasBasket = true You can also modify the inherited currentSpeed property of a Bicycle instance, and query the instance’s inherited description property: 1 bicycle.currentSpeed = 15.0 2 print("Bicycle: \(bicycle.description)") 3 // Bicycle: traveling at 15.0 miles per hour Subclasses can themselves be subclassed. The next example creates a subclass of Bicycle for a two-seater bicycle known as a “tandem”: 1 class Tandem: Bicycle { 2 var currentNumberOfPassengers = 0 3 } Tandem inherits all of the properties and methods from Bicycle, which in turn inherits all of the properties and methods from Vehicle. The Tandem subclass also adds a new stored property called currentNumberOfPassengers, with a default value of 0. PDF conversion courtesy of www.appsdissected.com If you create an instance of Tandem, you can work with any of its new and inherited properties, and query the read-only description property it inherits from Vehicle: 1 let tandem = Tandem() 2 tandem.hasBasket = true 3 tandem.currentNumberOfPassengers = 2 4 tandem.currentSpeed = 22.0 5 print("Tandem: \(tandem.description)") 6 // Tandem: traveling at 22.0 miles per hour Overriding A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass. This is known as overriding. To override a characteristic that would otherwise be inherited, you prefix your overriding definition with the override keyword. Doing so clarifies that you intend to provide an override and haven’t provided a matching definition by mistake. Overriding by accident can cause unexpected behavior, and any overrides without the override keyword are diagnosed as an error when your code is compiled. The override keyword also prompts the Swift compiler to check that your overriding class’s superclass (or one of its parents) has a declaration that matches the one you provided for the override. This check ensures that your overriding definition is correct. PDF conversion courtesy of www.appsdissected.com Accessing Superclass Methods, Properties, and Subscripts When you provide a method, property, or subscript override for a subclass, it’s sometimes useful to use the existing superclass implementation as part of your override. For example, you can refine the behavior of that existing implementation, or store a modified value in an existing inherited variable. Where this is appropriate, you access the superclass version of a method, property, or subscript by using the super prefix: An overridden method named someMethod() can call the superclass version of someMethod() by calling super.someMethod() within the overriding method implementation. An overridden property called someProperty can access the superclass version of someProperty as super.someProperty within the overriding getter or setter implementation. An overridden subscript for someIndex can access the superclass version of the same subscript as super[someIndex] from within the overriding subscript implementation. Overriding Methods You can override an inherited instance or type method to provide a tailored or alternative implementation of the method within your subclass. The following example defines a new subclass of Vehicle called Train, which overrides the makeNoise() method that Train inherits from Vehicle: PDF conversion courtesy of www.appsdissected.com 1 class Train: Vehicle { 2 override func makeNoise() { 3 print("Choo Choo") 4 } 5 } If you create a new instance of Train and call its makeNoise() method, you can see that the Train subclass version of the method is called: 1 let train = Train() 2 train.makeNoise() 3 // Prints "Choo Choo" Overriding Properties You can override an inherited instance or type property to provide your own custom getter and setter for that property, or to add property observers to enable the overriding property to observe when the underlying property value changes. Overriding Property Getters and Setters You can provide a custom getter (and setter, if appropriate) to override any inherited property, regardless of whether the inherited property is implemented as a stored or computed property at source. The stored or computed nature of an inherited property isn’t known by a subclass—it only knows that the inherited property has a certain name and type. You must always state both the name and the type of the property you are overriding, to enable the compiler to check that your override matches a superclass property with the same name and type. PDF conversion courtesy of www.appsdissected.com You can present an inherited read-only property as a read-write property by providing both a getter and a setter in your subclass property override. You can’t, however, present an inherited read-write property as a read-only property. NOTE If you provide a setter as part of a property override, you must also provide a getter for that override. If you don’t want to modify the inherited property’s value within the overriding getter, you can simply pass through the inherited value by returning super.someProperty from the getter, where someProperty is the name of the property you are overriding. The following example

Use Quizgecko on...
Browser
Browser