Swift Notes
Swift Summary
Swift
- Swift eliminates entire classes of unsafe code. Variables are always initialized before use, arrays and integers are checked for overflow, memory is automatically managed, and enforcement of exclusive access to memory guards against many programming mistakes
- Another safety feature is that by default Swift objects can never be nil
- Swift has an innovative feature known as optionals. An optional may contain nil, but Swift syntax forces you to safely deal with it using the ? syntax to indicate to the compiler you understand the behavior and will handle it safely.
-
Swift is a successor to both the C and Objective-C languages. It includes low-level primitives such as types, flow control, and operators. It also provides object-oriented features such as classes, protocols, and generics, giving Cocoa and Cocoa Touch developers the performance and power they demand.
- Overview
- Use
let
to make a constant andvar
to make a variable -
String interlopation
let name = "John" let greeting = "Hello \(name)"
-
Create arrays and dictionaries using brackets (
[]
), and access their elements by writing the index or key in brackets. A comma is allowed after the last element.var myList = ["item1", "item2", "item3"] var myDictionary = [ "Key1": "Value1", "Key2": "Value2 ] myDictionary["Key1"] = "Value x" myList.append("item4") let emptyArray = [String]() let emptyDictionary = [String: Float]()
-
type information can be inferred
myList = [] myDictionary = [:]
- Control Flow
-
Use
if
andswitch
to make conditionals, and usefor
-in
,while
, andrepeat-while
to make loops. Parentheses around the condition or loop variable are optional. Braces around the body are required.let vegetable = "red pepper" switch vegetable { case "celery": print("Add some raisins and make ants on a log.") case "cucumber", "watercress": print("That would make a good tea sandwich.") case let x where x.hasSuffix("pepper"): print("Is it a spicy \(x)?") default: print("Everything tastes good in soup.") } // Prints "Is it a spicy red pepper?"
-
You can keep an index in a loop by using
..<
to make a range of indexes.var total = 0 for i in 0..<4 { total += i } print(total) // Prints "6"
-
Use
..<
to make a range that omits its upper value, and use...
to make a range that includes both values.
-
- Functions and Closures
-
Use
func
to declare a function. Call a function by following its name with a list of arguments in parentheses. Use->
to separate the parameter names and types from the function’s return type.func greet(person: String, day: String) -> String { return "Hello \(person), today is \(day)." } greet(person: "Bob", day: "Tuesday")
-
By default, functions use their parameter names as labels for their arguments. Write a custom argument label before the parameter name, or write
_
to use no argument label.func greet(_ person: String, on day: String) -> String { return "Hello \(person), today is \(day)." } greet("John", on: "Wednesday")
-
Use a tuple to make a compound value—for example, to return multiple values from a function. The elements of a tuple can be referred to either by name or by number.
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) { var min = scores[0] var max = scores[0] var sum = 0 for score in scores { if score > max { max = score } else if score < min { min = score } sum += score } return (min, max, sum) } let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9]) print(statistics.sum) // Prints "120" print(statistics.2) // Prints "120"
-
Functions can be nested. Nested functions have access to variables that were declared in the outer function. You can use nested functions to organize the code in a function that is long or complex.
func returnFifteen() -> Int { var y = 10 func add() { y += 5 } add() return y } returnFifteen()
-
Functions are a first-class type. This means that a function can return another function as its value.
func makeIncrementer() -> ((Int) -> Int) { func addOne(number: Int) -> Int { return 1 + number } return addOne } var increment = makeIncrementer() increment(7)
-
A function can take another function as one of its arguments.
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool { for item in list { if condition(item) { return true } } return false } func lessThanTen(number: Int) -> Bool { return number < 10 } var numbers = [20, 19, 7, 12] hasAnyMatches(list: numbers, condition: lessThanTen)
-
You can write a closure without a name by surrounding code with braces (
{}
). Usein
to separate the arguments and return type from the body.numbers.map({ (number: Int) -> Int in let result = 3 * number return result })
-
You have several options for writing closures more concisely. When a closure’s type is already known, such as the callback for a delegate, you can omit the type of its parameters, its return type, or both. Single statement closures implicitly return the value of their only statement.
let mappedNumbers = numbers.map({ number in 3 * number }) print(mappedNumbers) // Prints "[60, 57, 21, 36]"
-
You can refer to parameters by number instead of by name—this approach is especially useful in very short closures. A closure passed as the last argument to a function can appear immediately after the parentheses. When a closure is the only argument to a function, you can omit the parentheses entirely.
let sortedNumbers = numbers.sorted { $0 > $1 } print(sortedNumbers) // Prints "[20, 19, 12, 7]"
-
-
Objects and Classes
class Shape { var numberOfSides = 0 func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } }
-
Create an instance of a class by putting parentheses after the class name. Use dot syntax to access the properties and methods of the instance.
var shape = Shape() shape.numberOfSides = 7 var shapeDescription = shape.simpleDescription()
-
Set up the class when an instance is created. Use
init
to create one.class NamedShape { var numberOfSides: Int = 0 var name: String init(name: String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } }
- Use
deinit
to create a deinitializer if you need to perform some cleanup before the object is deallocated. - Subclasses include their superclass name after their class name, separated by a colon.
-
Methods on a subclass that override the superclass’s implementation are marked with
override
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 test = Square(sideLength: 5.2, name: "my test square") test.area() test.simpleDescription()
-
In addition to simple properties that are stored, properties can have a getter and a setter.
class EquilateralTriangle: NamedShape { var sideLength: Double = 0.0 init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides = 3 } var perimeter: Double { get { return 3.0 * sideLength } set { sideLength = newValue / 3.0 } } override func simpleDescription() -> String { return "An equilateral triangle with sides of length \(sideLength)." } } var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle") print(triangle.perimeter) // Prints "9.3" triangle.perimeter = 9.9 print(triangle.sideLength) // Prints "3.3000000000000003"
- In the setter for perimeter, the new value has the implicit name
newValue
. - If you don’t need to compute the property but still need to provide code that is run before and after setting a new value, use
willSet
anddidSet
-
When working with optional values, you can write ? before operations like methods, properties, and subscripting. If the value before the ? is nil, everything after the ? is ignored and the value of the whole expression is nil. Otherwise, the optional value is unwrapped, and everything after the ? acts on the unwrapped value. In both cases, the value of the whole expression is an optional value.
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") let sideLength = optionalSquare?.sideLength
-
- Enumerations and Structures
-
Use
enum
to create an enumeration. Like classes and all other named types, enumerations can have methods associated with them.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()
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
-
Use
struct
to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they are passed around in your code, but classes are passed by reference.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 and Extensions
-
Use
protocol
to declare a protocol.protocol ExampleProtocol { var simpleDescription: String { get } mutating func adjust() }
-
Classes, enumerations, and structs can all adopt protocols.
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 struct SimpleStructure: ExampleProtocol { var simpleDescription: String = "A simple structure" mutating func adjust() { simpleDescription += " (adjusted)" } } var b = SimpleStructure() b.adjust() let bDescription = b.simpleDescription
- Notice the use of the
mutating
keyword in the declaration of SimpleStructure to mark a method that modifies the structure. The declaration of SimpleClass doesn’t need any of its methods marked as mutating because methods on a class can always modify the class. -
Use
extension
to add functionality to an existing type, such as new methods and computed properties. You can use an extension to add protocol conformance to a type that is declared elsewhere, or even to a type that you imported from a library or frameworkextension Int: ExampleProtocol { var simpleDescription: String { return "The number \(self)" } mutating func adjust() { self += 42 } } print(7.simpleDescription) // Prints "The number 7"
-
- Error Handling
-
You represent errors using any type that adopts the
Error
protocol.enum PrinterError: Error { case outOfPaper case noToner case onFire }
-
Use
throw
to throw an error andthrows
to mark a function that can throw an error. If you throw an error in a function, the function returns immediately and the code that called the function handles the error.func send(job: Int, toPrinter printerName: String) throws -> String { if printerName == "Never Has Toner" { throw PrinterError.noToner } return "Job sent" }
-
There are several ways to handle errors. One way is to use
do
-catch
. Inside thedo
block, you mark code that can throw an error by writingtry
in front of it. Inside thecatch
block, the error is automatically given the name error unless you give it a different name. -
You can provide multiple
catch
blocks that handle specific errors. You write a pattern aftercatch
just as you do aftercase
in aswitch
.do { let printerResponse = try send(job: 1440, toPrinter: "Gutenberg") print(printerResponse) } catch PrinterError.onFire { print("I'll just put this over here, with the rest of the fire.") } catch let printerError as PrinterError { print("Printer error: \(printerError).") } catch { print(error) } // Prints "Job sent"
-
Another way to handle errors is to use
try?
to convert the result to an optional. If the function throws an error, the specific error is discarded and the result isnil
. Otherwise, the result is an optional containing the value that the function returned.let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler") let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
-
Use
defer
to write a block of code that is executed after all other code in the function, just before the function returns. The code is executed regardless of whether the function throws an error. You can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times.var fridgeIsOpen = false let fridgeContent = ["milk", "eggs", "leftovers"] func fridgeContains(_ food: String) -> Bool { fridgeIsOpen = true defer { fridgeIsOpen = false } let result = fridgeContent.contains(food) return result } fridgeContains("banana") print(fridgeIsOpen) // Prints "false"
-
- Generics
-
Write a name inside angle brackets to make a generic function or type.
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] { var result = [Item]() for _ in 0..<numberOfTimes { result.append(item) } return result } makeArray(repeating: "knock", numberOfTimes: 4)
-
You can make generic forms of functions and methods, as well as classes, enumerations, and structures.
// Reimplement the Swift standard library's optional type enum OptionalValue<Wrapped> { case none case some(Wrapped) } var possibleInteger: OptionalValue<Int> = .none possibleInteger = .some(100)
-
Use
where
right before the body to specify a list of requirements—for example, to require the type to implement a protocol, to require two types to be the same, or to require a class to have a particular superclass.func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool where T.Element: Equatable, T.Element == U.Element { for lhsItem in lhs { for rhsItem in rhs { if lhsItem == rhsItem { return true } } } return false } anyCommonElements([1, 2, 3], [3])
-
Writing
<T: Equatable>
is the same as writing<T> ... where T: Equatable
.
-
- Types
String
Int
,UInt
,Int32
,UInt32
,Int64
,UInt64
,Int8
,UInt8
Doulbe
,Float
- Integer prefix: Decimal, binary
0b
, octal0o
, hexadecimal0x
- Floating-point prefex: Decimal , hexadecimal
0x
Bool
- Tuple
(404, "Not Found")
is a tuple of type(Int, String)
- Use
SwiftUI
- SwiftUI uses a declarative syntax so you can simply state what your user interface should do.