From 84cb0296175c3c8524d00edf23b90bf45e01fd35 Mon Sep 17 00:00:00 2001 From: Sam <30577766+Samasaur1@users.noreply.github.com> Date: Sat, 3 Aug 2019 07:13:22 -0400 Subject: [PATCH] [Swift/en] Update to Swift 5 and include omitted examples (#3553) * Update to Swift 5 and include omitted examples * Reformat lines to ~80 characters * Fix mistype and add optional chaining --- swift.html.markdown | 1014 ++++++++++++++++++++++++++++--------------- 1 file changed, 660 insertions(+), 354 deletions(-) diff --git a/swift.html.markdown b/swift.html.markdown index f834f373..48e0f6f2 100644 --- a/swift.html.markdown +++ b/swift.html.markdown @@ -8,22 +8,26 @@ contributors: - ["Clayton Walker", "https://github.com/cwalk"] - ["Fernando Valverde", "http://visualcosita.xyz"] - ["Alexey Nazaroff", "https://github.com/rogaven"] + - ["@Samasaur1", "https://github.com/Samasaur1"] filename: learnswift.swift --- Swift is a programming language for iOS and OS X development created by Apple. Designed to coexist with Objective-C and to be more resilient against erroneous code, Swift was introduced in 2014 at Apple's developer conference WWDC. It is built with the LLVM compiler included in Xcode 6+. -The official _[Swift Programming Language](https://itunes.apple.com/us/book/swift-programming-language/id881256329)_ book from Apple is now available via iBooks. +The official _[Swift Programming Language](https://itunes.apple.com/us/book/swift-programming-language/id881256329)_ book from Apple is now available via iBooks. It goes into much more detail than this guide, and if you have the time and patience to read it, it's recommended. Some of these examples are from that book. Another great reference is _About Swift_ on Swift's [website](https://docs.swift.org/swift-book/). ```swift // import a module -import UIKit +import Foundation -// -// MARK: Basics -// +// Single-line comments are prefixed with // +// Multi-line comments start with /* and end with */ +/* Nested multiline comments + /* ARE */ + allowed + */ // Xcode supports landmarks to annotate your code and lists them in the jump bar // MARK: Section mark @@ -31,220 +35,380 @@ import UIKit // TODO: Do something soon // FIXME: Fix this code -// In Swift 2, println and print were combined into one print method. Print automatically appends a new line. -print("Hello, world") // println is now print -print("Hello, world", terminator: "") // printing without appending a newline +//MARK: Hello, World +// From Swift 3 on, to print, just use the `print` method. +// It automatically appends a new line. +print("Hello, world") -// variables (var) value can change after being set -// constants (let) value can NOT be changed after being set +// +// MARK: - Variables +// -var myVariable = 42 + +//Use `let` to declare a constant and `var` to declare a variable. +let theAnswer = 42 +var theQuestion = "What is the Answer?" +theQuestion = "How many roads must a man walk down?" +theQuestion = "What is six by nine?" +// Atttempting to reassign a constant throws a compile-time error +//theAnswer = 54 + +// Both variables and constants can be declared before they are given a value, +// but must be given a value before they are used +let someConstant: Int +var someVariable: String +// These lines will throw errors: +//print(someConstant) +//print(someVariable) +someConstant = 0 +someVariable = "0" + +// As you can see above, variable types are automatically inferred. +// To explicitly declare the type, write it after the variable name, +// separated by a colon. +let aString: String = "A string" +let aDouble: Double = 0 + +// Values are never implicitly converted to another type. +// Explicitly make instances of the desired type. +let stringWithDouble = aString + String(aDouble) +let intFromDouble = Int(aDouble) + +// For strings, use string interpolation +let descriptionString = "The value of aDouble is \(aDouble)" +// You can put any expression inside string interpolation. +let equation = "Six by nine is \(6 * 9), not 42!" +// To avoid escaping double quotes and backslashes, change the string delimiter +let explanationString = #"The string I used was "The value of aDouble is \(aDouble)" and the result was \#(descriptionString)"# +// You can put as many number signs as you want before the opening quote, +// just match them at the ending quote. They also change the escape character +// to a backslash followed by the same number of number signs. + +let multiLineString = """ + This is a multi-line string. + It's called that because it takes up multiple lines (wow!) + Any indentation beyond the closing quotation marks is kept, the rest is discarded. + You can include " or "" in multi-line strings because the delimeter is three. + """ + +// Arrays +let shoppingList = ["catfish", "water", "tulips",] //commas are allowed after the last element +let secondElement = shoppingList[1] // Arrays are 0-indexed + +// Arrays declared with let are immutable; the following line throws a compile-time error +//shoppingList[2] = "mango" + +// Arrays are structs (more on that later), so this creates a copy instead of referencing the same object +var mutableShoppingList = shoppingList +mutableShoppingList[2] = "mango" + +// == is equality +shoppingList == mutableShoppingList // false + +// Dictionaries declared with let are also immutable +var occupations = [ + "Malcolm": "Captain", + "kaylee": "Mechanic" +] +occupations["Jayne"] = "Public Relations" +// Dictionaries are also structs, so this also creates a copy +let immutableOccupations = occupations + +immutableOccupations == occupations // true + +// Arrays and dictionaries both automatically grow as you add elements +mutableShoppingList.append("blue paint") +occupations["Tim"] = "CEO" + +// They can both be set to empty +mutableShoppingList = [] +occupations = [:] + +let emptyArray = [String]() +let emptyArray2 = Array() // same as above +// [T] is shorthand for Array +let emptyArray3: [String] = [] // Declaring the type explicitly allows you to set it to an empty array +let emptyArray4: Array = [] // same as above + +// [Key: Value] is shorthand for Dictionary +let emptyDictionary = [String: Double]() +let emptyDictionary2 = Dictionary() // same as above +var emptyMutableDictionary: [String: Double] = [:] +var explicitEmptyMutableDictionary: Dictionary = [:] // same as above + +// MARK: Other variables let øπΩ = "value" // unicode variable names -let π = 3.1415926 -let convenience = "keyword" // contextual variable name -let weak = "keyword"; let override = "another keyword" // statements can be separated by a semi-colon -let `class` = "keyword" // backticks allow keywords to be used as variable names -let explicitDouble: Double = 70 -let intValue = 0007 // 7 -let largeIntValue = 77_000 // 77000 -let label = "some text " + String(myVariable) // String construction -let piText = "Pi = \(π), Pi 2 = \(π * 2)" // String interpolation +let 🤯 = "wow" // emoji variable names -// Build Specific values -// uses -D build configuration -#if false - print("Not printed") - let buildValue = 3 -#else - let buildValue = 7 -#endif -print("Build value: \(buildValue)") // Build value: 7 +// Keywords can be used as variable names +// These are contextual keywords that wouldn't be used now, so are allowed +let convenience = "keyword" +let weak = "another keyword" +let override = "another keyword" + +// Using backticks allows keywords to be used as variable names even if they wouldn't be allowed normally +let `class` = "keyword" + +// MARK: - Optionals /* -Optionals are a Swift language feature that either contains a value, -or contains nil (no value) to indicate that a value is missing. -A question mark (?) after the type marks the value as optional. + Optionals are a Swift language feature that either contains a value, + or contains nil (no value) to indicate that a value is missing. + Nil is roughly equivalent to `null` in other languages. + A question mark (?) after the type marks the value as optional of that type. + + If a type is not optional, it is guaranteed to have a value. + + Because Swift requires every property to have a type, even nil must be + explicitly stored as an Optional value. + + Optional is an enum, with the cases .none (nil) and .some(T) (the value) + */ -Because Swift requires every property to have a value, even nil must be -explicitly stored as an Optional value. - -Optional is an enum. -*/ var someOptionalString: String? = "optional" // Can be nil -// same as above, but ? is a postfix operator (syntax candy) -var someOptionalString2: Optional = "optional" +// T? is shorthand for Optional — ? is a postfix operator (syntax candy) +let someOptionalString2: Optional = nil +let someOptionalString3 = String?.some("optional") // same as the first one +let someOptionalString4 = String?.none //nil + +/* + To access the value of an optional that has a value, use the postfix + operator !, which force-unwraps it. Force-unwrapping is like saying, "I + know that this optional definitely has a value, please give it to me. + + Trying to use ! to access a non-existent optional value triggers a + runtime error. Always make sure that an optional contains a non-nil + value before using ! to force-unwrap its value. + */ if someOptionalString != nil { // I am not nil if someOptionalString!.hasPrefix("opt") { print("has the prefix") } - - let empty = someOptionalString?.isEmpty } -someOptionalString = nil -/* -Trying to use ! to access a non-existent optional value triggers a runtime -error. Always make sure that an optional contains a non-nil value before -using ! to force-unwrap its value. -*/ +// Swift supports "optional chaining," which means that you can call functions +// or get properties of optional values and they are optionals of the appropriate type. +// You can even do this multiple times, hence the name "chaining." + +let empty = someOptionalString?.isEmpty // Bool? -// implicitly unwrapped optional -var unwrappedString: String! = "Value is expected." -// same as above, but ! is a postfix operator (more syntax candy) -var unwrappedString2: ImplicitlyUnwrappedOptional = "Value is expected." - -// If let structure - -// If let is a special structure in Swift that allows you to check if an Optional rhs holds a value, and in case it does - unwraps and assigns it to the lhs. -if let someOptionalStringConstant = someOptionalString { +// if-let structure - +// if-let is a special structure in Swift that allows you to check +// if an Optional rhs holds a value, and if it does unwrap +// and assign it to the lhs. +if let someNonOptionalStringConstant = someOptionalString { // has `Some` value, non-nil - if !someOptionalStringConstant.hasPrefix("ok") { + // someOptionalStringConstant is of type String, not type String? + if !someNonOptionalStringConstant.hasPrefix("ok") { // does not have the prefix } } +//if-var is allowed too! +if var someNonOptionalString = someOptionalString { + someNonOptionalString = "Non optional AND mutable" + print(someNonOptionalString) +} + +// You can bind multiple optional values in one if-let statement. +// If any of the bound values are nil, the if statement does not execute. +if let first = someOptionalString, let second = someOptionalString2, + let third = someOptionalString3, let fourth = someOptionalString4 { + print("\(first), \(second), \(third), and \(fourth) are all not nil") +} + +//if-let supports "," (comma) clauses, which can be used to +// enforce conditions on newly-bound optional values. +// Both the assignment and the "," clause must pass. +let someNumber: Int? = 7 +if let num = someNumber, num > 3 { + print("num is not nil and is greater than 3") +} + +// Implicitly unwrapped optional — An optional value that doesn't need to be unwrapped +let unwrappedString: String! = "Value is expected." + +// Here's the difference: +let forcedString = someOptionalString! // requires an exclamation mark +let implicitString = unwrappedString // doesn't require an exclamation mark + +/* + You can think of an implicitly unwrapped optional as giving permission + for the optional to be unwrapped automatically whenever it's used. + Rather than placing an exclamation mark after the optional's name each time you use it, + you place an exclamation mark after the optional's type when you declare it. + */ + +// Otherwise, you can treat an implicitly unwrapped optional the same way the you treat a normal optional +// (i.e., if-let, != nil, etc.) + +// Pre-Swift 5, T! was shorthand for ImplicitlyUnwrappedOptional +// Swift 5 and later, using ImplicitlyUnwrappedOptional throws a compile-time error. +//var unwrappedString2: ImplicitlyUnwrappedOptional = "Value is expected." //error + // The nil-coalescing operator ?? unwraps an optional if it contains a non-nil value, or returns a default value. -var someOptionalString: String? +someOptionalString = nil let someString = someOptionalString ?? "abc" print(someString) // abc +// a ?? b is shorthand for a != nil ? a! : b -// Swift has support for storing a value of any type. -// For that purposes there is two keywords: `Any` and `AnyObject` -// `AnyObject` == `id` from Objective-C -// `Any` – also works with any scalar values (Class, Int, struct, etc.) -var anyVar: Any = 7 -anyVar = "Changed value to a string, not good practice, but possible." -let anyObjectVar: AnyObject = Int(1) as NSNumber +// MARK: - Control Flow -/* - Comment here +let condition = true +if condition { print("condition is true") } // can't omit the braces - /* - Nested comments are also supported - */ -*/ - -// -// MARK: Collections -// - -/* -Array and Dictionary types are structs. So `let` and `var` also indicate -that they are mutable (var) or immutable (let) when declaring these types. -*/ - -// Array -var shoppingList = ["catfish", "water", "lemons"] -shoppingList[1] = "bottle of water" -let emptyArray = [String]() // let == immutable -let emptyArray2 = Array() // same as above -var emptyMutableArray = [String]() // var == mutable -var explicitEmptyMutableStringArray: [String] = [] // same as above - - -// Dictionary -var occupations = [ - "Malcolm": "Captain", - "kaylee": "Mechanic" -] -occupations["Jayne"] = "Public Relations" -let emptyDictionary = [String: Float]() // let == immutable -let emptyDictionary2 = Dictionary() // same as above -var emptyMutableDictionary = [String: Float]() // var == mutable -var explicitEmptyMutableDictionary: [String: Float] = [:] // same as above - - -// -// MARK: Control Flow -// - -// Condition statements support "," (comma) clauses, which can be used -// to help provide conditions on optional values. -// Both the assignment and the "," clause must pass. -let someNumber = Optional(7) -if let num = someNumber, num > 3 { - print("num is greater than 3") +if theAnswer > 50 { + print("theAnswer > 50") +} else if condition { + print("condition is true") +} else { + print("Neither are true") } -// for loop (array) -let myArray = [1, 1, 2, 3, 5] -for value in myArray { - if value == 1 { - print("One!") - } else { - print("Not one!") - } -} - -// for loop (dictionary) -var dict = ["one": 1, "two": 2] -for (key, value) in dict { - print("\(key): \(value)") -} - -// for loop (range) -for i in -1...shoppingList.count { - print(i) -} -shoppingList[1...2] = ["steak", "peacons"] -// use ..< to exclude the last number - -// while loop -var i = 1 -while i < 1000 { - i *= 2 -} - -// repeat-while loop -repeat { - print("hello") -} while 1 == 2 +// The condition in an `if` statement must be a `Bool`, so the following code is an error, not an implicit comparison to zero +//if 5 { +// print("5 is not zero") +//} // Switch +// Must be exhaustive +// Does not implicitly fall through, use the fallthrough keyword // Very powerful, think `if` statements with syntax candy // They support String, object instances, and primitives (Int, Double, etc) let vegetable = "red pepper" +let vegetableComment: String 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." + vegetableComment = "Add some raisins and make ants on a log." +case "cucumber", "watercress": // match multiple values + vegetableComment = "That would make a good tea sandwich." case let localScopeValue where localScopeValue.hasSuffix("pepper"): - let vegetableComment = "Is it a spicy \(localScopeValue)?" + vegetableComment = "Is it a spicy \(localScopeValue)?" default: // required (in order to cover all possible input) - let vegetableComment = "Everything tastes good in soup." + vegetableComment = "Everything tastes good in soup." +} +print(vegetableComment) + +// You use the `for-in` loop to iterate over a sequence, such as an array, dictionary, range, etc. +for element in shoppingList { + print(element) // shoppingList is of type `[String]`, so element is of type `String` +} +//Iterating through a dictionary does not guarantee any specific order +for (person, job) in immutableOccupations { + print("\(person)'s job is \(job)") +} +for i in 1...5 { + print(i, terminator: " ") // Prints "1 2 3 4 5" +} +for i in 0..<5 { + print(i, terminator: " ") // Prints "0 1 2 3 4" +} +//for index in range can replace a C-style for loop: +// for (int i = 0; i < 10; i++) { +// //code +// } +//becomes: +// for i in 0..<10 { +// //code +// } +//To step by more than one, use the stride(from:to:by:) or stride(from:through:by) functions +//`for i in stride(from: 0, to: 10, by: 2)` is the same as `for (int i = 0; i < 10; i += 2)` +//`for i in stride(from: 0, through: 10, by: 2)` is the same as `for (int i = 0; i <= 10; i += 2) + +// while loops are just like most languages +var i = 0 +while i < 5 { + i += Bool.random() ? 1 : 0 + print(i) } -// -// MARK: Functions -// +// This is like a do-while loop in other languages — the body of the loop executes a minimum of once +repeat { + i -= 1 + i += Int.random(in: 0...3) +} while i < 5 -// Functions are a first-class type, meaning they can be nested -// in functions and can be passed around +// The continue statement continues executing a loop at the next iteration +// The break statement ends a swift or loop immediately + +// MARK: - Functions + +// Functions are a first-class type, meaning they can be nested in functions and can be passed around. // Function with Swift header docs (format as Swift-modified Markdown syntax) -/** -A greet operation - -- A bullet in docs -- Another bullet in the docs - -- Parameter name : A name -- Parameter day : A day -- Returns : A string containing the name and day value. -*/ +/// A greet operation. +/// +/// - Parameters: +/// - name: A name. +/// - day: A day. +/// - Returns: A string containing the name and day value. func greet(name: String, day: String) -> String { return "Hello \(name), today is \(day)." } greet(name: "Bob", day: "Tuesday") -// similar to above except for the function parameter behaviors -func greet2(name: String, externalParamName localParamName: String) -> String { - return "Hello \(name), the day is \(localParamName)" +// Ideally, function names and parameter labels combine to make function calls similar to sentences. +func sayHello(to name: String, onDay day: String) -> String { + return "Hello \(name), the day is \(day)" +} +sayHello(to: "John", onDay: "Sunday") + +//Functions that don't return anything can omit the return arrow; they don't need to say that they return Void (although they can). +func helloWorld() { + print("Hello, World!") +} + +// Argument labels can be blank +func say(_ message: String) { + print(#"I say "\#(message)""#) +} +say("Hello") + +// Default parameters can be ommitted when calling the function. +func printParameters(requiredParameter r: Int, optionalParameter o: Int = 10) { + print("The required parameter was \(r) and the optional parameter was \(o)") +} +printParameters(requiredParameter: 3) +printParameters(requiredParameter: 3, optionalParameter: 6) + +// Variadic args — only one set per function. +func setup(numbers: Int...) { + // it's an array + let _ = numbers[0] + let _ = numbers.count +} + +// pass by ref +func swapTwoInts(a: inout Int, b: inout Int) { + let tempA = a + a = b + b = tempA +} +var someIntA = 7 +var someIntB = 3 +swapTwoInts(a: &someIntA, b: &someIntB) //must be called with an & before the variable name. +print(someIntB) // 7 + +type(of: greet) // (String, String) -> String +type(of: helloWorld) // () -> Void + +// Passing and returning functions +func makeIncrementer() -> ((Int) -> Int) { + func addOne(number: Int) -> Int { + return 1 + number + } + return addOne +} +var increment = makeIncrementer() +increment(7) + +func performFunction(_ function: (String, String) -> String, on string1: String, and string2: String) { + let result = function(string1, string2) + print("The result of calling the function on \(string1) and \(string2) was \(result)") } -greet2(name: "John", externalParamName: "Sunday") // Function that returns multiple items in a tuple func getGasPrices() -> (Double, Double, Double) { @@ -271,46 +435,24 @@ print("Highest gas price: \(pricesTuple2.highestPrice)") func testGuard() { // guards provide early exits or breaks, placing the error handler code near the conditions. // it places variables it declares in the same scope as the guard statement. + // They make it easier to avoid the "pyramid of doom" guard let aNumber = Optional(7) else { - return + return // guard statements MUST exit the scope that they are in. + // They generally use `return` or `throw`. } - + print("number is \(aNumber)") } testGuard() -// Variadic Args -func setup(numbers: Int...) { - // it's an array - let _ = numbers[0] - let _ = numbers.count -} +// Note that the print function is declared like so: +// func print(_ input: Any..., separator: String = " ", terminator: String = "\n") +// To print without a newline: +print("No newline", terminator: "") +print("!") -// Passing and returning functions -func makeIncrementer() -> ((Int) -> Int) { - func addOne(number: Int) -> Int { - return 1 + number - } - return addOne -} -var increment = makeIncrementer() -increment(7) +// MARK: - Closures -// pass by ref -func swapTwoInts(a: inout Int, b: inout Int) { - let tempA = a - a = b - b = tempA -} -var someIntA = 7 -var someIntB = 3 -swapTwoInts(a: &someIntA, b: &someIntB) -print(someIntB) // 7 - - -// -// MARK: Closures -// var numbers = [1, 2, 6] // Functions are special case closures ({}) @@ -336,85 +478,157 @@ numbers = numbers.sorted { $0 > $1 } print(numbers) // [18, 6, 3] -// -// MARK: Structures -// +// MARK: - Enums + +// Enums can optionally be of a specific type or on their own. +// They can contain methods like classes. + +enum Suit { + case spades, hearts, diamonds, clubs + var icon: Character { + switch self { + case .spades: + return "♤" + case .hearts: + return "♡" + case .diamonds: + return "♢" + case .clubs: + return "♧" + } + } +} + +// Enum values allow short hand syntax, no need to type the enum type +// when the variable is explicitly declared +var suitValue: Suit = .hearts + +// Conforming to the CaseIterable protocol automatically synthesizes the allCases property, +// which contains all the values. It works on enums without associated values or @available attributes. +enum Rank: CaseIterable { + case ace + case two, three, four, five, six, seven, eight, nine, ten + case jack, queen, king + var icon: String { + switch self { + case .ace: + return "A" + case .two: + return "2" + case .three: + return "3" + case .four: + return "4" + case .five: + return "5" + case .six: + return "6" + case .seven: + return "7" + case .eight: + return "8" + case .nine: + return "9" + case .ten: + return "10" + case .jack: + return "J" + case .queen: + return "Q" + case .king: + return "K" + } + } +} + +for suit in [Suit.clubs, .diamonds, .hearts, .spades] { + for rank in Rank.allCases { + print("\(rank.icon)\(suit.icon)") + } +} + +// String enums can have direct raw value assignments +// or their raw values will be derived from the Enum field +enum BookName: String { + case john + case luke = "Luke" +} +print("Name: \(BookName.john.rawValue)") + +// Enum with associated Values +enum Furniture { + // Associate with Int + case desk(height: Int) + // Associate with String and Int + case chair(String, Int) + + func description() -> String { + //either placement of let is acceptable + switch self { + case .desk(let height): + return "Desk with \(height) cm" + case let .chair(brand, height): + return "Chair of \(brand) with \(height) cm" + } + } +} + +var desk: Furniture = .desk(height: 80) +print(desk.description()) // "Desk with 80 cm" +var chair = Furniture.chair("Foo", 40) +print(chair.description()) // "Chair of Foo with 40 cm" + +// MARK: - Structures & Classes + +/* + Structures and classes in Swift have many things in common. Both can: + - Define properties to store values + - Define methods to provide functionality + - Define subscripts to provide access to their values using subscript syntax + - Define initializers to set up their initial state + - Be extended to expand their functionality beyond a default implementation + - Conform to protocols to provide standard functionality of a certain kind + + Classes have additional capabilities that structures don't have: + - Inheritance enables one class to inherit the characteristics of another. + - Type casting enables you to check and interpret the type of a class instance at runtime. + - Deinitializers enable an instance of a class to free up any resources it has assigned. + - Reference counting allows more than one reference to a class instance. + + Unless you need to use a class for one of these reasons, use a struct. + + Structures are value types, while classes are reference types. + */ + +// MARK: Structures -// Structures and classes have very similar capabilities struct NamesTable { let names: [String] - + // Custom subscript subscript(index: Int) -> String { return names[index] } } -// Structures have an auto-generated (implicit) designated initializer +// Structures have an auto-generated (implicit) designated "memberwise" initializer let namesTable = NamesTable(names: ["Me", "Them"]) let name = namesTable[1] print("Name is \(name)") // Name is Them -// -// MARK: Error Handling -// - -// The `Error` protocol is used when throwing errors to catch -enum MyError: Error { - case badValue(msg: String) - case reallyBadValue(msg: String) -} - -// functions marked with `throws` must be called using `try` -func fakeFetch(value: Int) throws -> String { - guard 7 == value else { - throw MyError.reallyBadValue(msg: "Some really bad value") - } - - return "test" -} - -func testTryStuff() { - // assumes there will be no error thrown, otherwise a runtime exception is raised - let _ = try! fakeFetch(value: 7) - - // if an error is thrown, then it proceeds, but if the value is nil - // it also wraps every return value in an optional, even if its already optional - let _ = try? fakeFetch(value: 7) - - do { - // normal try operation that provides error handling via `catch` block - try fakeFetch(value: 1) - } catch MyError.badValue(let msg) { - print("Error message: \(msg)") - } catch { - // must be exhaustive - } -} -testTryStuff() - -// // MARK: Classes -// -// Classes, structures and its members have three levels of access control -// They are: internal (default), public, private - -public class Shape { - public func getArea() -> Int { +class Shape { + func getArea() -> Int { return 0 } } -// All methods and properties of a class are public. -// If you just need to store data in a -// structured object, you should use a `struct` - -internal class Rect: Shape { +class Rect: Shape { var sideLength: Int = 1 - + // Custom getter and setter property - private var perimeter: Int { + var perimeter: Int { get { return 4 * sideLength } @@ -423,16 +637,16 @@ internal class Rect: Shape { sideLength = newValue / 4 } } - + // Computed properties must be declared as `var`, you know, cause' they can change var smallestSideLength: Int { return self.sideLength - 1 } - + // Lazily load a property // subShape remains nil (uninitialized) until getter called lazy var subShape = Rect(sideLength: 4) - + // If you don't need a custom getter and setter, // but still want to run code before and after getting or setting // a property, you can use `willSet` and `didSet` @@ -442,19 +656,19 @@ internal class Rect: Shape { print(someIdentifier) } } - + init(sideLength: Int) { self.sideLength = sideLength // always super.init last when init custom properties super.init() } - + func shrink() { if sideLength > 0 { sideLength -= 1 } } - + override func getArea() -> Int { return sideLength * sideLength } @@ -486,13 +700,13 @@ class Circle: Shape { override func getArea() -> Int { return 3 * radius * radius } - + // Place a question mark postfix after `init` is an optional init // which can return nil init?(radius: Int) { self.radius = radius super.init() - + if radius <= 0 { return nil } @@ -509,64 +723,9 @@ if let circle = myEmptyCircle { print("circle is not nil") } +// MARK: - Protocols -// -// MARK: Enums -// - -// Enums can optionally be of a specific type or on their own. -// They can contain methods like classes. - -enum Suit { - case spades, hearts, diamonds, clubs - func getIcon() -> String { - switch self { - case .spades: return "♤" - case .hearts: return "♡" - case .diamonds: return "♢" - case .clubs: return "♧" - } - } -} - -// Enum values allow short hand syntax, no need to type the enum type -// when the variable is explicitly declared -var suitValue: Suit = .hearts - -// String enums can have direct raw value assignments -// or their raw values will be derived from the Enum field -enum BookName: String { - case john - case luke = "Luke" -} -print("Name: \(BookName.john.rawValue)") - -// Enum with associated Values -enum Furniture { - // Associate with Int - case desk(height: Int) - // Associate with String and Int - case chair(String, Int) - - func description() -> String { - switch self { - case .desk(let height): - return "Desk with \(height) cm" - case .chair(let brand, let height): - return "Chair of \(brand) with \(height) cm" - } - } -} - -var desk: Furniture = .desk(height: 80) -print(desk.description()) // "Desk with 80 cm" -var chair = Furniture.chair("Foo", 40) -print(chair.description()) // "Chair of Foo with 40 cm" - - -// -// MARK: Protocols -// +// protocols are also known as interfaces in some other languages // `protocol`s can require that conforming types have specific // instance properties, instance methods, type methods, @@ -577,36 +736,130 @@ protocol ShapeGenerator { func buildShape() -> Shape } -// Protocols declared with @objc allow optional functions, -// which allow you to check for conformance. These functions must be -// marked with @objc also. -@objc protocol TransformShape { - @objc optional func reshape() - @objc optional func canReshape() -> Bool +// MARK: - Other + +// MARK: Typealiases + +// Typealiases allow one type (or composition of types) to be referred to by another name +typealias Integer = Int +let myInteger: Integer = 0 + +// MARK: = Operator + +// Assignment does not return a value. This means it can't be used in conditional statements, +// and the following statement is also illegal +// let multipleAssignment = theQuestion = "No questions asked" +//But you can do this: +let multipleAssignment = "No questions asked", secondConstant = "No answers given" + +// MARK: Ranges + +// The ..< and ... operators create ranges. + +// ... is inclusive on both ends (a "closed range") — mathematically, [0, 10] +let _0to10 = 0...10 +// ..< in inclusive on the left, exclusive on the right (a "range") — mathematically, [0, 10) +let singleDigitNumbers = 0..<10 +// You can omit one end (a "PartialRangeFrom") — mathematically, [0, ∞) +let toInfinityAndBeyond = 0... +// Or the other end (a "PartialRangeTo") — mathematically, (-∞, 0) +let negativeInfinityToZero = ..<0 +// (a "PartialRangeThrough") — mathematically, (-∞, 0] +let negativeInfinityThroughZero = ...0 + +// MARK: Wildcard operator + +// In Swift, _ (underscore) is the wildcard operator, which allows values to be ignored + +// It allows functions to be declared without argument labels: +func function(_ labelLessParameter: Int, label labeledParameter: Int, labelAndParameterName: Int) { + print(labelLessParameter, labeledParameter, labelAndParameterName) +} +function(0, label: 0, labelAndParameterName: 0) + +// You can ignore the return values of functions +func printAndReturn(_ str: String) -> String { + print(str) + return str +} +let _ = printAndReturn("Some String") + +// You can ignore part of a tuple and keep part of it +func returnsTuple() -> (Int, Int) { + return (1, 2) +} +let (_, two) = returnsTuple() + +// You can ignore closure parameters +let closure: (Int, Int) -> String = { someInt, _ in + return "\(someInt)" +} +closure(1, 2) // returns 1 + +// You can ignore the value in a for loop +for _ in 0..<10 { + // Code to execute 10 times } -class MyShape: Rect { - var delegate: TransformShape? +// MARK: Access Control - func grow() { - sideLength += 2 +/* + Swift has five levels of access control: + - Open: Accessible *and subclassible* in any module that imports it. + - Public: Accessible in any module that imports it, subclassible in the module it is declared in. + - Internal: Accessible and subclassible in the module it is declared in. + - Fileprivate: Accessible and subclassible in the file it is declared in. + - Private: Accessible and subclassible in the enclosing declaration (think inner classes) + + See more here: https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html + */ - // Place a question mark after an optional property, method, or - // subscript to gracefully ignore a nil value and return nil - // instead of throwing a runtime error ("optional chaining"). - if let reshape = self.delegate?.canReshape?(), reshape { - // test for delegate then for method - self.delegate?.reshape?() - } - } +// MARK: Conditional Compilation, Compile-Time Diagnostics, & Availability Conditions + +// Conditional Compilation +#if false +print("This code will not be compiled") +#else +print("This code will be compiled") +#endif +/* + Options are: + os() macOS, iOS, watchOS, tvOS, Linux + arch() i386, x86_64, arm, arm64 + swift() >= or < followed by a version number + compiler() >= or < followed by a version number + canImport() A module name + targetEnvironment() simulator + */ +#if swift(<3) +println() +#endif + +// Compile-Time Diagnostics +// You can use #warning(message) and #error(message) to have the compiler emit warnings and/or errors +#warning("This will be a compile-time warning") +// #error("This would be a compile-time error") + +//Availability Conditions +if #available(iOSMac 10.15, *) { + // macOS 10.15 is available, you can use it here +} else { + // macOS 10.15 is not available, use alternate APIs } +// MARK: Any and AnyObject -// -// MARK: Other -// +// Swift has support for storing a value of any type. +// For that purpose there are two keywords: `Any` and `AnyObject` +// `AnyObject` == `id` from Objective-C +// `Any` works with any values (class, Int, struct, etc.) +var anyVar: Any = 7 +anyVar = "Changed value to a string, not good practice, but possible." +let anyObjectVar: AnyObject = Int(1) as NSNumber -// `extension`s: Add extra functionality to an already existing type +// MARK: Extensions + +// Extensions allow you to add extra functionality to an already-declared type, even one that you don't have the source code for. // Square now "conforms" to the `CustomStringConvertible` protocol extension Square: CustomStringConvertible { @@ -619,17 +872,23 @@ print("Square: \(mySquare)") // You can also extend built-in types extension Int { - var customProperty: String { - return "This is \(self)" + var doubled: Int { + return self * 2 } - - func multiplyBy(num: Int) -> Int { + + func multipliedBy(num: Int) -> Int { return num * self } + + mutating func multiplyBy(num: Int) { + self *= num + } } -print(7.customProperty) // "This is 7" -print(14.multiplyBy(num: 3)) // 42 +print(7.doubled) // 14 +print(7.doubled.multipliedBy(num: 3)) // 42 + +// MARK: Generics // Generics: Similar to Java and C#. Use the `where` keyword to specify the // requirements of the generics. @@ -642,10 +901,21 @@ func findIndex(array: [T], valueToFind: T) -> Int? { } return nil } -let foundAtIndex = findIndex(array: [1, 2, 3, 4], valueToFind: 3) -print(foundAtIndex == 2) // true +findIndex(array: [1, 2, 3, 4], valueToFind: 3) // 2 + +// You can extend types with generics as well +extension Array where Array.Element == Int { + var sum: Int { + var total = 0 + for el in self { + total += el + } + return total + } +} + +// MARK: Operators -// Operators: // Custom operators can start with the characters: // / = - + * % < > ! & | ^ . ~ // or @@ -678,4 +948,40 @@ var bar: Float = 20 foo <-> bar print("foo is \(foo), bar is \(bar)") // "foo is 20.0, bar is 10.0" + +// MARK: - Error Handling + +// The `Error` protocol is used when throwing errors to catch +enum MyError: Error { + case badValue(msg: String) + case reallyBadValue(msg: String) +} + +// functions marked with `throws` must be called using `try` +func fakeFetch(value: Int) throws -> String { + guard 7 == value else { + throw MyError.reallyBadValue(msg: "Some really bad value") + } + + return "test" +} + +func testTryStuff() { + // assumes there will be no error thrown, otherwise a runtime exception is raised + let _ = try! fakeFetch(value: 7) + + // if an error is thrown, then it proceeds, but if the value is nil + // it also wraps every return value in an optional, even if its already optional + let _ = try? fakeFetch(value: 7) + + do { + // normal try operation that provides error handling via `catch` block + try fakeFetch(value: 1) + } catch MyError.badValue(let msg) { + print("Error message: \(msg)") + } catch { + // must be exhaustive + } +} +testTryStuff() ```