Wednesday, May 18, 2016

Learn the Essentials of Swift IOS

Your first lesson is presented in the form of a guided Swift playground, a type of file that lets you change and interact with the code directly in Xcode and see the result immediately. Playgrounds are great for learning and experimenting, and this one helps get you up to speed on fundamental Swift concepts. NOTE For the best experience, open this lesson as a playground in Xcode. image: ../Art/WWDR_download_icon_withPadding_2x.pngDownload Playground Learning Objectives At the end of the lesson, you’ll be able to: Differentiate between a constant and a variable Know when to use implicit and when to use explicit type declarations Understand the advantage of using optionals and optional binding Differentiate between optionals and implicitly unwrapped optionals Understand the purpose of conditional statements and loops Use switch statements for conditional branching beyond a binary condition Use where clauses to impose additional constraints in conditional statements Differentiate between functions, methods, and initializers Differentiate between classes, structures, and enumerations Understand syntax for (and basic concepts behind) inheritance and protocol conformance Determine implicit types and find additional information using Xcode’s quick help shortcut (Option-click) Import and use UIKit Basic Types A constant is a value that stays the same after it’s declared the first time, while a variable is a value that can change. A constant is referred to as immutable, meaning that it can’t be changed, and a variable is mutable. If you know that a value won’t need to be changed in your code, declare it as a constant instead of a variable. Use let to make a constant and var to make a variable. var myVariable = 42 myVariable = 50 let myConstant = 42 Every constant and variable in Swift has a type, but 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. This is called type inference. Once a constant or variable has a type, that type can’t be changed. If the initial value doesn’t provide enough information (or if there is no initial value), specify the type by writing it after the variable, separated by a colon. let implicitInteger = 70 let implicitDouble = 70.0 let explicitDouble: Double = 70 EXPERIMENT In Xcode, Option-click the name of a constant or variable to see its inferred type. Try doing that with the constants in the code above. 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. Here, you convert an Int to a String. let label = "The width is " let width = 94 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. This is known as string interpolation. let apples = 3 let oranges = 5 let appleSummary = "I have \(apples) apples." let fruitSummary = "I have \(apples + oranges) pieces of fruit." Use optionals to work with values that might be missing. An optional value either contains a value or contains nil (no value) to indicate that a value is missing. Write a question mark (?) after the type of a value to mark the value as optional. let optionalInt: Int? = 9 To get the underlying value from an optional, you unwrap it. You’ll learn unwrapping optionals later, but the most straightforward way to do it involves the force unwrap operator (!). Only use the unwrap operator if you’re sure the underlying value isn’t nil. let actualInt: Int = optionalInt! Optionals are pervasive in Swift, and are very useful for many situations where a value may or may not be present. They’re especially useful for attempted type conversions. var myString = "7" var possibleInt = Int(myString) print(possibleInt) In this code, the value of possibleInt is 7, because myString contains the value of an integer. But if you change myString to be something that can’t be converted to an integer, possibleInt becomes nil. myString = "banana" possibleInt = Int(myString) print(possibleInt) An array is a data type that keeps track of an ordered collection of items. Create arrays using brackets ([]), and access their elements by writing the index in brackets. Arrays start at index 0. var ratingList = ["Poor", "Fine", "Good", "Excellent"] ratingList[1] = "OK" ratingList To create an empty array, use the initializer syntax. You’ll learn more about initializers in a little while. // Creates an empty array. let emptyArray = [String]() You’ll notice that the code above has a comment. A comment is a piece of text in a source code file that doesn’t get compiled as part of the program but provides context or useful information about individual pieces of code. A single-line comment appears after two slashes (//) and a multiline comment appears between a set of slashes and asterisks (/* … */). You’ll see and write both types of comments throughout the source code in the lessons. An implicitly unwrapped optional is an optional that can also be used like a nonoptional value, without the need to unwrap the optional value each time it’s accessed. This is because an implicitly unwrapped optional is assumed to always have a value after that value is initially set, although the value can change. Implicitly unwrapped optional types are indicated with an exclamation mark (!) instead of a question mark (?). var implicitlyUnwrappedOptionalInt: Int! You’ll rarely need to create implicitly unwrapped optionals in your own code. More often, you’ll see them used to keep track of outlets between an interface and source code (which you’ll learn about in a later lesson) and in the APIs you’ll see throughout the lessons. Control Flow Swift has two types of control flow statements. Conditional statements, like if and switch, check whether a condition is true—that is, if its value evaluates to the Boolean true—before executing a piece of code. Loops, like for-in and while, execute the same piece of code multiple times. An if statement checks whether a certain condition is true, and if it is, the if statement evaluates the code inside the statement. You can add an else clause to an if statement to define more complex behavior. An else clause can be used to chain if statements together, or it can stand on its own, in which case the else clause is executed if none of the chained if statements evaluate to true. let number = 23 if number < 10 { print("The number is small") } else if number > 100 { print("The number is pretty big") } else { print("The number is between 10 and 100") } EXPERIMENT Change number to a different integer value to see how that affects which line prints. Statements can be nested to create complex, interesting behavior in a program. Here’s an example of an if statement with an else clause nested inside a for-in statement (which iterates through each item in a collection in order, one-by-one). let individualScores = [75, 43, 103, 87, 12] var teamScore = 0 for score in individualScores { if score > 50 { teamScore += 3 } else { teamScore += 1 } } print(teamScore) Use optional binding in an if statement to check whether an optional contains a value. var optionalName: String? = "John Appleseed" var greeting = "Hello!" if let name = optionalName { greeting = "Hello, \(name)" } 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. You can use a single if statement to bind multiple values. A where clause can be added to a case to further scope the conditional statement. In this case, the if statement executes only if the binding is successful for all of these values and all conditions are met. var optionalHello: String? = "Hello" if let hello = optionalHello where hello.hasPrefix("H"), let name = optionalName { greeting = "\(hello), \(name)" } Switches in Swift are quite powerful. A switch statement supports any kind of data and a wide variety of comparison operations—it isn’t limited to integers and tests for equality. In this example, the switch statement switches on the value of the vegetable string, comparing the value to each of its cases and executing the one that matches. let vegetable = "red pepper" switch vegetable { case "celery": let vegetableComment = "Add some raisins and make ants on a log." case "cucumber", "watercress": let vegetableComment = "That would make a good tea sandwich." case let x where x.hasSuffix("pepper"): let vegetableComment = "Is it a spicy \(x)?" default: let vegetableComment = "Everything tastes good in soup." } 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 that part of a pattern to a constant. Just like in an if statement, a where clause can be added to a case to further scope the conditional statement. However, unlike in an if statement, a switch case that has multiple conditions separated by commas executes when any of the conditions are met. 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 statement at the end of each case’s code. Switch statements must be exhaustive. A default case is required, unless it’s clear from the context that every possible case is satisfied, such as when the switch statement is switching on an enumeration. This requirement ensures that one of the switch cases always executes. You can keep an index in a loop by using a Range. Use the half-open range operator ( ..<) to make a range of indexes. var firstForLoop = 0 for i in 0..<4 { firstForLoop += i } print(firstForLoop) The half-open range operator (..<) doesn’t include the upper number, so this range goes from 0 to 3 for a total of four loop iterations. Use the closed range operator ( ...) to make a range that includes both values. var secondForLoop = 0 for _ in 0...4 { secondForLoop += 1 } print(secondForLoop) This range goes from 0 to 4 for a total of five loop iterations. The underscore (_) represents a wildcard, which you can use when you don’t need to know which iteration of the loop is currently executing. Functions and Methods A function is a reusable, named piece of code that can be referred to from many places in a program. Use func to declare a function. A function declaration can include zero or more parameters, written as name: Type, which are additional pieces of information that must be passed into the function when it’s called. Optionally, a function can have a return type, written after the ->, which indicates what the function returns as its result. A function’s implementation goes inside of a pair of curly braces ({}). func greet(name: String, day: String) -> String { return "Hello \(name), today is \(day)." } Call a function by following its name with a list of arguments (the values you pass in to satisfy a function’s parameters) in parentheses. When you call a function, you pass in the first argument value without writing its name, and every subsequent value with its name. greet("Anna", day: "Tuesday") greet("Bob", day: "Friday") greet("Charlie", day: "a nice day") Functions that are defined within a specific type are called methods. Methods are explicitly tied to the type they’re defined in, and can only be called on that type (or one of its subclasses, as you’ll learn about soon). In the earlier switch statement example, you saw a method that’s defined on the String type called hasSuffix(), shown again here: let exampleString = "hello" if exampleString.hasSuffix("lo") { print("ends in lo") } As you see, you call a method using the dot syntax. When you call a method, you pass in the first argument value without writing its name, and every subsequent value with its name. For example, this method on Array takes two parameters, and you only pass in the name for the second one: var array = ["apple", "banana", "dragonfruit"] array.insert("cherry", atIndex: 2) array Classes and Initializers In object-oriented programming, the behavior of a program is based largely on interactions between objects. An object is an instance of a class, which can be thought of as a blueprint for that object. Classes store additional information about themselves in the form of properties, and define their behavior using methods. Use class followed by the class’s name to define 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. This example declares a Shape class with a numberOfSides property and a simpleDescription() method. class Shape { var numberOfSides = 0 func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } Create an instance of a class—an object—by putting parentheses after the class name. Use dot syntax to access the properties and methods of the instance. Here, shape is an object that’s an instance of the Shape class. var shape = Shape() shape.numberOfSides = 7 var shapeDescription = shape.simpleDescription() This Shape class is missing something important: an initializer. An initializer is a method that prepares an instance of a class for use, which involves setting an initial value for each property and performing any other setup. Use init to create one. This example defines a new class, NamedShape, that has an initializer which takes in a name. class NamedShape { var numberOfSides = 0 var name: String init(name: String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } Notice how self is used to distinguish the name property from the name argument to the initializer. Every property needs a value assigned—either in its declaration (as with numberOfSides) or in the initializer (as with name). You don’t call an initializer by writing init; you call it by putting parentheses with the appropriate arguments after the class name. When you call an initializer, you include all arguments names along with their values. let namedShape = NamedShape(name: "my named shape") Classes inherit their behavior from their parent class. A class that inherits behavior from another class is called a subclass of that class, and the parent class is called a superclass. Subclasses include their superclass name after their class name, separated by a colon. A class can inherit from only one superclass, although that class can inherit from another superclass, and so on, resulting in a class hierarchy. 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 detects methods with override that don’t actually override any method in the superclass. This example defines the Square class, a subclass of NamedShape. class Square: NamedShape { var sideLength: Double init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides = 4 } func area() -> Double { return sideLength * sideLength } override func simpleDescription() -> String { return "A square with sides of length \(sideLength)." } } let testSquare = Square(sideLength: 5.2, name: "my test square") testSquare.area() testSquare.simpleDescription() Notice that the initializer for the Square class has three different steps: Setting the value of properties that the subclass, Square, declares. Calling the initializer of the superclass, NamedShape. Changing the value of properties defined by the superclass, NamedShape. Any additional setup work that uses methods, getters, or setters can also be done at this point. Sometimes, initialization of an object needs to fail, such as when the values supplied as the arguments are outside of a certain range, or when data that’s expected to be there is missing. Initializers that may fail to successfully initialize an object are called failable initializers. A failable initializer can return nil after initialization. Use init? to declare a failable initializer. class Circle: NamedShape { var radius: Double init?(radius: Double, name: String) { self.radius = radius super.init(name: name) numberOfSides = 1 if radius <= 0 { return nil } } override func simpleDescription() -> String { return "A circle with a radius of \(radius)." } } let successfulCircle = Circle(radius: 4.2, name: "successful circle") let failedCircle = Circle(radius: -7, name: "failed circle") Initializers can also have a number of keywords associated with them. A designated initializer does not require any keywords. This initializer acts as one of the primary initializers for a class; any initializer within a class must ultimately call through to a designated initializer. The convenience keyword next to an initializer indicates a convenience initializer. Convenience initializers are secondary initializers. They can add additional behavior or customization, but must eventually call through to a designated initializer. A required keyword next to an initializer indicates that every subclass of the class that has that initializer must implement its own version of the initializer (if it implements any initializer). Type casting is a way to check the type of an instance, and to treat that instance as if it’s a different superclass or subclass from somewhere else in its own class hierarchy. A constant or variable of a certain class type may actually refer to an instance of a subclass behind the scenes. Where you believe this is the case, you can try to downcast to the subclass type using a type cast operator. Because downcasting can fail, the type cast operator comes in two different forms. The optional form, as?, returns an optional value of the type you are trying to downcast to. The forced form, as!, attempts the downcast and force-unwraps the result as a single compound action. Use the optional type cast operator (as?) when you’re not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This lets you check for a successful downcast. Use the forced type cast operator (as!) only when you’re sure that the downcast will always succeed. This form of the operator will trigger a runtime error if you try to downcast to an incorrect class type. This example shows the use of the optional type cast operator (as?) to check whether a shape in an array of shapes is a square or a triangle. You increment the count of the squares and triangles variables by one each time the corresponding shape is found, printing the values at the end. class Triangle: NamedShape { init(sideLength: Double, name: String) { super.init(name: name) numberOfSides = 3 } } let shapesArray = [Triangle(sideLength: 1.5, name: "triangle1"), Triangle(sideLength: 4.2, name: "triangle2"), Square(sideLength: 3.2, name: "square1"), Square(sideLength: 2.7, name: "square2")] var squares = 0 var triangles = 0 for shape in shapesArray { if let square = shape as? Square { squares++ } else if let triangle = shape as? Triangle { triangles++ } } print("\(squares) squares and \(triangles) triangles.") EXPERIMENT Try replacing as? with as!. What error do you get? Enumerations and Structures Classes aren’t the only ways to define data types in Swift. Enumerations and structures have similar capabilities to classes, but can be useful in different contexts. Enumerations define a common type for a group of related values and enable you to work with those values in a type-safe way within your code. Enumerations can have methods associated with them. Use enum to create an enumeration. enum Rank: Int { case Ace = 1 case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten case Jack, Queen, King func simpleDescription() -> String { switch self { case .Ace: return "ace" case .Jack: return "jack" case .Queen: return "queen" case .King: return "king" default: return String(self.rawValue) } } } let ace = Rank.Ace let aceRawValue = ace.rawValue In the example above, the raw-value type of the enumeration is Int, so you have to specify only the first raw value. 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 member. Use the init?(rawValue:) initializer to make an instance of an enumeration from a raw value. if let convertedRank = Rank(rawValue: 3) { let threeDescription = convertedRank.simpleDescription() } The member 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. enum Suit { case Spades, Hearts, Diamonds, Clubs func simpleDescription() -> String { switch self { case .Spades: return "spades" case .Hearts: return "hearts" case .Diamonds: return "diamonds" case .Clubs: return "clubs" } } } let hearts = Suit.Hearts let heartsDescription = hearts.simpleDescription() Notice the two ways that the Hearts member of the enumeration is referred to above: When a value is assigned to the hearts constant, the enumeration member Suit.Hearts is referred to by its full name because the constant doesn’t have an explicit type specified. Inside the switch, the enumeration member is referred to by the abbreviated form .Hearts 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. 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 are passed around in your code, but classes are passed by reference. Structures are great for defining lightweight data types that don’t need to have capabilities like inheritance and type casting. Use struct to create a structure. struct Card { var rank: Rank var suit: Suit func simpleDescription() -> String { return "The \(rank.simpleDescription()) of \(suit.simpleDescription())" } } let threeOfSpades = Card(rank: .Three, suit: .Spades) let threeOfSpadesDescription = threeOfSpades.simpleDescription() Protocols A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol doesn’t actually provide an implementation for any of these requirements—it only describes what an implementation will look like. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol. Use protocol to declare a protocol. protocol ExampleProtocol { var simpleDescription: String { get } func adjust() } NOTE The { get } following the simpleDescription property indicates that it is read-only, meaning that the value of the property can be viewed, but never changed. Protocols can require that conforming types have specific instance properties, instance methods, type methods, operators, and subscripts. Protocols can require specific instance methods and type methods to be implemented by conforming types. These methods are written as part of the protocol’s definition in exactly the same way as for normal instance and type methods, but without curly braces or a method body. Classes, structures, and enumerations adopt a protocol by listing its name after their name, separated by a colon. A type can adopt any number of protocols, which appear in a comma-separated list. If a class has a superclass, the superclass’s name must appear first in the list, followed by protocols. You conform to the protocol by implementing all of its requirements. Here, SimpleClass adopts the ExampleProtocol protocol, and conforms to the protocol by implementing the simpleDescription property and adjust() method. class SimpleClass: ExampleProtocol { var simpleDescription: String = "A very simple class." var anotherProperty: Int = 69105 func adjust() { simpleDescription += " Now 100% adjusted." } } var a = SimpleClass() a.adjust() let aDescription = a.simpleDescription Protocols are first-class types, which means they can be treated like other named types. For example, you can create an ExampleProtocol array and call adjust() on each of the instances in it (because any instance in that array would be guaranteed to implement adjust(), one of the protocol’s requirements). class SimpleClass2: ExampleProtocol { var simpleDescription: String = "Another very simple class." func adjust() { simpleDescription += " Adjusted." } } var protocolArray: [ExampleProtocol] = [SimpleClass(), SimpleClass(), SimpleClass2()] for instance in protocolArray { instance.adjust() } protocolArray Swift and Cocoa Touch Swift is designed to provide seamless interoperability with Cocoa Touch, the set of Apple frameworks you use to develop apps for iOS. As you walk through the rest of the lessons, it helps to have a basic understanding of how Swift interacts with Cocoa Touch. So far, you’ve been working exclusively with data types from the Swift standard library. The Swift standard library is a set of data types and capabilities designed for Swift and baked into the language. Types like String and Array are examples of data types you see in the standard library. let sampleString: String = "hello" let sampleArray: Array = [1, 2, 3.1415, 23, 42] EXPERIMENT Read about types in the standard library by Option-clicking the type in Xcode. Option-click on String and Array in the code above while looking at this playground in Xcode. When writing iOS apps, you’ll be using more than the Swift standard library. One of the most frequently used frameworks in iOS app development is UIKit. UIKit contains useful classes for working with the UI (user interface) layer of your app. To get access to UIKit, simply import it as a module into any Swift file or playground. import UIKit After importing UIKit, you can use Swift syntax with UIKit types and with their methods, properties, and so on. let redSquare = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) redSquare.backgroundColor = UIColor.redColor() Many of the classes you’ll be introduced to in the lessons come from UIKit, so you’ll see this import statement often. With this breadth of knowledge about Swift, you’re about to jump into making a full-fledged app in the next lesson. Although this lesson is the extent of playgrounds you’ll work with for now, remember that they can be a powerful tool in app development for anything from debugging, to visualizing complex code, to rapid prototyping.

0 comments:

Post a Comment