Swift:
Useful and important resources:
You can write Swift code:
Let's write and run some little scripts. Fire up your favorite editor and continue the tradition:
print("Hello, world")
$ swift hello.swift Hello, world
Let’s move on to using for-loops, if-statements, and string interpolation:
for c in 1...40 { for b in 1..<c { for a in 1..<b { if a * a + b * b == c * c { print("(\(a), \(b), \(c))") } } } }
$ swift triple.swift (3, 4, 5) (6, 8, 10) (5, 12, 13) (9, 12, 15) (8, 15, 17) (12, 16, 20) (15, 20, 25) (7, 24, 25) (10, 24, 26) (20, 21, 29) (18, 24, 30) (16, 30, 34) (21, 28, 35) (12, 35, 37) (15, 36, 39) (24, 32, 40)
And then work with variable declarations (both let
and var
), command line arguments, while-loops, and tuples:
// Writes Fibonacci numbers up to and including the first commandline argument. let n = Int(CommandLine.arguments[1]) ?? 0 var (a, b) = (0, 1) while b <= n { print(b, terminator: " ") (a, b) = (b, a+b) } print("")
$ swift fib.swift 300
1 1 2 3 5 8 13 21 34 55 89 144 233
$ swift fib.swift dog
$ swift fib.swift
fatal error: Array index out of range
Time for word counting. This script reads from standard input, counts all words, lowercased (where words are Unicode letters plus the apostrophe and the right single quote), and prints them to standard output together with their counts ordered by frequency descending:
import Foundation var counts = [String: Int]() while let line = readLine()?.lowercased() { var wordCharacters = CharacterSet.letters wordCharacters.insert(charactersIn: "'’") let words = line.components(separatedBy: wordCharacters.inverted).filter{$0 != ""} for word in words { counts[word] = (counts[word] ?? 0) + 1 } } for (word, count) in (counts.sorted {$0.1 > $1.1}) { print("\(word) \(count)") }
Let’s try it out on Project Gutenberg’s The Count of Monte Cristo, by Alexandre Dumas, père:
$ curl http://www.gutenberg.org/files/1184/1184-0.txt | swift wordcount.swift the 28635 of 12919 to 12898 and 12571 a 9472 i 8377 you 8280 . . . brails 1 filthy 1 certificates 1 revolutionists 1 deserts 1 dauzats’ 1 dandy’s 1 paled 1
And let’s ramp it up a bit illustrating arrays, writing your own functions, argument labels, and inout
parameters:
import Foundation func generatePermutations(of a: inout [Character], upTo n: Int) { if n == 0 { print(String(a)) } else { for i in 0..<n { generatePermutations(of: &a, upTo: n - 1) a.swapAt(n % 2 == 0 ? 0 : i, n) } generatePermutations(of: &a, upTo: n - 1) } } if CommandLine.arguments.count != 2 { fputs("Exactly one argument is required\n", stderr) exit(1) } let word = CommandLine.arguments[1] var charArray = Array(word) generatePermutations(of: &charArray, upTo: charArray.count - 1)
Run the swift
command in your terminal with no arguments to bring up a standard REPL:
$ swift Welcome to Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53). Type :help for assistance. 1> 2 + 2 $R0: Int = 4 2> 2 > 2 $R1: Bool = false 3> var greeting = "Hello, world" greeting: String = "Hello, world" 4> let numbers = [50, 20, 30, 90] numbers: [Int] = 4 values { [0] = 50 [1] = 20 [2] = 30 [3] = 90 } 5> numbers.count $R2: Int = 4 6> let poem = """ 7. This is not a Hai 8. ku but I tried to make 9. it one but failed 10. """ poem: String = "This is not a Hai\nku but I tried to make\nit one but failed" 11> func triple(x: Int) -> Int { 12. return x * 3 13. } 14> triple(x: 300) $R3: Int = 900 15> numbers.map(triple) $R4: [Int] = 4 values { [0] = 150 [1] = 60 [2] = 90 [3] = 270 } 16> $R4[2] $R5: Int = 90
REPLs are for one-at-a-time expression evaluation. Playgrounds are way more sophisticated.
If you have Xcode, select File→New Playground and start playing around. There are many online tutorials out there. Definitely a better way to experiment, right?
Pssssssst
The Swift Tour is more encompassing than these notes and you might want to read through it first, or even instead of these notes.
But here goes yet another language overview anyway.
Things you can declare in Swift include:
let
)var
)func
)struct
)class
)enum
)protocol
)typealias
)init
)deinit
)subscript
)extension
)prefix
, postfix
, infix
)precedencegroup
)You can also declare by importing from a modules.
Examples are coming!
Swift’s type system is super clean! Wildly, There are no “primitive types” and get this: there are no intrinsic built-in types! Every type—even the numeric types—are defined in the standard library! And this is cool: There are six kinds of types, and all types fit into one of those six kinds:
Kind | Description | Value or Reference | Examples |
---|---|---|---|
Structure | A basic value type, whose instances can have properties and methods | Value | struct Point { |
Class | A basic reference type, whose instances can have properties and methods | Reference | class Point { |
Enumeration | Sum type | Value | enum Response { |
Tuple | Product type | Value | |
Function | A type for functions, made from parameter types and a return type | Reference | (String, String) -> [Int] |
Protocol | Set of requirements that can be adopted by structs, classes, and enumerations. (Similar to what other languages call interfaces or mixins.) Instances are not allowed. | N/A | protocol Summarizable { |
Wait, what? Numbers, booleans, and strings are structs?
Yes. Things are simpler that way. There is no need for primitive types and reference types like Java and JavaScript have. Simple values like5
,false
, and"hello"
can be the receivers of methods. No fancy wrapping is required.
Swift variables and constants are type-constrained. You are always permitted to specify the type in the declaration, but if you are initializing at the point of declaration, Swift will infer the type for you.
let dozen: Int = 12 // Can specify type but don't have to var fresh = true // Type is inferred to be Bool var wins: Int // No value, type must be specified let states = ["Ready", "Set", "Go"] // Inferred to be [String] let step = states.firstIndex(of: "Go") // Inferred to be Int? var counts: [String:Int] = [:] // Empty collection MUST have type var frequencies = [String:Int]() // ...or use the type initializer
Instances of value types are copied when passed around (assigned, passed as arguments, and returned from functions), while instances of reference types are shared.
struct S { var x = 0 } // structs have value semantics var s1 = S() // create an *instance* of type S var s2 = s1 // copy, by value s1.x = 5 // mutate through s1 assert(s2.x == 0) // s2 unchanged class C { var x = 0 } // classes have reference semantics var c1 = C() // create an *instance* of type C var c2 = c1 // reference copy c1.x = 5 // mutate through c1 assert(c2.x == 5) // c2 sees change assert(c1 === c2) // Same identity!
You should probably memorize the following:
Value Types | Reference Types |
---|---|
Copied when assigned, passed, or returned | Shared when assigned, passed, or returned |
You will probably want to define your own == operator to compare the internal states |
Built-in === operator compares the references (identities) |
Safe across multiple threads | Used when you want shared, mutable state |
For value types, let
and var
not only determine whether you can or cannot rebind a name, but also whether the associated instance is mutable or immutable. Woah!
> let combination = [31, 22, 0] > combination = [3, 30, 11] error: cannot assign to value: 'combination' is a 'let' constant > combination[1] = 19 error: cannot assign through subscript: 'combination' is a 'let' constant
const combination = [31, 22, 0]; combination = [3, 30, 11]; combination[1] = 19;
?
Numbers are Booleans are not intrinsic to the language; they are just struct
types in the Swift Standard Library. That’s rather remarkable, right? Check out this set of links to basic types in the Apple documentation. Also, take a look at the implementation of the Bool type at GitHub.
Here are some examples to get you started (and to practice with var
and let
):
1> 90 >= 3 && !(3 < 5) || true $R0: Bool = true 2> false.toggle() error: cannot use mutating member on immutable value: literals are not mutable 2> let godotHasArrived = false godotHasArrived: Bool = false 3> godotHasArrived.toggle() error: cannot use mutating member on immutable value: 'godotHasArrived' is a 'let' constant 3> var dogIsBarking = true dogIsBarking: Bool = true 4> dogIsBarking.toggle() 5> dogIsBarking $R1: Bool = false
The numeric types in the standard library are:
Type | Description |
---|---|
Int8 | -128 ... 127 |
Int16 | -32_768 ... 32_767 |
Int32 | -2_147_483_648 ... 2_147_483_647 |
Int64 | -9_223_372_036_854_775_808 ...9_223_372_036_854_775_807 |
Int | Either Int32 or Int64, depending on hardware |
UInt8 | 0 ... 255 |
UInt16 | 0 ... 65_536 |
UInt32 | 0 ... 4_294_967_296 |
UInt64 | 0 ... 18_446_744_073_709_551_616 |
UInt | Either UInt32 or UInt64, depending on hardware |
Float | IEEE-754 binary32 |
Double | IEEE-754 binary64 |
Let’s see some integers in action:
// Binary, octal, and hex literals assert(0b110100100 == 420) assert(0o644 == 420) assert(0x1a4 == 420) // Some non-mutating methods assert([5, 6, 7].contains(Int.random(in: 5..<8))) assert([5, 6, 7].contains(Int.random(in: 5...8))) assert(20.isMultiple(of: 5) == true) assert(-23.signum() == -1) assert(0.signum() == 0) assert(23.signum() == 1) // A fun one to be sure assert(97.quotientAndRemainder(dividingBy: 25) == (quotient: 3, remainder: 22)) let (q, r) = 97.quotientAndRemainder(dividingBy: 25) assert(q == 3 && r == 22) // Some mutating methods var x = 13 x.negate() assert(x == -13) // Generic numeric functions (not methods!) assert(abs(-23) == 23) // Static properties assert(Int16.min == -32768) assert(Int64.max == 9223372036854775807) assert(UInt64.max == 18446744073709551615) assert((UInt8.min, UInt8.max) == (0, 255)) assert((Int8.min, Int8.max) == (-128, 127))
And some doubles:
// Non-mutating operations on doubles let nineFourths = 9.0 / 4 assert(nineFourths.squareRoot() == 1.5) assert(nineFourths * 4 == 9) assert(nineFourths.rounded() == 2) assert(nineFourths.isEqual(to: 2.25)) assert(!nineFourths.isZero) assert(nineFourths.isFinite) assert(nineFourths.description == "2.25") // Mutating operations on doubles var x: Double = 529 x.formSquareRoot() // now x is 23 x.formRemainder(dividingBy: 10) // now x is 3 x.negate() // now x is -3 assert(x == -3) // Static properties and methods assert(Double.maximum(-5.5, 2) == 2) assert(Double.minimum(-5.5, 2) == -5.5) assert(Double.pi.isFinite) assert(!Double.infinity.isFinite) assert(Double.nan.isNaN) // Remember your IEEE 754 Encoding? assert(Double.greatestFiniteMagnitude.nextUp == Double.infinity) assert(Double.leastNonzeroMagnitude.nextDown == 0.0) assert(Double.leastNonzeroMagnitude.isSubnormal) assert(8.0.isNormal)
%
operator remainder or modulo? (Hint: try -19 % 7. Did you get -5 or 2? What does that mean?)
OMG SWIFT KNOWS ABOUT GRAPHEME CLUSTERS! When you ask Swift for the count of elements in the string, it counts what you see. Someone needs a big thank you for doing stuff right.
// Grapheme: x in a circle // Characters: Lowecase x + Combining circle let s3 = "x\u{20dd}" assert(s3.count == 1) // Yep, it’s a grapheme! assert(s3.utf16.count == 2) // 0078 20DD assert(s3.utf8.count == 4) // 78 E2 83 9D // Grapheme: Lowecase e with acute accent // Characters: Lowercase e + Combining acute let s1 = "e\u{301}" assert(s1.count == 1) // Swift knows this a grapheme let s2 = "\u{e9}" // the precomposed character assert(s1 == s2) // == compares the graphemes, AMAZING! // Grapheme: Woman surfer skin-tone 3 // Characters: Surfer + Skin Tone 3 + Joiner + Female Sign let s4 = "🏄🏻♀" assert(s4.count == 1) assert(s4.unicodeScalars.count == 4) assert(s4.utf16.count == 6) assert(s4.utf8.count == 14) let s5 = "🏄\u{1F3FB}\u{200D}♀" assert(s4 == s5) // Grapheme: Woman scientist // Characters: Woman + Joiner + Microscope let s6 = "👩\u{200d}🔬" assert(s6.count == 1) assert(s6.unicodeScalars.count == 3) assert(s6.utf16.count == 5) assert(Array(s6) == ["👩🔬"]) assert(Array(s6.utf16) == [55357, 56425, 8205, 55357, 56620]) assert(Array(s6.utf8) .map { String($0, radix:16) } .joined(separator: " ") == "f0 9f 91 a9 e2 80 8d f0 9f 94 ac")
Apple says “Strings in Swift are Unicode correct and locale insensitive, and are designed to be efficient.” We’ll see the efficiency part later. But now, how about some more String things?
assert(String(repeating: "yes", count: 3) == "yesyesyes") let poem = """ Today is a good day ☀️ """ assert(poem.hasPrefix("Today is\na good")) assert(poem.hasSuffix("ood\nday ☀️")) assert(poem.contains("o")) assert(!poem.contains("O")) assert(!poem.isEmpty) assert(poem.uppercased() == "TODAY IS\nA GOOD\nDAY ☀️") assert(poem < "Tomorrow") assert(String(127, radix: 16) == "7f") assert(String(127, radix: 16, uppercase: true) == "7F") var s = "hello" s.append("world") assert(s == "helloworld") assert(s + " people" == "helloworld people") s += "!!!" assert(s == "helloworld!!!") s = "😂😎🤔🎃" s.removeFirst() assert(s == "😎🤔🎃")
Text processing is a huge topic! Check out the Apple Standard Library Documentation for the String type and the Character type.
These are all generic value types (structs) in the standard library. Take advantage of shorthand notation: write Array<T>
as [T]
and write Dictionary<K,V>
as [K:V]
. Some array examples (no substitute for a full dive into the documentation, of course):
var a = [10, 20, 30] var b = [Int](repeating: 1, count: 4) a[2] = 40 a.append(50) a += b assert(a.count == 8) a[1...3] = [0, 2, 4] // Subscripts can be ranges! a.remove(at: 3) assert(a == [10, 0, 2, 1, 1, 1, 1]) assert(a[2..<5] == [2, 1, 1]) // Half-open range assert(a[1..4] == [0, 2, 1, 1]) // Closed range assert(a[5...] == [1, 1]) // Partial range from assert(a[...3] == [10, 0, 2, 1]) // Partial range through assert(a[..<3] == [10, 0, 2, 1]) // Partial range upto
Sets need a little more syntax. Set literals look like array literals, and Swift favors arrays:
let a: Set<Int> = [10, 20, 30] let b: Set<Int> = [20, 30, 40] assert(a.union(b).count == 4) assert(a.intersection(b) == Set([20, 30])) assert(a.contains(10)) assert(!b.isEmpty)
Here’s some dictionary fun:
let capitals = [ "Netherlands": "Amsterdam", "Aruba": "Oranjestad", "Cura\u{e7}ao": "Willemstad", "Sint Maarten": "Philipsburg", ] assert(capitals.count == 4) assert(capitals.keys.contains("Aruba")) assert(capitals.values.sorted().last == "Willemstad") for (country, capital) in capitals { print("The capital of \(country) is \(capital).") }
Yay, no billion dollar mistake in Swift. Sure, there is a value called nil
, but it does not belong to the type String
. It belongs to the type String?
though. This is nice: if there’s a chance you might need nil
, you need a different type.
String
consists ONLY of strings. Yasss! No billion-dollar mistake.String?
consists of all the strings AND the value nil
.Think of an optional as a box. A value of Int?
is either a box with an Int in it, or an empty box:
Optional.none
Optional.some(5)
// Q: How do I specify the empty box? A: nil let firstGreatGrandchild: String? = nil // Q: How do put something in a box (i.e., WRAP the optional)? // A: Just assign, Swift will know what to do let supervisor: String? = "Alice" // Q: How do I unwrap? A: There are many ways, but the best is to // write code to handle *both* the something-is-there case and the // something-is-not-there case. Take advantage of Swift allowing // bindings in its compound statements (if, guard). if-let is ugly // because error handling is at the end of fun and there’s too much // nesting for s in ["42", "dog", "3xy", "-2E3", "-1"] { if let i = Int(s) { print("\"\(s)\" parsed as an integer is \(i)") print("I can do actual work now") print("Kind of ugly I'm so nested") } else { print("\"\(s)\" can't be parsed to an integer") } } // guard-else better!! Error handled immediately; less nesting, awesome! for s in ["42", "dog", "3xy", "-2E3", "-1"] { guard let i = Int(s) else { print("\"\(s)\" can't be parsed to an integer") continue } print("\"\(s)\" parsed as an integer is \(i)") print("I can do actual work now") print("All the work is in the happy path of the func") print("Not nested how pretty") } // Q: But what if I just want to unwrap and I have a default value? // A: You are in luck! There is the Nil Coalescing operator (??). var frequencies = ["A": 50, "B": 22] frequencies["A"] = (frequencies["A"] ?? 0) + 1 frequencies["C"] = (frequencies["C"] ?? 0) + 1 assert(frequencies == ["A": 51, "B": 22, "C": 1]) // Q: Can I chain optionals? A: Yes, the syntax is very similar to // JavaScript's (just slightly different when subscripting and // invocation) assert(frequencies["A"]?.description == "51") assert(frequencies["X"]?.description == nil) // Q: Can I just unwrap the darn thing and not have to worry about // if-let, guard-let, and all this syntax. Just let me unwrap! I know // what the heck I am doing. A: Okay, you can, but if your optional // is nil, your program will crash hard. No backsies! let message: String? = "I got this" print(message!)
As is typical for a modern, statically typed language, function declarations need types to be specified for parameters and return values. But here’s something pretty cool: every parameter has BOTH a parameter name and an argument label:
// By default, parameter name and argument label are the same func perimeter(base: Int, height: Int) -> Int { return 2 * (base + height) } // We can specify both the parameter name and argument label func average(of x: Int, and y: Int) -> Double { return Double(x + y) / 2.0 } // Underscore means no argument label may appear in call func middle(_ x: Int, _ y: Int) -> Double { return Double(x + y) / 2.0 } assert(perimeter(base: 4, height: 20) == 48) assert(average(of: 5, and: 8) == 6.5) assert(middle(5, 8) == 6.5)
The functions above appear in documentation as perimeter(base:height:)
, average(of:and:)
, and middle(_:_:)
.
Yes, Swift has default parameter values, variadic parameters, and inout parameters:
func moveUp(_ level: inout Int, by steps: Int = 1) { level += steps } var level = 10 moveUp(&level) assert(level == 11) moveUp(&level, by: 8) assert(level == 19) func zeros(_ items: Int...) -> Int { return items.filter{ $0 == 0 }.count } assert(zeros() == 0) assert(zeros(2, 3) == 0) assert(zeros(2, 0, 5, 0, 0, 0, 1, 0) == 5)
What’s the deal with inout
? Parameters cannot be changed inside the function unless you prefix their type with inout
. This modifies that value of the corresponding argument (woah). Swift requires that inout
parameters:
&
to make it clear at the point of call that the argument might be changedHopefully, you won’t get confused with this, but here goes:
However, most of the time we use the term “closure” in Swift, we refer to a closure expression. There are two forms:
{ x, y in 2 * x + y }
{ 2 * $0 + $1 }
If the final parameter of a function is a closure, you get a very convenient syntax, writing the closure after the argument list.
// If the last parameter is a closure func addThenOperate(_ x: Int, _ y: Int, then f: (Int) -> Int) -> Int { return f(x + y) } // You can call it normally or use a shorthand syntax assert(addThenOperate(5, 3, then: { $0 * $0 }) == 64) assert(addThenOperate(5, 3) { $0 * $0 } == 64) // The shorthand syntax is very common in pipelines func sumOfCubesOfOdds(from a: [Int]) -> Int { return a.filter{ $0 % 2 != 0 }.map{ $0 * $0 * $0 }.reduce(0, +) } assert(sumOfCubesOfOdds(from: [8, 3, 1, 8, -1]) == 27)
Structures and classes are types from which you create instances. These types provide:
var
, let
)init
)func
)subscript
)Structs are the simplest. Here is a very simple struct, illustrating how properties can be either stored properties or computed properties.
import Foundation // for sqrt let π = Double.pi struct Circle { var radius: Double // stored property var area: Double { // computed property get {return π * radius * radius} set(a) {radius = sqrt(a/π)} } } var c = Circle(radius: 10) assert(c.radius == 10 && c.area == 100*π) // invokes get c.area = 16*π // invokes set assert(c.radius == 4 && c.area == 16*π)
This struct had properties only. We got an initializer for free though, because structs give you one that take all the stored properties in order. Here’s one with an explicit initializer, a subscript, and a few methods, even mutating methods (you know what those are, right?).
import Foundation struct Triangle: CustomStringConvertible { static let sides = 3 var vertices: [(Double, Double)] init(v1: (Double, Double), v2: (Double, Double), v3: (Double, Double)) { vertices = [v1, v2, v3] } subscript(index: Int) -> (Double, Double) { get { return vertices[index] } set { vertices[index] = newValue } } func perimeter() -> Double { let ((x1, y1), (x2, y2), (x3, y3)) = (vertices[0], vertices[1], vertices[2]) return hypot(x1-x2, y1-y2) + hypot(x2-x3, y2-y3) + hypot(x3-x1, y3-y1) } func area() -> Double { let ((x1, y1), (x2, y2), (x3, y3)) = (vertices[0], vertices[1], vertices[2]) return abs((x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2)) / 2) } mutating func translateBy(dx: Double, dy: Double) { self.vertices = self.vertices.map{ (x, y) in (x + dx, y + dy) } } mutating func scaleBy(sx: Double, sy: Double) { self.vertices = self.vertices.map{ (x, y) in (x * sx, y * sy) } } mutating func rotate(by θ: Double) { self.vertices = self.vertices.map{ (x, y) in (x*cos(θ)-y*sin(θ), x*sin(θ)+y*cos(θ)) } } var description: String { return "<Triangle {\(self.vertices.map({ "\($0)" }).joined(separator: "-"))}>" } } var t = Triangle(v1: (0, 0), v2: (3, 0), v3: (0, 4)) t.translateBy(dx: 10, dy: -3) assert(t.description == "<Triangle {(10.0, -3.0)-(13.0, -3.0)-(10.0, 1.0)}>") assert("*\(t)*" == "*<Triangle {(10.0, -3.0)-(13.0, -3.0)-(10.0, 1.0)}>*") assert(t.perimeter() == 12) assert(t.area() == 6) t.rotate(by: 3) t.scaleBy(sx: 5, sy: 5) assert(abs(t.area() - 150) < 1e-10) assert(abs(t.perimeter() - 60) < 1e-10) assert(Triangle.sides == 3)
We also illustrated a type property. In Swift we have instance properties and type properties, and we have instance methods and type methods.
Oh, CustomStringConvertible
is a protocol; we’ll see those later.
Let’s talk about classes now. Structures are value types but classes are reference types. Classes also have more functionality than structures: classes can have deinitializers, they can inherit from superclasses, and they have run-time type checking and casting. That’s quite a bit more! Here’s a class that’s unfortunately a bit too contrived. There are better examples online. Somewhere.
// First, a superclass. Properties are defined with let, so they // can't be changed after they are set by an initialzer. class Rectangle { let width: Double let height: Double init(width: Double, height: Double) { self.width = width self.height = height } var description: String { return "Rectangle \(self.width) x \(self.height)" } func area() -> Double { return self.width * self.height } } class Square: Rectangle { init(side: Double) { super.init(width: side, height: side) } convenience init() { self.init(side: 1.0) } override var description: String { return "Square with side \(self.width)" } } // You can create instances as usual let r = Rectangle(width: 20, height: 30) assert(r.width == 20) assert(r.area() == 600) assert(r.description == "Rectangle 20.0 x 30.0") let s = Square(side: 50) assert(s.width == 50) assert(s.area() == 2500) assert(s.description == "Square with side 50.0") // Now since squares are rectangles, you can do this: let rekt = Square(side: 2.18281828) let rekts = [r, s, rekt, Rectangle(width: 3, height: 5)] // You can type check assert(!(rekts[0] is Square)) assert(rekts[1] is Square)
We’ll discuss deinitializers and casting later.
Swift has a pretty decent disjoint sum type, yay. Simple example first:
enum Token { case plus case minus case number(Double) case identifier(String) func lexeme() -> String { switch self { case .plus: return "+" case .minus: return "-" case .number(let value): return String(value) case .identifier(let name): return name } } } let expression: [Token] = [.number(4.0), .plus, .identifier("x")] let text = expression.map{ $0.lexeme() }.joined(separator: " ") assert(text == "4.0 + x")
Values of an enumeration type may be assigned raw values, from the enumeration’s underlying raw type. This allows additional information to be associated with each enum value.
enum Suit: String { case spades, hearts, diamonds, clubs } enum Direction: Int { case north = 0, east = 90, south = 180, west = 270 } assert(Suit.hearts.rawValue == "hearts") assert(Direction.south.rawValue == 180) assert(Direction.west == Direction(rawValue: 270))
Recursive enumerations need to be marked with indirect
:
indirect enum Tree<T> { case empty case node(Tree, T, Tree) var size: Int { switch self { case .empty: return 0 case .node(let left, _, let right): return left.size + 1 + right.size } } } let t: Tree<Int> = .node(.node(.empty, 3, .empty), 7, .empty) assert(t.size == 2)
Hey did we just make a generic type? We did! Hang on for more details. Coming soon.
Do you want to add properties or initializers or methods or subscripts to a struct or class or protocol “later”? YOU CAN DO THIS!
extension Int { var abs: Int {return self >= 0 ? self : -self} func factorial() -> Int { return (1...self).reduce(1, {$0 * $1}) } static var allOnes: Int {return ~0} } assert(8.abs == 8) assert(Int.allOnes == -1) assert(10.factorial() == 3628800)
The built-in operators are defined in the standard library. The unary operators are:
Operator | Fixity | Description |
---|---|---|
+ | Prefix | Unary plus |
- | Prefix | Unary negation |
! | Prefix | Logical NOT |
~ | Prefix | Bitwise NOT |
..< | Prefix | Partial Range UpTo |
... | Prefix | Partial Range Through |
... | Postfix | Partial Range From |
The binary (and ternary) operators in Swift are all infix, and from highest to lowest precedence, are:
Group | Operator | Description | Associativity |
---|---|---|---|
BitwiseShiftPrecedence | << >> |
Bitwise left shift, Bitwise right shift | None |
MultiplicationPrecedence | * / % &* & |
Multiplication, Division, Remainder, Multiplication ignoring overflow, Bitwise AND | Left |
AdditionPrecedence | + - &+ &- | ^ |
Addition, Subtraction, Modular addition, Modular subtraction, Bitwise OR, Bitwise XOR | Left |
RangeFormationPrecedence | ..< ... |
Half-open range, Closed range | None |
CastingPrecedence | is as as? as! |
Type check, Type cast, Type cast returning optional, Type cast that can crash | Left |
NilCoalescingPrecedence | ?? |
Nil Coalesce | Right |
ComparisonPrecedence | < <= > >= == != === !== ~= .= .!= .< .<= .> .>= |
Less, Less or equal, Greater, Greater or equal, Equal, Not equal, Identical, Not identical, Matches, Pointwise equal, Pointwise not equal, Pointwise less, Pointwise less or equal, Pointwise greater, Pointwise greater or equal | None |
LogicalConjunctionPrecedence | && |
Logical AND | Left |
LogicalDisjunctionPrecedence | || |
Logical OR | Left |
DefaultPrecedence | Any custom binary operator you define yourself without specifying a precedence level | None | |
TernaryPrecedence | ?: |
Ternary conditional | Right |
AssignmentPrecedence | = *= /= %= += -= &= |= ^= <<= >>= |
Assign | Right |
An example is the best way to see this:
import Foundation struct Vector: CustomStringConvertible { let i: Double let j: Double func magnititude() -> Double { return sqrt(i * i + j * j) } var description: String { return "<\(i),\(j)>" } } func + (left: Vector, right: Vector) -> Vector { return Vector(i: left.i + right.i, j: left.j + right.j) } func * (left: Vector, right: Vector) -> Double { return left.i * right.i + left.j * right.j } prefix func - (v: Vector) -> Vector { return Vector(i: -v.i, j: -v.j) } let u = Vector(i: 3, j: 4) let v = Vector(i: -5, j: 10) assert(u.i == 3) assert(u.j == 4) assert(u.magnititude() == 5) assert((u + v).description == "<-2.0,14.0>") assert(u * v == 25) assert((-v).description == "<5.0,-10.0>")
If you want to declare your own operators, you can! For infix operators, you can use an existing precedence group, create your own precedence group, or use the default one; you can define your own associativity (left
, right
, or none
); the default is none
.
precedencegroup JustForFun { higherThan: AdditionPrecedence lowerThan: MultiplicationPrecedence associativity: left } infix operator ~|*|~ : JustForFun postfix operator ^^ func ~|*|~ (x: Int, y: Int) -> Int { return 2 * x + y } postfix func ^^ (x: Int) -> Int { return x - 2 } assert(8^^ ~|*|~ 3 == 15)
Time for the important stuff! Apple says Swift is the world’s first protocol oriented language. So what is a protocol? See what Apple has to say about them in the language guide. Maybe go through this tutorial. Ok TL;DR the language guide says “a protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements.” Super simple examples to start:
import Foundation protocol Summarizable { var summary: String { get } } struct Circle: Summarizable { var radius = 1.0 var summary: String { return "Circle with radius \(radius)" } } enum Direction: Int, Summarizable { case north, east, south, west var summary: String { return "Bearing \(90 * self.rawValue)" } } let a: [Summarizable] = [Circle(radius: 5), Direction.west] assert(a[0].summary == "Circle with radius 5.0") assert(a[1].summary == "Bearing 270")
Protocols are everywhere in Swift. They are even used in cases where you’d think classes and subclasses would be used. Apple has this to say:
Use Structures and Protocols to Model Inheritance and Share Behavior Structures and classes both support a form of inheritance.
Structures and protocols can only adopt protocols; they can't inherit from classes. However, the kinds of inheritance hierarchies you can build with class inheritance can be also modeled using protocol inheritance and structures.
If you're building an inheritance relationship from scratch, prefer protocol inheritance. Protocols permit classes, structures, and enumerations to participate in inheritance, while class inheritance is only compatible with other classes. When you're choosing how to model your data, try building the hierarchy of data types using protocol inheritance first, then adopt those protocols in your structures.
Oh and by the way, you can extend protocols too.
Let’s do a code-along! We’ll do the classic OOP example with shapes that can rectangles or circles, but instead of an abstract base class Shape
, we’ll make it a protocol, and we’ll make circles and rectangles structs instead of classes. Fun!
Since Swift is a statically typed language, you’d probably cry if you only had a single array type, a single set type, and a single array type, with no way to type-constrain the elements they could hold. You know you can constrain the elements, but what’s the theory behind it all?
When creating a generic type or function, you supply type parameters. The generic type or function is not a real type or function, but specific instantiations are. So in this script:
class Stack<T> { private var data: [T] = [] func push(_ item: T) { data.append(item) } func pop() -> T? { return data.popLast() } var count: Int { return data.count } } var numbers = Stack<Int>() var names = Stack<String>() numbers.push(30) names.push("Hello") print(names.pop() ?? "oops") assert(names.count == 0)
Stack
is not a real class (it’s more like a template for a class), but Stack<Int>
and Stack<String> are.
Similarly, in:
func twice<T>(_ f: (T) -> T, on x: T) -> T { return f(f(x)) } assert(twice({$0 * 5}, on: 10) == 250) assert(twice({(x, y) in (x + 1, x * y)}, on: (2.5, 10.0)) == (4.5, 87.5))
twice
is not a real function (it’s more like a template for a function), but twice<Int>
and twice<(Double, Double)>
are.
What do we mean by real class?
The Swift compiler will actually compile generic instantiations of the same template separately. So a generic type of function never results in compiled code itself; only its instantiations do.
You can require (constrain) generic type parameters so that any instantiating type argument must conform to a specific protocol (or protocols, believe it or not, see the docs for how). Here’s a trivial example:
func comparison_results<T: Comparable>(_ x: T, _ y: T) -> (T, T) { return (min: x < y ? x : y, max: x > y ? x : y) } assert(comparison_results(55, 13) == (min: 13, max: 55))
: Comparable
constraint and make note of the error. Also try invoking the function with two dictionaries, and make note of the error.
It seems like the bad or unexpected things that happen fall into three categories:
Error
instance, or return a Result<Success, Failure:Error> instance.When designing for fault-tolerance, design your operations so that they succeed or fail. You have two techniques in Swift to build such operations:
Result<Success, Failure:Error>
object, an enum with .success
holding the expected value and .failure
holding an error instance.An error instance is an instance of any type conforming to the protocol Error
. Here’s a long example:
import Foundation struct Song { let songId: String let lyrics: String let price: Int } struct User { let userId: String var name: String var currency: Int } let songs = [ "1": Song(songId: "1", lyrics: "Lalala", price: 10), "2": Song(songId: "2", lyrics: "Dumdumdum", price: 50), ] let validTokens = [ "ABC": User(userId: "alice", name: "Alice", currency: 30), "XYZ": User(userId: "bob", name: "Bob", currency: 15), ] enum SongFetchError: Error { case illegalToken case noSuchSong case insufficientCurrency(shortBy: Int) } /* * Example of a fetch that throws an error on failure. */ func retrieveSong(token: String, songId: String) throws -> Song { guard var user = validTokens[token] else { throw SongFetchError.illegalToken } guard let song = songs[songId] else { throw SongFetchError.noSuchSong } guard user.currency >= song.price else { throw SongFetchError.insufficientCurrency(shortBy: song.price - user.currency) } user.currency -= song.price return song } /* * Example of a fetch that returns a Result instance. */ func getSong(token: String, songId: String) -> Result<Song, SongFetchError> { guard var user = validTokens[token] else { return .failure(.illegalToken) } guard let song = songs[songId] else { return .failure(.noSuchSong) } guard user.currency >= song.price else { return .failure(.insufficientCurrency(shortBy: song.price - user.currency)) } user.currency -= song.price return .success(song) } // --------------------------------------------------------------------------------------- // HANDLING ERRORS THAT ARE THROWN // --------------------------------------------------------------------------------------- // General way: do-catch statement with try do { let song = try retrieveSong(token: "ABC", songId: "2") print("Sing along: \(song.lyrics)") } catch SongFetchError.illegalToken { print("Sorry, bad token") } catch SongFetchError.noSuchSong { print("Sorry, that's not there") } catch SongFetchError.insufficientCurrency(let need) { print("You need \(need) more currency units") } catch { print("I don't know what's going on") } // If you don't care about the error, you can make it an optional with "try?" let lyrics = (try? retrieveSong(token: "ABC", songId: "2"))?.lyrics ?? "Nothing to sing" print(lyrics) // If you KNOW IT WILL WORK you can force it with "try!" but beware let song = try! retrieveSong(token: "XYZ", songId: "1") print(song.lyrics) // --------------------------------------------------------------------------------------- // HANDLING ERRORS IN RESULT INSTANCES // --------------------------------------------------------------------------------------- // General way: switch on .success and .failure switch getSong(token: "ABC", songId: "2") { case .success (let song): print("Sing along: \(song.lyrics)") case .failure(SongFetchError.illegalToken): print("Sorry, bad token") case .failure(SongFetchError.noSuchSong): print("Sorry, that's not there") case .failure(SongFetchError.insufficientCurrency(let need)): print("You need \(need) more currency units") } // You can ignore failure by just binding to .success if case .success = getSong(token: "XYZ", songId: "1") { print("Sing along: \(song.lyrics)") } // Invoking .get() on a result makes it throw on error, so // try result.get() can be used in a do-catch // try? result.get() ---> makes an optional // try! result.get() ---> will force
When would you return a Result
instance rather than throwing? Well one case is with async operations (not discussed on this page). Because if you throw from an async operation, your caller can’t catch it. But you can pass a result object to a callback, see? (Here’s an article.)
Now what about things that are just so bad that when they happen, your Swift program crashes hard? These runtime errors are not thrown and they can’t be caught. They are fatal. Your program just dies when they happen. Here’s an incomplete list of when they can occur, but it’s a start:
Int64.max + 10
Careful with !
, it’s an exclamation mark for a reason!
Like many modern languages, you aren’t limited to assigning to a single variable; you can bind using a pattern. And patterns can appear in lots of places, such as while, if, and guard conditions, and of course, in variable and constant declarations, the index of a for-statement, the cases of a switch statement, the catch clauses of a do-statement. There are several kinds of patterns, with various restrictions. We’ll lay them all out in code, with examples:
// The WILDCARD PATTERN for _ in 1...5 { print("Hey!") } // The IDENTIFIER PATTERN for number in 1...5 { print(number) } // The TUPLE PATTERN - matches only switch ("dog", 5) { case ("cat", 10): assert(false) case ("dog", 15 - 10): assert(true) case ("dog", 2): assert(false) default: assert(false) } // The VALUE BINDING PATTERN - matches and binds var point = (1, 2, 3) var matched = false if case (let x, _, let z) = point, x < 10 { assert(x == 1 && z == 3) matched = true } assert(matched) // The ENUMERATION CASE PATTERN // Matches any case of an enumeration // Cam only appear in // (1) switch case labels // (2) while, if, guard conditions // (3) for-in case enum Token { case print; case eq; case num(Int); case id(String)} let program: [Token] = [.id("x"), .eq, .num(3), .print, .num(100)] var square = Double.nan if case let .num(number) = program[2] { square = Double(number * number) } assert(square == 9) // The OPTIONAL PATTERN // Matches the values in a .some part of an optional // Just syntactic sugar for an Enumeration Case Pattern for Optionals // So can only appear where Enumeration Case Patterns can appear var todos: [String] = [] let advice: String? = "Breathe deeply" if case let todo? = advice { todos.append(todo) } assert(todos == ["Breathe deeply"]) // The TYPE CHECK ("is") PATTERN // Can ONLY appear in switch-statement case labels class Animal {} class Dog: Animal {} let pet: Animal = Dog() switch pet { case is Dog: assert(true) default: assert(false) } // The TYPE CASTING ("as") PATTERN let things: [Any] = [1, "dog", false] switch things[1] { case let n as Int: print("Ima number whose square is \(n * n)") case let s as String: print(s) default: print("I don't even know what's going on") } // The EXPRESSION PATTERN // Can ONLY appear in switch-statement case labels switch Int.random(in: -15...100) { case 0: print("Zero") case let n where n.isMultiple(of: 10): print("Multiple of 10") case ...0: print("Negative") case 2: print("A couple") case ...5: print("Not many") default: print("Quite a few") }
For more on patterns, read this extensive 4-part article on patterns, and of course, the section on patterns in the Swift book.
We are now ready to give a complete overview of the Swift statements. Swift statements are way more powerful and flexible than you might be used to in C: breaks and continues can be labeled, and there are ample cases for pattern-matching and destructuring, even directly in for-, if-, and while- statements. There are lovely guard
and defer
statements. Here is how they’re laid out:
Stmt ExpStmt DeclStmt LoopStmt ForInStmt WhileStmt RepeatWhileStmt BranchStmt IfStmt GuardStmt SwitchStmt ControlTransferStmt BreakStmt ContinueStmt FallthroughStmt ReturnStmt ThrowStmt DeferStmt DoStmt LabeledStmt CompilerControlStmt ConditionalCompilationStmt LineControlStmt DiagnosticStmt
The syntax for a few of these follows:
Many of these are familiar to C programmers, but there are a few new statements and some superpowers, like pattern matching on tuples and enums and optionals in compound statements and conditionals. The switch
is more modern: you have to opt-in to fallthrough. And remember, catching errors is much cleaner, and the defer
statement makes for guaranteed cleanup. Speaking of conditions, here’s the syntax:
The let
and var
bindings in a conditional are only for optionals, and in fact, they're really just syntactic sugar:
// The only two conditionals we need are the plain expression and the // *case condition*, namely "case Pattern = Exp". One of the patterns // is called an enum-case-pattern, and Optionals are enums with .some // and .none so we can unwrap optionals with normal pattern matching: if case .some(let x) = Int("100") { print(x * 3) } // The condition form "(let|var) pattern = Exp" is ONLY allowed to have // an initializing expression that is an optional. So the following is // really just syntactic sugar for the statement above: if let x = Int("100") { print(x * 3) }
Why are patterns in conditions so awesome?
Patterns often introduce bindings, and these new names are local to the construct in which they are defined. There’s no need to make a non-local variable just to use it in both the if or while condition and the statement body, as is required in many other languages.
Let’s see some statements in action:
let points = [(3, 8), (2, 5), (13, 3), (1, 21)] enum Token { case print; case eq; case num(Int); case id(String)} let program: [Token] = [.id("x"), .eq, .num(3), .print, .num(100)] // For-statement with pattern and where for (x, y) in points where y >= 8 { print(x, y) } // For-statement with case for case let Token.num(number) in program { print(number * number) } // Repeat-while statement var level = 0 repeat { level += Int.random(in: -3...10) print("Now at level \(level)") } while level < 90 // Multiple conditions in an if-statement if case let (x, y) = points[2], x * y > 20 { print("Vertically \(y) and horizontally \(x)") } // Defer statement: runs just before the function returns, whether // normally or because of a throw! struct BoxError: Error { } class Box { private(set) var isOpen = false func open() { print("opening"); isOpen = true } func close() { print("closing"); isOpen = false } func tinker() throws { throw BoxError() } } func doWork(_ box: inout Box) throws { defer { box.close() } box.open() try box.tinker() } var myBox = Box() try? doWork(&myBox) assert(!myBox.isOpen)
A safe language provides lots of guarantees about program behavior and has as few ambiguities as possible. It provides features to help the programmer write correct code.
In Swift:
defer
statement makes it easy to ensure resources are always cleaned up!
character makes you notice)Not only does Swift give a compile-time error when trying to use an identifier that has never been declared, it will give a compile-time error when using an identifier that has not been initialized.
var message: String print(message) // compile time error!
The compiler does control-flow analysis to make sure all uses of identifiers are on a path through the code where the identifier has been initialized, i.e., Swift is a definitive initialization language, unlike say Go, which provides (default implicit initialization).
The following blocks of code cause fatal, non-recoverable, crashes:
let lights = ["red", "amber", "green"] print(lights[200]) // CRASH
var x: Int8 = 100 var y: Int8 = 100 print(x + y) // CRASH
We can’t do much about the bounds checking other than to be careful, but if you want “wraparound” semantics for your arithmetic operators, use &+
and friends:
var x: Int8 = 100 var y: Int8 = 100 assert(x &+ y == -56) let sheep = (Array(0...5) as [Int16]).map({delta in 32765 &+ delta}) assert(sheep == [32765, 32766, 32767, -32768, -32767, -32766])
Relevant XKCD:
Pause and think. Structs, arrays, and dictionaries are all value types, meaning that they are copied when assigned or passed as arguments, so if they are large, how can copying possibly be efficient?
In practice, we rarely modify the copy of a large structure (in fact, the larger the structure, the more likely we are to use an inout
modifier and actually mutate it). But in the case where we do want to make a copy, we’re golden: Swift only physically makes a copy just before the first write. Prior to the first write (if any), large structures are indeed shared behind the scenes.
What happens to instances you create that are no longer accessible by any reference?
In most languages, a tracing garbage collector runs on a separate thread, preempting your program to look for and remove inaccessible instances. This is an expensive operation! And would you want to interrupt flight control software or medical software?
Swift doesn’t do tracing garbage collection, so that its performance characteristics are predictable. Instead, it uses automatic reference counting. Each instance has a reference count, starting at 0. Every time a reference is created for it, the count increases; when a reference goes away, the count decreases. When the count goes to zero, the instance is deallocated (and the deinitializer is called). Example:
var history: [String] = [] class C { init() {history.append("init")} deinit {history.append("deinit")} } func f(_ c: C) { // 3. count==2 history.append("f") print(c) history.append("/f") } // 4. c out of scope, count == 1 func main() { history.append("main") let x = C() // 1. object created, count==1 f(x) // 2. reference passed history.append("/main") } // 5. x out of scope, count == 0 main() assert(history == ["main", "init", "f", "/f", "/main", "deinit"])
In exchange for predictability, Swift requires you to understand how references are counted so that you do not end up with memory leaks. Sometimes you need weak references. Objects are only deallocated when their strong reference count goes to zero.
class Person { var name: String var boss: Person? weak var assistant: Person? init(name: String, boss: Person? = nil) { initCount += 1 self.name = name self.boss = boss } deinit { deinitCount += 1 } } func main() { let alice = Person(name: "Alice") let bob = Person(name: "Bob", boss: alice) alice.assistant = bob } var (initCount, deinitCount) = (0, 0) main() assert(initCount == 2 && deinitCount == 2)
We’re going to over this example, with drawings and animations. We’ll see the memory leak if both references were strong, and show how deallocation works beautifully when one of the references is weak.
We missed quite a lot, including, but not limited to:
Never
typeWe’ve covered: