Awesome
Swift Cheat Sheet
Notes taken from The Swift Programming Language.
Topics
- The Basics
- Basic Operators
- Strings and Characters
- Collection Types
- Control Flow
- Functions
- Closures
- Enumerations
- Classes & Structures
- Properties
- Methods
- Subscripts
- Inheritance
- Initialization
- Deinitialization
- Automatic Reference Counting
- Optional Chaining
- Type Casting
- Nested Types
- Extensions
- Protocols
- Generics
- Access Control
The Basics
Constants & Variables
-
Declaring Constants & Variables
// Constant let maximumNumberOfLoginAttempts = 10 // Variable var currentLoginAttempt = 0 var x = 0.0, y = 0.0, z = 0.0
-
Type Annotations
var welcomeMessage: String var red, green, blue: Double
- Rare that you need to write type annotations
- If you provide an initial value, Swift can infer the type to be used
- Rare that you need to write type annotations
-
Naming Constants and Variables
-
Constant/variable names can contain almost any character, including Unicode:
let π = 3.14159 let 你好 = "你好世界" let = "dogcow"
-
If const/var is reserved Swift keyword, surround with back ticks (`), avoid if you can
-
-
Printing Constants & Variables
println(friendlyWelcome) println("Current value \(friendlyWelcome)")
Comments
// this is a comment
/* multiple lines comment */
/* /* nested multilines supported */ */
Semicolons
// No semicolon, unless for multiple statements on a single line
let cat="hello"; println(cat)
Integers
-
Whole numbers with no fractional component, signed or unsigned
-
Swift provides signed & unsigned integers in 8, 16, 32 and 64 bits
- Follow naming convention similar to C, e.g.
- UInt8
- Int32
- Follow naming convention similar to C, e.g.
-
Integer Bounds
let minValue = UInt8.min // 0 let maxValue = UInt8.max // 255
-
Int
- If you don't need specific size, use Int, same size with current platform's native word size
- 32-bit platform, Int = Int32
- 64-bit platform, Int = Int64
- Unless you need to use specific size, always use Int, for codes consistency and interoperability.
- On 32-bit platform Int values: -2,147,483,648 => 2,147,483,648, large enough
- If you don't need specific size, use Int, same size with current platform's native word size
-
UInt
- When you specifically need unsigned integer, else Int is preferred
- A consistent use of Int aids code interoperability, avoids the need to convert to different number types, matches integer type inference as described in Type Safety and Type Inference
Floating Point Numbers
- Numbers with a fractional component, e.g. 3.14159
- Represent a much wider range of values than integer types
- Can store numbers much larger/smaller than can be stored in an Int
- Types:
- Double
- 64-bit floating point number
- Use it when floating-point values must be very large or particularly precise
- 64-bit floating point number
- Float
- 32-bit floating point number
- Use it when floating point values do not require 64-bit precision
- 32-bit floating point number
- Double
- Double has a precision of at least 15 decimal digits, and Float 6 decimal digits
Type Safety and Type Inference
-
Type safe language
- Encourages you to be clear about types of values
- Type checking, compile errors if there is a mismatch
-
Type Inference
- Does not mean you nave to specify the type of every constant/variable
- Enables a compiler to deduce the type of a particular expression automatically by examining the values you provide
- Because of type inference, Swift requires fewer type of declarations than C or Objective-C
let meaningOfLife = 42 // Inferred as Int let pi = 3.14159 // Infered as double, Swift always chooses Double rather than Float let anotherPi = 3 + 0.14 // Also inferred as double
Numeric Literals
-
All of these integer literals have a decimal value of 17:
let decimalInteger = 17 let binaryInteger = 0b10001 // 17 in binary notation let octalInteger = 0o21 // 17 in octal notation let hexadecimalInteger = 0x11 // 17 hexa decimal notation
-
Floating-point literals can be decimal or hexadecimal.They must have a number on both sides of the decimal point
-
Optional exponent, indicated by and uppercase/lowercase e for decimal floats or an p for hexadecimal floats
-
For decimal numbers with an exponent of exp, the base number is multiplied by 10exp:
1.25e2 // = 1.25 x 102 = 125.0 1.25e-2 // = 1.25 x 10-2 = 0.0125
-
For hexadecimal numbers with an exponent of exp, the base is multiplied by 2exp:
0xFp2 // means 15x22 = 60.0 0xFp2 // means 15x2-2 = 3.75
-
All of these floating numbers have a decimal value of 12.1875
let decimalDouble = 12.1875 let exponentDouble = 1.21875e1 let hexDouble = 0xC.3p0
-
-
Numeric literals can contain extra formatting to make them easier to read
-
Padded with extra zeroes or contain underscores
-
Not affecting the value:
let paddedDouble = 000123.456 let oneMillion = 1_000_000 let justOverOneMillion = 1_000_000.000_000_1
-
Numeric Type Conversion
- Overview
- Use Int for most of the cases
- Use others only when needed
- Use others to catch any accidental value overflows & implicitly documents the nature of data being used
- Integer Conversion
-
Compilation error if a number does not fit into a variable integer type
let cannotBeNegative: UInt8 = -1
-
Because of limited range of values, you must opt in to number type conversion on a case-by-case basis
-
Opt-in approach prevents hidden conversion errors
-
To convert:
* let twoThousand: UInt16 = 2_000 * let one: UInt 8 - 1 * let twoThousandAndOne = twoThousand + UInt16(one)
-
SomeType(ofInitialValue) => default in Swift and pass in an initial value
- UInt16 has initialiser that accepts a UInt8 value
- You can't pass in any type here, it has to be a type which UInt16 provides an init
- Extending inits to cover other types covered in Extensions
-
- Integer & Floating Point Conversion
-
Conversion between integer to floating point has to be explicit:
let three = 3 let floatNumber = 0.14 let pi = Double(three) + floatNumber let intPi = Int(pi) // 3
-
Rules for combining numeric constants/variables are different than numeric literals
let c = 3 + 0.14 // is fine
- Because their type is inferred only at the point they are evaluated by the compiler
-
Type Aliases
-
Define alternative name for an existing type
-
When you want to refer to an existing type by a name, that is contextually more appropriate
-
When working with data of a specific size from an external source
typealias AudioSample = UInt16 var maxAmplitudeFound = AudioSample.min // UInt16.min = 0
Booleans
-
Bool
let orangesAreOrange = true
-
Type safety prevents this:
let i = 1 if i { // error } if i == 1 { // fine }
Tuples
-
Group multiple values into a single compound value
-
Values do not have to be the same type as each other
let http404Error = (404, "Not found") let (statusCode, statusMessage) = http404Error let (justStatusCode, _) = http404Error println("Status code is \(http404Error.0), message: \(http404Error.1)") let http200Status = (statusCode: 200, description: "OK") println("Status code: \(http200Status.statusCode)")
-
Useful to return values of functions, by returning a tuple with two distinct values, each of a different type the function provides more useful info about its outcome
-
Note
- Tuples useful for temporary groups of related values, not suited for creation of complex data structures
- If data structure likely to persist beyond a temp scope, model it as a class or structure.
Optionals
-
Use optional where a value may be present
-
If there is a value, it equals to x, or there isn't a value at all
-
Note
-
Optionals don't exist in C/Obj-C. Nearest is to return nil.
-
Only works for objects, doesn't work for structures, basic C types, and enums.
-
Usually Obj-C methods return a special value NSNotFound to indicate absence of a a value
-
Swift optionals let you indicate the absence of a value for any type at all
let possibleNumber = "123" let convertedNumber = possibleNumber.toInt() // converted number is inferred to be of type "Int?" or "optional Int"
- Because toInt might fail
- Optional Int is written as Int?
-
-
nil
-
Set an optional variable to a valueless by assigning to nil
var serverResponseCode: Int? = 404 // 404 serverResponseCode = nil // no value
-
nil cannot be used with non optional constants/variables
-
If your variables/constants may not have any value, use optional
-
Default to nil
var surveyAnswer: String? // nil
-
Note
- Swift's nil is different from Objective-C's.
- In Objective-C nil is a pointer to a nonexistent object
- In Swift it is not a pointer, it is an absence value of a certain type
- Optionals of any type can be set to nil, not just object types
-
-
If statements & Forced Unwrapping
if convertedNumber != nil { println("Coverted value: \(convertedValue)!")) }
- Trying to use ! to access non-existent optional value will trigger an error
-
Optional Binding
-
To find out whether an optional contains a value, if so make the value available as a temporary constant or variable
-
Can be used in if/while statement
if let constantName = someOptional { // statements }
-
-
Implicitly Unwrapped Optionals
-
Sometimes it is clear that an optional will always have a value
-
To remove the need to check and unwrap optional's value every time it is accessed
let assumedString: String! = "Implicitly unwrapped" let implicitString: String = unassumedString if assumedString != nil { println(assumedString) } if let definiteString = assumedString { ... }
-
If you try to access an implicitly unwrapped optional when it does not contain value, it will trigger runtime error, same with optional
-
Assertions
-
Some cases, not possible for code to continue, use assertions end code execution, to provide an opportunity to debug
let age = -3 assert(age >=0, "A person's age cannot be less than 0)
-
When to use Assertions
- An integer subscript index is passed to a custom subscript implementation, but the subscript index value could be too low or too high
- Value passed to a function, check for invalid value
- An optional value is currently nil, but a non-nil value is essential for subsequent code to execute
-
Assertion cause app to terminate, an effective way to check conditions before app is published
Basic Operators
Overview
- Operator is a special symbol or phrase that you use to check, change or combine values
- Supports most standard C operators, and improves several capabilities to eliminate common coding errors
- Assignment (=) does not return a value, to prevent mistakenly used for ===
- Arithmetic operators (+, -...) detect and disallow value overflow to avoid unexpected result
- You can opt-in to value overflow behaviour using Swift's overflow operator in "Overflow Operators"
- C lets you perform remainder(%) operator on floating numbers
- Provides two range operators a...<b and a...b
Terminology
// Unary
prefix: !b
postfix: i++
// Binary
2 + 3
// Ternary
a ? b : c
Assignment Operator
let b = 10
let (x,y) = (1,2)
if x = y { // invalid }
-
Does not allow value to be overflow
-
You can option to value overflow behaviour by using
a &+ b
-
Addition operator also supported for string "hello " + "world"
-
Two Character values, one char, one String, can be added together
let dog :Character = "" let cow: Character = "" let dogCow = dog + cow // dogCow is equal to "
Arithmetic Operators
-
Overview
- Addition (+)
- Substraction (-)
- MUltiplication (*)
- Division (/)
-
Remainder Operator
-
Works out how many multiples b will fit inside a and returns the value that is left over (remainder)
-
Known as modulo operator, its behaviour in Swift for negative numbers, strictly speaking a remainder rather than a modulo operation
-
Formula:
a % b a = (b × some multiplier) + remainder
-
Example:
9 % 4 = 1 -9 % 4 = -1 9 % -4 = 1
-
-
Floating-Point Remainder Calculations
-
Unlike the remainder operator in C or Obj-C, it also works on floating-point numbers:
8 % 2.5 // = 0.5
-
-
Increment & Decrement Operators
-
++ and ---
var i = 0 ++i // i = 1
-
If used as prefix, it increments the variable before returning its value
-
If used as suffix, it increments the variable after returning its value
var a = 0 let b = ++a // b = 1 let c = a++ // a = 2, but c = 1
-
-
Unary Plus Operator
let three = 3 let minusTree = -three // -3 let plusTree = -minusTree // 3
- Prepended without any space
-
Unary Plus Operator
-
Returns the value without any change
let minusSix = -6 let alsoMinusSix = +minusSix // -6
-
Compound Assignment Operators
-
+=, -=
var a = 1 a += 2 // a = a + 2
-
Does not return a value
Comparison Operators
-
Supports all standard C comparison operators
a == b a != b a > b a < b a >= b a <= b
-
Swift also provides two identity operators === and !==
- Test two object references both refer to the same object instance
Ternary Conditional Operator
- question ? answer1 : answer2
Nil Coalescing Operator
-
a ?? b
-
Unwraps an optional a if it contains a value or returns a default value b if a is nil
-
a is always an optional type
-
Shorthand for
a != nil ? a! : b
Range Operators
-
Closed Range Operator
- a..b
- Defines a range that runs from a to be, and includes the values of a and b
for index in 1...5 { ... }
-
Half-Open Range Operator
a..<b
-
Defines a range that runs from a to b, but does not include b
-
Useful when work with zero-based lists, such as arrays
let names = ["Anna", "Alex", "Brian", "Jack"] let count = names.count for i in 0..<count { println("Person \(i + 1) is called \(names[i])") }
-
Logical Operators
-
Logical Not Operator (!a)
- Inverts Boolean value, true becomes false and vice versa
-
Logical AND Operator (a && b)
-
Logical OR Operator (a || b)
-
Combining Logical Operators
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword
-
Explicit Parantheses
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
Strings and Characters
Overview
"hello world"
- Represented by String, a collection of values of Character type
- Unicode compliant
- Swift's string is bridged seamlessly to Foundation's NSString, the entire NSString API is available to call on any String value you create, in addition to the String features described later on.
- You can also use String value with any API that requires NSString instance
- More info, check Using Swift with Cocoa & Obj-C
String Literals
let someString = "Some string literal value"
Initializing an Empty String
var emptyString = ""
var anotherEmptyString = String()
emptyString == anotherEmptyString
if emptyString.isEmpty { ... }
String Mutability
let constantString = "hello"
constantString += " world" // compilation error
String Are Value Types
- String value is copied when it is passed to a function or method or
- when it is assigned to a constant or a variable
- New copy is created, passed and assigned not the original version
- Note
- Different from NSString in Cocoa, it is also pass by reference
- Behind the scenes, Swift's compiler optimises string usage, so copying only takes place when necessary
Working with Characters
-
Strings are collection of characters
for char in "Dog!" { println(char) }
-
Create a single character
let yenSign: Character = "¥"
Concatenating Strings & Characters
var welcome = "string1" + "string2"
welcome.append("!")
String Interpolation
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
Unicode
-
An international standard for encoding, representing, and processing text in different writing systems
- Represent characters from any language in standardised form, and to read/write those chars to and from external source such as text file or web page
-
Unicode Scalars
- Swift's native String type is built from Unicode scalar values.
- A unique 21-bit number for a character/modifier such as:
- U+0061 for LATIN SMALL LETTER A ("a")
- U+1F425 for FRONT-FACING BABY CHICK ("")
- Note
- Unicode scalara is any unicode code point in the range of U+0000 to U+D7FF inclusive or U+E000 to U+10FFFF inclusive.
- Unicode scalars do not include the Unicode surrogate pair code points, which are code points in the range of U+D800 to U+DFFF inclusive
- Not all 21-bit Unicode scalars are assigned to a char, some are reserved for future assignment
-
Special Unicode Characters in String Literals
-
\0: null character
-
\: backslash
-
\t: horizontal tab
-
\n: line feed
-
\r: carriage return
-
": double quote
-
': single quote
-
An arbitrary unicode scalar, written as \u{n}, where n is between one and eight hexadecimal digits
"\u{24}" // $ "\u{2665}" // ♥ "\u{1F496}" // - Unicode scalar: U+1F496
-
-
Extended Grapheme Clusters
-
Every instance of Swift's Character type represents a single extended grapheme cluster
- A sequence of one or more Unicode scalars that (when combined) produce a single human-readable character
let eAcute: Character = "\u{E9}" // é let combinedEAcute: Character = "\u{65}\u{301}" // e followed by ́eAcute is é, combinedEAcute is é
-
To represent many complex script characters as a single character value.
- Hangul syllables from the Korean alphabet, can be represented as either a precomposed or decomposed sequence
let precomposed: Character = "\u{D55C}" // 한 let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, precomposed is 한, decomposed is 한 let enclosedEAcute: Character = "\u{E9}\u{20DD}" // enclosedEAcute is é⃝
-
Counting Characters
let unusualMenagerie = "Koala , Snail , Penguin , Dromedary"
println("unusualMenagerie has \(countElements(unusualMenagerie)) character
// prints "unusualMenagerie has 40 characters
-
Use of extended grapheme clusters for Character values means character count may not be affected
var word = "cafe" + "\u{301}" // café countElements(word) // 4
-
Note
- The number of chars in a string cannot be calculated without iterating through the whole string, beware of countElements must iterate over the whole string
- countElements is not the same with length
- length is based on the number of 16-bit code units within the string's UTF-16 representation, not the number of Unicode extended grapheme clusters within the string
- To reflect this fact, the length property from NSString is called utf16Count when it is accessed on a Swift String value
- length is based on the number of 16-bit code units within the string's UTF-16 representation, not the number of Unicode extended grapheme clusters within the string
Comparing Strings
-
String & Character Equality
- == and !=
- Two String values (or two Character values) are considered equal if their extended grapheme clusters are canonically equivalent
- If they have the same linguistic metalling or appearance, even if they are composed from different Unicode scalars behind the scene
"\u{E9}" == "\u{65}\u{301}" // é - true "\u{41}" == "\u{0410}" // false - latinCapitalLetterA & cyrillicCapitalLetterA different linguistic meaning
- Note
- String and character comparisons in Swift are not locale sensitive
-
Prefix & Suffix Equality
- hasPrefix, hasSuffix
Unicode Representations of Strings
-
UTF-8 Representation
- When written to a file, Unicode scalars are encoded in one of several Unicode-defined encoding forms.
- Each form encodes the string in small chunks known as code units, which include:
- UTF-8 encoding form, as 8-bit code units
- UTF-16
- UTF-32
- Several ways to access Unicode representation of strings
- Iterate with for-in, access each Character values as Unicode extended grapheme clusters.
- Access a String value in one of the three Unicode-compliant representation
- Collection of UTF-8 code units (string's utf8 property)
- Collection of UTF-16 units (utf16 property)
- Collection of 21-bit Unitcode scalar values, or equivalent to UTF-32 encoding form (unicodeScalars property)
- Next is an example different presentation of D, o, g, !! (double exclamation mark, U+203c) and (U+1F436)
- let dogString = "Dog‼"
-
UTF-8 Representation
for codeUnit in dogString.utf8 { print("\(codeUnit) ") } print("\n") // 68 111 103 226 128 188 240 159 144 182 // 68, 111, 103 = D, o, g // 226, 128, 188 = 3-byte UTF-8 representation of DOUBLE EXCLAMATION MARK character // "240, 159, 144, 182 = 4-byte UTF-8 representation of the DOG FACE
-
UTF-16 Representation
for codeUnit in dogString.utf16 { print("\(codeUnit) ") } print("\n") // 68 111 103 8252 55357 56374 // 8252 = decimal equivalent of hexadecimal value of 203C which represent Unicode+203C for double Exclamation Mark, this character represented as a single code unit in UTF-16 // 55357, 56374 = UTF-16 surrogate pair representation of the DOG FACE
- These values are high-surrogate value of U+83D (55357) and low-surrogate value U+DC36 (56374)
-
Unicode Scalar Representation
for scalar in dogString.unicodeScalars { print("\(scalar.value) ") println("\(scalar) ") // print the characters } print("\n") // 68 111 103 8252 128054 // 8252 = decimal of U+203C = double exclamation mark // 128054 = decimal of U+1F436 of the dog face character
Collection Types
Overview
- Arrays and dictionaries in Swift are always clear about the types of values and keys that they can store.
- You cannot insert a value of the wrong type
- Swift's array and dictionaries are implemented as generic collections.
Mutability of Collections
- If you create a collection and assign it to a variable, it is mutable
- It is immutable for a constant
Arrays
-
Overview
- Swift's Array differ from Objective-C's NSArray or NSMutableArray which can store any kind of object
- In Swift, the type of values is always made clear, explicitly or through type inference
-
Array Type Shorthand Syntax
- Array<SomeType> or [SomeType] (preferred)
-
Array Literals
[value 1, value 2, value 3] var shoppingList: [String] = ["Eggs", "Milk"]
-
Accessing and Modifying An Array
var shoppingList: [String] = ["Eggs", "Milk"] shoppingList.count shoppingList.isEmpty shoppingList.append("Flour") shoppingList += ["BakingPowder", "Cheese"] // Add new items to an array var firstItem = shoppingList[0] // subscript syntax shoppingList[0] = "Six eggs" shoppingList[4...6] = ["Bananas", "Apples"] // Shopping list now has 4 items shoppingList.insert("Maple Syrup", atIndex: 0) let mapleSyrup = shoppingList.removeAtIndex(0) let apples = shoppingList.removeLast()
- Note
- Subscript can't be used to append a new item to an array
- Out of range index, cause runtime error
- Note
-
Iterating Over an Array
for item in shoppingList { println(item) }
-
Enumerate function
for (index, value) in enumerate(shoppingList) { println("Item \(index + 1): \(value)") }
-
-
Creating and Initialising an Array
var someInts = [Int]() someInts.append(3) someInts = [] // reset to empty Int array var xyz = [] // runtime error, cause type unknown var threeDoubles = [Double](count: 3, repeatedValue: 2.5) // [2.5, 2.5, 2.5]
Dictionaries
-
Overview
- Swift dictionaries are specific about the types of keys and values
-
Dictionary Type Shorthand Syntax
- Dictionary<KeyType, ValueType> or [KeyType: ValueType] (preferred)
-
Dictionary Literals
[key 1: value 1, key 2: value 2, key 3: value 3] var airports: [String: String] = ["TYO": "Tokyo", "DUB": Dublin"]
-
If key and values are consistent, you could skip the type definition
var airports = ["TYO": "Tokyo", "DUB": "Dublin"]
-
-
Accessing and Modifying a Dictionary
airports.count airports.isEmpty airports["LHR"] = "London" airports.updateValue("Dublin International", forKey: "DUB") airports["APL"] = nil airports.removeValueForKey("DUB")
-
Iterating Over a Dictionary
for (airportCode, airportName) in airports { ... } for key in airport.keys { ... } for value in airport.values { ... }
- Swift's Dictionary is an unordered collection
-
Creating and Empty Dictionary
var namesOfIntegers = [Int: String]() // empty dictionary namesOfIntegers[:] // reset to empty, once the context is already known
-
Hash Values for Dictionary Key Types
- A type must be hash able to be used as dictionary's key type
- A hash value is an Int value that is the same for all object that compare equal if a == b, a.hashValue == b.hashValue
- All Swift's basic types are hash able by default
- Enumeration member values without associated values are hash able by default
- Note
- Make custom typ to conform to Hashable protocol
- To provide hashValue and "==" operator property implementation
- hashValue not required to be the same across different executions or in different programs
Control Flow
For Loops
-
For-In
-
Index is only within the scope of the loop
for index in 1...5 { println("\(index) times 5 is \(index * 5)") }
-
If you don't need the index value
for _ in 1...power { ... }
-
You can use for in for array and dictionaries too:
for name in names { ... } for (key, value) in dictionaries { ... }
-
String character
for char in "hello" { ... }
-
-
For
for var i = 0; i < 3; ++i { ... }
While Loops
-
While
while [cond] { ... }
-
Do-While
do { ... } while [cond]
Conditional Statements
-
If
if [cond] { ... } if [cond] { ... } else { ... } if [cond] { ... } else if [cond] { ... }
-
Switch
switch [value] { case [value 1]: // respond to value 1 case [value 2], [value 3]: // respond to value 2 or 3 default: // otherwise, do something else }
- default is a must
-
No Implicit Fall through
- In contrast with switch statements in C and Objective-C, switch statement in Swift do not fall through the bottom of each case
- Though break is not required in Swift, you can still use a break statement to match and ignore a particular case or to break out from a case before it is completed
- Body of each case must contain one executable statement
- To opt-in for fall through behaviour, use the fallthough keyword
-
Range matching
switch count { case 1...3: // statement here }
-
Tuples
-
You can use tuple to test multiple values in a switch statement
let somePoint = (1, 1) switch somePoint { case (0, 0): .... case (_, 0): ... case (-2...2, -2...2): ... default: ... }
-
-
Value Bindings
-
Bind value or values it matches to temporary constants or variables for use in the body of the case, known as value binding
switch anotherPoint { case (let x, 0): ... case let (x, y): ... }
-
Note that on this case the default statement is not required, cause every possible case as been catered for
-
-
Where
-
To check additional conditions
case let (x, y) where x == y:
-
Control Transfer Statements
- Continue
- Tells a loop to stop what it is doing, and start again at the beginning of the next iteration
- Break
- Break In a Loop Statement
- Break In a Switch Statement
- Used to match and ignore one or more cases in switch statement
- Switch does not allow empty case, to deliberately match & ignore a case
- Fallthrough
-
Switch statements in Swift do not fall through the bottom of each case into the next one
-
To enable fall through the next case use fallthrough keyword
case abc: // fallthrough case next: // executed again // fallthrough default: // executed again
-
Fallthrough does not check the next condition
-
- Labeled Statements
-
You can nest switch statement inside another loop statement
-
Sometimes it is important to be explicit which statement you want to break/continue
-
You can mark a loop statement or switch statement with a statement label
-
Format:
- [label name]: while [condition] { ... }
gameLoop: while [condition] { switch [variable] { case [condition] break grameLoop } }
-
Functions
Defining and Calling Functions
func sayHello(personName: String) -> String {
let greeting = "Hello, " + personName + "!"
return greeting
}
sayHello("Anna")
Function Parameters & Return Values
-
Multiple Input Parameters
func halfOpenRangeLength(start: Int, end: Int) -> Int { return end - start } println(halfOpenRangeLength(1, 10)) // prints "9"
-
Functions without Parameters
func sayHelloWorld() -> String { return "hello, world" } println(sayHelloWorld())
-
Functions without Return Values
func sayGoodbye(personName: String) { println("Goodbye, \(personName)!") } sayGoodbye("Dave")
-
Functions with Multiple Return Values
func minMax(array: [Int]) -> (min: Int, max: Int) { var currentMin = array[0] var currentMax = array[0] for value in array[1..<array.count] { if value < currentMin { currentMin = value } else if value > currentMax { currentMax = value } } return (currentMin, currentMax) } let bounds = minMax([8, -6, 2, 109, 3, 71]) bounds.min bounds.max
-
-
Optional Tuple Return Types
- -> (Int, Int)?
- It's different from (Int?, Int?)
- if let bounds = minMax(...) { ... }
Function Parameter Names
fund someFunction(paramName: Int) { ... }
* Param only be used inside the function
-
External Parameter Names
-
Sometimes it is useful to use a different param when you call a function
-
To do that, define an external parameter name for each parameter
func someFunction(paramExt paramName: Int) { ... } someFunc(paramExt: 2)
-
Consider using external param names when the purpose of a function's argument are not clear
-
-
Shorthand External Parameter Names
- If the local param name the same with external param name, use the hash symbol (#)
- func someFunction(#paramName: Int)
-
Default Parameter Values
-
Place params with default values at the end of a function's param list.
-
Ensures that all calls to the func use the same order for their non-default args
func someFunc(paramName: String = " ") { ... }
-
-
External Names for Parameters with Default Values
-
Swift provides an automatic external name for any param that has a default value, and it is the same with the paramName just like using the hash symbol
func someFunc(joiner: String = " ") someFunc(joiner: "-")
-
You can opt out of this behaviour by writing an underscore (_) instead of an explicit external name when you define a parameter
- Not recommended
-
-
Variadic Parameters
-
Accepts 0 or more values for a specified type
-
[someType]...
func average(numbers: Double...) -> Double average(1, 2, 3, 4, 5)
-
At most one variadic param, must be as the last in the parameter list
-
If there are default values as well, place variadic param after all the defaulted parameters
-
-
Constant & Variable Parameters
-
Function params are constants by default
-
Sometimes it is useful to have a variable copy to work with
func someFunc(var paramName: String)
-
Changes made to a variable param do not persist beyond the end of each call, not visible outside the function's body
-
Variable param only exists for the lifetime of that function call
-
-
In-Out Parameters
-
If you want a function to modify a parameter's value, and want the changes to persist.
-
Use 'inout' keyword at the start of the parameter definition
-
You can only pass a variable not a constant
-
You place an & before a variable's name when you pass it as an argument
-
Can not have default values, and variadic params can not be marked as inout. If you mark it as inout, you can't mark it as var or let
func someFunc(inout paramName: Int) var locName = 3 someFunc(&locName)
-
Function Types
// Function Type as a parameter type for another function
func printMathResult(mathFunc: (Int, Int) -> Int, a: Int, b:Int) { ... }
printMathResult(addTwoInts, 3, 5)
// Function Type as Return Types
func someFunc(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}
Nested Functions
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
Closures
Overview
- Three forms:
- Global functions are closures that have a name and do not capture any values
- Nested functions are closures that have name and can capture values from their enclosing function
- Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context
- Swift closures have clean & clear style with optimisations:
- Inferring parameter and return types from the context
- Implicit returns from single-expression closures
- Shorthand argument names
- Trailing closure syntax
Closure Expressions
-
A way to write inline closure in a brief focused syntax
-
The Sorted Function
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] func backwards(s1: String, s2: String) -> Bool { return s1 > s2 } var reversed = sorted(names, backwards)
-
Closure Expression Syntax
{ ([parameters] -> [return type] in [statements] }
-
Can use constant, variable and inout parameters.
-
Default values cannot be provided
-
Variadic parameters can be used
-
Tuples can also be used as parameter types and return types
reversed = sorted(names, { (s1: String, s2: String) -> Bool in return s1 -> s2 })
-
Closure can be written on a single line
-
-
Inferring Type from Context
-
Because sorting closure is passed as an argument to a function, Swift can infer the types of its parameters and return from the type of the sorted function's second parameter.
reversed = sorted(names, { s1, s2 in return s1 > s2 })
-
You never need to write an inline closure in it fullest form when closure is used as a function argument
-
You can still make types explicit to avoid ambiguity for readers
-
-
Implicit Returns from Single-Expression Closures
-
Single-expression closures can implicitly return the result of their single expression by omitting the return keyword from their declaration:
reversed = sorted(names, { s1, s2 in s1 > s2 })
-
-
Shorthand Argument Names
-
$0, $1, $2
-
You can omit closure's argument list from its definition
-
The in keyword can also be omitted because the closure expression is made up entirely of its body
reversed = sorted(names, { $0 > $1 })
-
-
Operator Functions
-
There's actually an even shorter way to write the closure expression above
-
Swift's String type defines its string specific implementation of the > operator as a function that has two parameters of type String, and returns Bool
-
It matches the function type needed for the sorted function
-
Thus Swift can infer that you want to use its string specific implementation:
reversed = sorted(names, >)
-
Trailing Closures
-
If closure is long and passed as function's final argument
-
Write outside of (and after) the parentheses of the function call it supports:
func someFunc(closure: () -> ()) { ... } someFunc({ // closure body }) someFunc() { // trailing's closure body }
-
If a closure expression is provided as the function's only argument, you provide that expression as a trailing closure, you can omit a pair of parentheses ()
reversed = sorted(names) { $0 > $1 }
-
Map example:
let strings = numbers.map { (var number) -> String in var output = "" while number > 0 { output = digitNames[number % 10]! + output number /= 10 } return output }
-
Capturing Values
-
Closure capture constants and variables from the surrounding context in which it is defined
-
Closure can refer and modify those values even if the original scope no longer exists
-
Simplest form of a closure in Swift is a nested function
func makeIncrementor(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementor() -> Int { runningTotal += amount return runningTotal } return incrementor } let incrementByTen = makeIncrementor(forIncrement: 10) incrementByTen() // 10 incrementByTen() // 20
-
If you assign a closure to a property of a class instance
- The closure captures that instance by referring to the instance or its members, this will create strong reference cycle
-
Swift uses capture lists to break these strong reference cycles, more info later
Closures are Reference Types
-
Whenever you assign a function or a closure to a constant or a variable, you are actually setting that constant or variable to be a reference to the function/closure
-
Example above, it is the choice of closure that incrementByTen refers to that is constant, not the contents of the closure itself
-
Meaning if you assign a closure to two different constants/variables, both refer to the same closure
let alsoIncrementByTen = incrementByTen alsoIncrementByTen()
Enumerations
Overview
- Defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code
- C enums assign related names to set a of integer values
- Enums in Swift are more flexible, and do not have to provide a value for each member of the enumeration
- If a value (raw value) is provided for each enumeration member, the value can be a string, a character, a value, of any integer or floating-point type
- Adopt many features traditionally supported only by classes, such as:
- computed properties to provide additional info about enum's current value
- instance methods
- define initialisers to provide an initial member value
- extended to expand their functionality
- conform to protocols
Enumeration Syntax
enum SomeEnumeration {
// enumeration definition goes here
}
enum CompassPoint {
case North
case South
case East
case West
}
-
Unlike C & Obj-C, Swift enumeration members are not assigned with a default integer value when they are created
- Instead, the different enumeration members are fully fledged values in their own right, with an explicitly defined type of CompassPoint
-
Multiple member values:
enum Planet { case Mercury, Venus, Earth }
-
Each enum defines a brand new type
-
Name should start with a capital letter
-
Singular format
var directionToHead = CompassPoint.West
-
Once directionToHead is defined, you can use a shorthand notation
directionHead = .East
Matching Enumeration Values with a Switch Statement
directionToHead = .South
switch directionToHead {
case .North:
println("Lots of planets have a north")
case .South:
println("Watch out for penguins")
case .East:
println("Where the sun rises")
case .West:
println("Where the skies are blue")
}
- Switch statement must be exhaustive considering an enumeration's members, if .West is omitted, there'll be a compilation error.
Associated Values
-
Sometimes it is useful to store associated values of other types along with these member values.
-
This enables you to store additional custom info along with the member value, and permit this info to vary each time you use that member in your code.
-
Associated value can be any given type, and the value type can be different for each member of the enumeration
-
Enumerations similar to these are known as discriminated unions, tagged unions and variants in other programming language.
enum Barcode { case UPCA(Int, Int, Int, Int) case QRCode(String) } var productBarCode = Barcode.UPCA(8, 85909, 51226, 3) productBarCode = .QRCode("ABCDEF")
-
Switch statement:
switch productBarcode { case .UPCA(let numberSystem, let manufacturer, let product, let check): println("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).") case .QRCode(let productCode): println("QR code: \(productCode).") } // prints "QR code: ABCDEFGHIJKLMNOP."
-
You can place a single var/let before the member name for brevity:
switch productBarcode { case let .UPCA(numberSystem, manufacturer, product, check): println("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).") case let .QRCode(productCode): println("QR code: \(productCode).") } // prints "QR code: ABCDEFGHIJKLMNOP.
-
Raw Values
-
Enumeration members can come repopulated with default values (raw values), which are all of the same type
enum ASCIIControlCharacter: Character { case Tab = "\t" case LineFeed = "\n" case CarriageReturn = "\r" }
-
Raw value has to be unique, raw value for a particular enumeration number is always the same
-
When integers are used for raw values, they auto-increment
enum Planet: Int { case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune } let earthsOrder = Planet.Earth.rawValue // 3 let possiblePLanet = Planet(rawValue: 2)
Classes & Structures
Overview
- Swift does not require you to create separate interface implementation files for custom classes and structures
Comparing Classes and Structures
-
Things in common
- Define properties to store values
- Define methods to provide functionality
- Define subscripts to provide access to their values using subscript syntax
- Define initializers to setup their initial state
- Extended to expand their functionality
- Conform to protocols
-
Classes have additional capabilities that structures do not:
- Inheritance
- Type casting
- Deinitializers
- Reference counting allows more than one reference to a class instance
- Structures are always copied when they are passed in your code, they don't use reference counting
-
Definition Syntax
-
Similar between Class & Structure
-
Syntax:
class SomeClass { ... } struct SomeStructure { ... }
-
When you define a new class or structure, you define a new Swift type.
struct Resolution { var width = 0 var height = 0 } class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 var name: String? }
-
-
Class and Structure Instances
let someResolution = Resolution() let someVideoMode = VideoMode()
-
Accessing Properties
someResolution.width // 0 someVideoMode.resolution.width // 0 someVideoMode.resolution.width = 1280
- Note
- Unlike Obj-C, Swift enables you to set sub properties of a structure property directly.
- You are able to set resolution.width directly, without the need to set the entire resolution property
- Note
-
Memberwise Initializers for Structure Types
-
Use it to initialise member properties of new structure instances
-
Initial values can be passed to the member wise init by name:
let vga = Resolution(width: 640, height: 480)
-
Class does not have default member wise initialiser.
-
Structures and Enumerations are Value Types
- A value type, means its value is copied when ascend to a variable or a constant or when it is passed to a function, including all types in Swift, i.e. integers, floating-point numbers, booleans, etc.
Classes Are Reference Types
- Reference types are not copied when they are assigned to a variable/constant or when they are passed to a function. Reference to the same instance is used instead.
- Identity Operators
- Comparing if two objects are the same instance (===)
- Equal means two instances having the same values (!==)
- pointers
- A swift constant or variable that refers to an instance is similar to a pointer in C, but is not a direct pointer to an address in memory and does not require you to write an asterisk
- These references are defined like any other constant/variable in Swift
Choosing Between Classes and Structures
- Consider structure if:
- Primary purpose is to encapsulate a few relatively simple data values
- Expect that the encapsulated values are copied rather than referenced
- Any properties stores by the structure a themselves of value types, which would be expected to be copied
- Does not need to inherit properties or behaviour from another existing type
- Examples of structures
- Size of a geometric shape, with width, height properties, both of type Double
- A way to refer ranges within a series perhaps encapsulating a start and length properties, both of type Int
- A point in a 3D coordinate system, perhaps encapsulating, x, y, z properties, of type Double
Assignment and Copy Behavior for Strings, Arrays, and Dictionaries
- String, Array, Dictionary types are implemented as structures
- Different from NSString, NSArray, and NSDictionary which are implemented as classes, which are passed by reference.
- Swift only performs actual copy behind the scenes when absolutely necessary, it manages all value copying to ensure optimal performance
Properties
Overview
- Associate values with a particular class, structure or enumeration
- Stored properties store constant and variable values as part of an instance
- Provided only by classes and structures
- Computed properties are provided by classes, structures and enumerations.
- Stored and computed properties are associated with instances, but properties can be associated to the type itself, known as type properties
- You can define property observer, for properties you define or properties that a subclass inherits from its superclass.
Stored Properties
-
Is a constant or variable that is stored as part of an instance of a particular class or structure
struct FixedLengthRange { var firstValue: Int let length: Int } var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) // the range represents integer values 0, 1, and 2 rangeOfThreeItems.firstValue = 6 // the range now represents integer values 6, 7, and 8
-
Stored Properties of Constant Structure Instances
-
When an instance of a value type is marked as a constant, so are all of its properties, but not true for classes.
let someStruct = SomeStruct(x: 0, y: 0) someStruct.x = 12 // compile-error
-
-
Lazy Stored Properties
-
Property whose initial value is not calculated until the first time it is used
-
Use 'lazy' modifier before its declaration
-
Must always declare lazy as a var
-
Useful for a property that is dependent on outside factors, whose values are not known after an initialisation completes, e.g. computationally expensive
class DataImporter { var fileName = "data.txt" // the DataImporter class would provide data importing functionality here } class DataManager { lazy var importer = DataImporter() var data = [String]() // the DataManager class would provide data management functionality here } let manager = DataManager() manager.data.append("Some data") manager.data.append("Some more data") // the DataImporter instance for the importer property has not yet been created
-
DataImporter instance for the importer property is only created when the importer property is first accessed, e.g.
- manager.importer.fileName
-
-
Stored Properties and Instance Variables
- Obj-C provides two ways to store values and references as apart of a class instance
- In additional to properties, you can use instance variables as a backing store for values store in a property
- Swift unifies these concepts into a single property declaration
- It does not have a corresponding instance variable, and the backing store for a property is not accessed directly
- To simplify and avoid confusion
- Obj-C provides two ways to store values and references as apart of a class instance
Computed Properties
-
Do not store a value, provides a getter and optional setting to retrieve and set other properties/values directly
struct Point { var x = 0.0, y = 0.0 } struct Size { var width = 0.0, height = 0.0 } struct Rect { var origin = Point() var size = Size() var center: Point { get { let centerX = origin.x + (size.width / 2) let centerY = origin.y + (size.height / 2) return Point(x: centerX, y: centerY) } set(newCenter) { origin.x = newCenter.x - (size.width / 2) origin.y = newCenter.y - (size.height / 2) } } } var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0)) let initialSquareCenter = square.center square.center = Point(x: 15.0, y: 15.0)
-
Shorthand Setting Declaration
-
If computed property's setting does not define a name for the new value to be set, "newValue" is used:
set { origin.x = newValue.x - (size.width / 2) origin.y = newValue.y - (size.height / 2) }
-
-
Read-Only Computed Properties
- Has getter but no setter
- You must declare computed properties, including read only properties as variable properties with 'var' keyword, because their value is not fixed.
- You can simplify read-only computed property by removing the get keyword and its braces:
struct Cuboid { var width = 0.0, height = 0.0, depth = 0.0 var volume: Double { return width * height * depth } } let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
Property Observers
- You can add property observers to any stored properties you define, apart from the lazy stored properties
- Any inherited property by overriding the property within a subclass
- Note
- You don't need to define property observers for non-overridden computed properties, because you can observe and respond to changes to their value directly from within the computed property's setter.
- You have the option to define either or both of these observers on a property
- willSet - called before the value is stored
- default param "newValue", you can override
- didSet - called immediately after the new value is stored
- default old property param name is "oldValue"
- willSet - called before the value is stored
- Note
-
willSet and didSet are not called when a property is first initialised, only called when its value is set outside the initialisation context
-
If you assign a value to a property within its own didSet observer, the new value will replace the one that you just set
class StepCounter { var totalSteps: Int = 0 { willSet(newTotalSteps) { } didSet { if totalSteps > oldValue { ... } } } }
-
Global and Local Variables
- Capabilities above to observe properties are for both global and local properties
- Global variables are defined outside any function, method, closure or type context
- Global constants and variables are always computed lazily, similar to Lazy Stored Properties
Type Properties
-
For value types (structures and enumerations), you can define stored and computed type properties,
-
For classes, you can define computed type properties only
-
Stored type properties for value types can be variables or constants.
- Computed type properties are always declared as variable properties in the same way as computed instance properties
-
Type Property Syntax
-
In C and Obj-C, you define static constants and variables associated with a type as global static variables
-
In Swift type properties are written as part of type's definition, with "static" keyword for value types, and and "class" for class types.
struct SomeStructure { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } } enum SomeEnumeration { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } } class SomeClass { class var computedTypeProperty: Int { // return an Int value here } }
-
The computed type property are for read-only, but you can define read-write computed type properties with the same syntax for computed instance properties
-
-
Querying and Setting Type Properties
println(SomeClass.computedTypeProperty) // prints "42" println(SomeStructure.storedTypeProperty) // prints "Some value." SomeStructure.storedTypeProperty = "Another value.
- Setting type property
Methods
Overview
- Functions associated with a particular type
- Classes, structures and enumerations can define instance and type methods.
- Major difference from C and Obj-C. In Obj-C, only classes can define methods.
Instance Methods
class Counter {
var count = 0
func increment() {
count++
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
-
Local and External Parameter Names for Methods
- Function params can have both local and external name, same wit method parameters
- Methods are just functions associated with a type
- But the default behaviour is different for functions and methods
- Methods in Swift similar to Obj-C, name of a method refers to the method's first parameter using a preposition such as "width", "for", "by", e.g. incrementBy(amount:, numberOfTimes). Easy to read as a sentence
- Swift makes this naming convention easy to write by using a different default approach for method parameters than it uses for functions
-
It gives the first parameter name in a method a local parameter name by default
-
And gives the second and subsequent parameter names both local and external parameter names by default
class Counter { var count: Int = 0 func incrementBy(amount: Int, numberOfTimes: Int) { count += amount * numberOfTimes } }
-
You can call the method as follows:
let counter = Counter() counter.incrementBy(5, numberOftimes: 3)
-
Default behaviour as if
func incrementBy(amount: Int, #numberOfTimes: Int) { count += amount * numberOfTimes }
-
- Function params can have both local and external name, same wit method parameters
-
Modifying External Parameter Name Behaviour for Methods
- Use # or add external parameter name for the first param
- To disable second param onwards, add _
-
The self Property
- self refers to current instance
- You don't usually need to declare
- Unless when a parameter name has the same name as the property of that instance
-
Modifying Value Types from Within Instance Methods
-
Structures and enumeration are value types, which properties of a value type cannot be modified from within its instance methods.
-
You can opt-in to mutating behaviour for that method, the method can also assign a completely new instance to its implicit self property
-
Use "mutating" keyword before the "func" for the method:
struct Point { var x = 0.0, y = 0.0 mutating func moveByX(deltaX: Double, y deltaY: Double) { x += deltaX y += deltaY } } var somePoint = Point(x: 1.0, y: 1.0) somePoint.moveByX(2.0, y: 3.0)
-
Note that you cannot call a mutating method on a constant structure
let fixedPoint = Point(x: 3.0, y: 3.0) fixedPoint.moveByX(2.0, y: 3.0) // this will report an error
-
-
Assigning to self Within a Mutating Method
struct Point { var x = 0.0, y = 0.0 mutating func moveByX(deltaX: Double, y deltaY: Double) { self = Point(x: x + deltaX, y: y + deltaY) } }
-
Mutating methods for enumerations can set the self to a different member of the same enumeration
enum TriStateSwitch { case Off, Low, High mutating func next() { switch self { case Off: self = Low case Low: self = High case High: self = Off } } } var ovenLight = TriStateSwitch.Low ovenLight.next() // ovenLight is now equal to .High ovenLight.next() // ovenLight is now equal to .Off"
-
Type Methods
-
You indicate type methods for classes by writing the keyword "class" before the method's fund keyword, and type methods for structures and enumerations by writing the keyword "static" before the method's fund keyword
-
In Obj-C type methods only for classes, in Swift for all classes, structures, and enumerations.
class SomeClass { class func someTypeMethod() { // type method implementation goes here } } SomeClass.someTypeMethod()
-
self refers to type itself
-
Accessing type methods from the same scope, doesn't require a self:
struct LevelTracker { static var highestUnlockedLevel = 1 static func unlockLevel(level: Int) { if level > highestUnlockedLevel { highestUnlockedLevel = level } } static func levelIsUnlocked(level: Int) -> Bool { return level <= highestUnlockedLevel } var currentLevel = 1 mutating func advanceToLevel(level: Int) -> Bool { if LevelTracker.levelIsUnlocked(level) { currentLevel = level return true } else { return false } } }
Subscripts
Overview
- Classes, structures, enumerations can define subscripts
- Shortcuts for accessing the member elements of a collection, list or sequence
- Use subscripts to set/get values by index without the need for separate methods
- E.g. someArray[index], someDictionary[key]
- You can define multiple subscripts for a single type
- Appropriate subscript overload is based on the type of index value passed to the subscript
- Not limited to a single dimensions
- You can define multiple input params to suit your custom type's needs
Subscript Syntax
-
Similar to instance methods, but can be read-write or read-only
subscript(index: Int) -> Int { get { // return an appropriate subscript value here } set(newValue) { // perform a suitable setting action here } }
-
As with read-only computed properties, you can drop the get keyword:
subscript(index: Int) -> Int { // return an appropriate subscript value here } struct TimesTable { let multiplier: Int subscript(index: Int) -> Int { return multiplier * index } } let threeTimesTable = TimesTable(multiplier: 3) println("six times three is \(threeTimesTable[6])") // prints "six times three is 18
-
Subscript Usage
-
Exact meaning of "subscript" depends on the context which it is used
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] numberOfLegs["bird"] = 2
-
Dictionary type implements its key-value subscripting, takes and receives an optional type
numberofLegs["bird"] returns a value of type Int?
-
Subscript Options
-
Can take any number of input parameters, and can be of any type
- Subscripts can also return any type
- Can you variable parameters, and variadic parameters
- No in-out params or default parameter values
-
There could be more than one subscript implementations, which subscript to used will be inferred based on the type s of the value or values that are contained in subscript braces, i.e. subscript overloading
-
Support multiple parameters
struct Matrix { let rows: Int, columns: Int var grid: [Double] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns grid = Array(count: rows * columns, repeatedValue: 0.0) } func indexIsValidForRow(row: Int, column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } subscript(row: Int, column: Int) -> Double { get { assert(indexIsValidForRow(row, column: column), "Index out of range") return grid[(row * columns) + column] } set { assert(indexIsValidForRow(row, column: column), "Index out of range") grid[(row * columns) + column] = newValue } } }
Inheritance
Overview
- Classes can also add property observers to inherited properties
- Property observers can be added to any property, stored or computed
Defining a base class
-
Any class that does not inherit from another class is known s base class
-
Swift classes do not inherit from a universal base class
-
Classes you define without a superclass, are base classes.
class Vehicle { var currentSpeed = 0.0 var description: String { return "traveling at \(currentSpeed) miles per hour" } func makeNoise() { // do nothing - an arbitrary vehicle doesn't necessarily make a noise } } let someVehicle = Vehicle() someVehicle.description
Subclassing
-
Extending a new class based on an existing class
class SomeSubclass: SomeSuperClass { ...}
Overriding
-
Use override keyword to clearly show the intent or error
- Compiler will also check if you are overriding any of the superclass methods
-
Accessing superclass Methods, Properties, and Subscripts
- super.someMethod, super.someProperty, super[someIndex]
-
Overriding methods
class Train: Vehicle { override func makeNoise() { println("Choo Choo") } }
-
Overring properties
- To provide your own implementation or to add property observer
-
Overriding property Getters and Setters
-
You can present an inherited read-only property as read-write property
-
You can not present an inherited read-write property as a read only
-
Note
- If you provide a setter, you need to provide the getter as well
- If you don't want to modify the inherited property's value within the overriding getter, just return the super class value, i.e. super.someProperty
class Car: Vehicle { var gear = 1 override var description: String { return super.description + " in gear \(gear)" } }
-
-
Overriding property Observers
-
Override the property to add the observers
-
Note
- You can not add property observers to inherited constant stored properties or inherited read-only computed properties, because these properties cannot be set
- You can't override both setter and property observer for the same property
- If you already provide custom setter, observe any value changes from within the custom setter instead
-
Example:
class AutomaticCar: Car { override var currentSpeed: Double { didSet { gear = Int(currentSpeed / 10.0) + 1 } } }
-
Preventing Overrides
- Mark it as "final", e.g. final var, final fund, final class fund, final subscript
Initialization
Overview
- Unlike Obj-C inits, Swift inits do not return value
- Instance of class types can also implement a deinitializer
Setting Initial Values for Stored Properties
-
Classes and structures must set all their stored properties to an init value by the time an instance is created
-
You can store initial value of a stored property in an initialiser, or by assigning default value
-
Note
- When you assign the default value to a stored property, or sets the initial value within the initializers, the property is set directly without calling property observers.
-
Initializers
init() { // perform some initialization here } struct Fahrenheit { var temperature: Double init() { temperature = 32.0 } }
Customizing Initialization
-
Initialization Parameters
struct Celsius { var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } init(fromKelvin kelvin: Double) { temperatureInCelsius = kelvin - 273.15 } } let boilingPointOfWater = Celsius(fromFahrenheit: 212.0) // boilingPointOfWater.temperatureInCelsius is 100.0 let freezingPointOfWater = Celsius(fromKelvin: 273.15) // freezingPointOfWater.temperatureInCelsius is 0.0"
-
Local & External Parameter Names
-
Initializers do not have an identifying function name, thus the names and types of an init parameters play an important role identifying which init to call.
-
Swift provide an automatic external name to be the same with the local name
struct Color { let red, green, blue: Double init(red: Double, green: Double, blue: Double) { self.red = red self.green = green self.blue = blue } init(white: Double) { red = white green = white blue = white } } let magenta = Color(red: 1.0, green: 0.0, blue: 1.0) let halfGray = Color(white: 0.5) // without external name will trigger an error
-
-
Initializer Parameters Without External Names
// Use "_" as the external name init(_ celsius: Double) { temperatureInCelsius = celsius } Celcius(37.0)
-
Optional Property Types
-
Optional property types are automatically initialised with a value of nil
class SurveyQuestion { var text: String var response: String? init(text: String) { self.text = text } func ask() { println(text) } } let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?") cheeseQuestion.ask() cheeseQuestion.response = "Yes, I do like cheese."
-
-
Modifying Constant Properties during Initialization
- You can modify the value of a constant property during initialisation
- Can only be modified in class init, not sub-class init.
Default Initializers
-
If no init is provided, Swift provide default init, optional parameters will be set to nil, example:
class ShoppingListItem { var name: String? var quantity = 1 var purchased = false } var item = ShoppingListItem()
-
Memberwise Initialisers for Structure Types
-
Structure types automatically receive a member wise initialiser if there is no custom initialisers
-
Memberwise initializer is a shorthand way to initialise the member properties of new structure instances
-
Initial values can be passed to the init by name
struct Size { var width = 0.0, height = 0.0 } let twoByTwo = Size(width: 2.0, height: 2.0)
-
Initializer Delegation for Value Types
-
The rules are different for value types and class types.
- Value types (structures and enumerations) do not support inheritance, so their initialiser delegation process is relatively simple, they can only delegate to another initialiser, class has superclass.
-
self.init can only be called within an initialiser
-
If custom initialiser is defined, you won't have access to the default initialiser
struct Size { var width = 0.0, height = 0.0 } struct Point { var x = 0.0, y = 0.0 } struct Rect { var origin = Point() var size = Size() init() {} init(origin: Point, size: Size) { self.origin = origin self.size = size } init(center: Point, size: Size) { let originX = center.x - (size.width / 2) let originY = center.y - (size.height / 2) self.init(origin: Point(x: originX, y: originY), size: size) } } let basicRect = Rect() // basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0) let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0)) // originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0) ```
- Note
- For an alternative way to write this example without defining the init() and init(origin:size:) initializers yourself, see Extensions.
Class Inheritance and Initialization
-
All of class's stored properties must be assigned to an initial value during initialisation
-
Designated Initializers & Convenience Initializers
- Designated inits are primary inits for a class
- Init all properties in that class and calls its superclass chain inits
- Quite common to have only one
- Convenience inits, secondary support inits
- Designated inits are primary inits for a class
-
Syntax for Designated and Convenienc initialisers
- init ([parameters]) { ... }
- convenience init([parameters]) { ... }
-
Init Chaining
- Rules to simply relationship between designated & convenience inits
- Designated init must call a designated init from its immediate class
- Convenience init must call another init from the same class
- Convenience init must ultimately call a designated init
- Simple way to remember:
- Designated init delegates up
- Convenience init delegates across
- More complex example, designated inits act as a funnel:
- Rules to simply relationship between designated & convenience inits
-
Two-Phase Initialization
- Two phases:
- Phase 1
- Each stored property is assigned an initial value
- Phase 2
- Each class is given the opportunity to customise its stored properties further before new instance is ready to use
- Phase 1
- Two-phase init prevents property values being accessed before they are initialised, and prevent property values being set to a different value by another initialiser unexpectedly
- Similar to Obj-C, the main difference during Phase 1, Objc-C assigns zero or null values (0 or nil) to every property.
- Swift's init is more flexible, lets you set custom initial values, and can cope with types for which 0 or nil is not a valid default value
- Compiler performs four helpful safety checks:
- Safety Check 1
- A designated init must sensor that all the properties introduced by its class are initialised before it delegates up to a superclass init
- Safety Check 2
- A designated init must delegate up to a superclass init before assigning a value to an inherited property
- If it does't the new value the designated init assigns will be overwritten by the superclass
- Safety Check 3
- A convenience init must delegate to another init before assigning a value to any property.
- If it doesn't the new value the convenience init assigns will be overwritten by its own class designated init
- Safety Check 4
- An init cannot call any instances methods, read the values of any instance properties, or refer to self as a value until the first phase of init is complete
- Safety Check 1
- Two phases:
-
Phase 1
- A designated or convenience init is called
- Memory for a new instance of that class is allocated, the memory is not initialised yet
- A designated init for that class confirms that all stored properties introduced by that class have a value, the memory for those stored properties is now initialised
- The designated initialiser hands off to a superclass init to perform the same task for its own stored properties
- This continues up the class inheritance chain until top chain is reached
- The final class in the chain has ensured that all its stored properties have a value, instance's memory is considered fully initialised, phase 1 is complete
-
Phase 2
- Working back down from the top of the chain, each designated initialiser in the chain has the option customise the instance further.
- Inits are now able to access self and can modify its properties, call its instance methods and so on
- Finally any convenience inits in the chain have the option to customise the instance to work with self.
- Working back down from the top of the chain, each designated initialiser in the chain has the option customise the instance further.
-
Init Inheritance and Overriding
-
Unlike subclass in Obj-C, Swift subclass do not inherit their superclass init by default
-
Note
- Superclass inits are inherited in certain circumstances, only when it is safe and appropriate, more info in the next section
-
If you want to the same init from the superclass, override the init with "override modifier.
-
If you write subclass init that matches superclass convenience init, superclass convenience init can never be called directly by your subclass, as described in Init Chaining
- Therefore your subclass is not providing an override of the superclass init
- Thus you do not write the override modifier when providing a matching implementation of a superclass convenience initialiser
class Vehicle { var numberOfWheels = 0 var description: String { return "\(numberOfWheels) wheel(s)" } } class Bicycle: Vehicle { override init() { super.init() numberOfWheels = 2 } }
-
Note
- Subclasses are only allowed to modify variable superclass properties during initialisation. Subclasses cannot modify inherited constant properties.
-
-
Automatic Init Inheritance
- Subclass do not inherit their superclass inits by default, unless certain conditions are met
- In practice, you do not need to write init overrides in many common scenarios, and can inherit superclass inits with minimal efforts
- Assuming you provide default values for any new properties you introduce in a subclass, the following two rules apply:
- Rule 1
- If your subclass does not define any designated inits, it automatically inherits all its superclass initializers
- Rule 2
- If your subclass provides an implementation of all its superclass designated initialisers - either by inheriting them as per rule 1 or by custom implementation, it automatically inherits all of the superclass convenience inits
- Rule 1
- Note
- A subclass can implement a superclass designated init as subclass convenience init as part of satisfying rule 2
-
Designated and Convenience Inits in Action
class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") } }
class RecipeIngredient: Food { var quantity: Int init(name: String, quantity: Int) { self.quantity = quantity super.init(name: name) } override convenience init(name: String) { self.init(name: name, quantity: 1) } } let oneMysteryItem = RecipeIngredient() let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
class ShoppingListItem: RecipeIngredient { var purchased = false var description: String { var output = "\(quantity) x \(name)" output += purchased ? " ✔" : " ✘" return output } } var breakfastList = [ ShoppingListItem(), ShoppingListItem(name: "Bacon"), ShoppingListItem(name: "Eggs", quantity: 6), ]
-
Required Inits
-
To indicate every subclass must implement that initialiser:
class SomeClass { required init() { // initializer implementation goes here } }
-
You do not write the override modifier when overriding:
class SomeSubclass: SomeClass { required init() { // subclass implementation of the required initializer goes here } }
-
You do not have to provide an explicit implementation if the init can be inherited.
-
Setting a Default Property Value with a Closure or Function
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
- Note
-
If you use closure to init a property, remember the rest of the instance has not been initialised yet
-
You cannot access any other properties, and you cannot use "self"
struct Checkerboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false for i in 1...10 { for j in 1...10 { temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard }() func squareIsBlackAtRow(row: Int, column: Int) -> Bool { return boardColors[(row * 10) + column] } }
-
Deinitialization
Overview
- Called immediately before a class instance is deallocated
- deinit keyword, only for class types
How Deinitialization works
- Swift uses ARC to manage memory for the instance, typically you won't need to perform manual cleanup
- When you are working with your own resources, you probably would, ex.g. create a custom class to open a file/write some data to it, you need to close the file before the class instance is deallocated
- You are not allowed to call a reinit yourself
- Superclass deinits are inherited by their subclasses, and called automatically at the end of a subclass deinit. Always called even if subclass does not implement a reinit.
Deinitializers in Action
struct Bank {
static var coinsInBank = 10_000
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receiveCoins(coins: Int) {
coinsInBank += coins
}
}
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
}
}
var playerOne: Player? = Player(coins: 100)
println("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// prints "A new player has joined the game with 100 coins"
println("There are now \(Bank.coinsInBank) coins left in the bank")
// prints "There are now 9900 coins left in the bank
playerOne!.winCoins(2_000)
println("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// prints "PlayerOne won 2000 coins & now has 2100 coins"
println("The bank now only has \(Bank.coinsInBank) coins left")
// prints "The bank now only has 7900 coins left
playerOne = nil
println("PlayerOne has left the game")
// prints "PlayerOne has left the game"
println("The bank now has \(Bank.coinsInBank) coins")
// prints "The bank now has 10000 coins
Automatic Reference Counting
Overview
- To track and manage your app's memory usage
- Memory management just works, in a few cases ARC requires more info about the relationship between parts of your code.
- Reference counting only applies to classes, Structures and Enumerations are value types
How ARC Works
- To make sure that instances don't disappear when they are still needed, when you assign a class instance to a property, constant or variable, it makes a strong reference. It does not allow it to be deallocated, as long as strong reference remains.
ARC In Action
class Person {
let name: String
init(name: String) {
[self.name][2] = name
println("\(name) is being initialized")
}
deinit {
println("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed")
// prints "John Appleseed is being initialised"
reference2 = reference1
reference3 = reference1
reference1 = nil
reference2 = nil
reference3 = nil
// prints "John Appleseed is being deinitialized
Strong Reference Cycles Between Class Instances
-
Two class instances holding a strong reference to each other
-
You resolve strong reference cycles by defining some of the relationships between classes as weak or unowned references instead of as strong references.
class Person { let name: String init(name: String) { [self.name][2] = name } var apartment: Apartment? deinit { println("\(name) is being deinitialized") } } class Apartment { let number: Int init(number: Int) { self.number = number } var tenant: Person? deinit { println("Apartment #\(number) is being deinitialized") } } var john: Person? var number73: Apartment? john = Person(name: "John Appleseed") number73 = Apartment(number: 73)
-
john!.apartment = number73 number73!.tenant = john
-
john = nil number73 = nil // reference count does not frop to 0
Resolving Strong Reference Cycles Between Class Instances
- Two ways:
- weak references
- Use weak reference whenever it is valid for the reference to become nil at some point during its lifetime
- unowned references
- When you know that the reference will never be nil once it has been set during initialization
- weak references
- Weak References
-
Enable one instance in the reference cycle to refer to the other instance without keeping a strong hold on it
-
Use keyword "weak"
-
Optional, constant not allowed
-
ARC will set to nil when it is deallocated
class Person { let name: String init(name: String) { [self.name][2] = name } var apartment: Apartment? deinit { println("\(name) is being deinitialized") } } class Apartment { let number: Int init(number: Int) { self.number = number } weak var tenant: Person? deinit { println("Apartment #\(number) is being deinitialized") } }
-
- Unowned References
-
Like weak reference, but it is assumed to always have a value
-
Non-optional type
-
Use "unowned" keyword
-
ARC cannot set the reference to nil when it is deallocated
-
Note
-
If you try to access an unowned reference after the instance that it references is deallocated, will trigger a runtime error
-
Use unowned references only when you are sure that the reference refers to an instance
class Customer { let name: String var card: CreditCard? init(name: String) { [self.name][2] = name } deinit { println("\(name) is being deinitialized") } } class CreditCard { let number: UInt64 unowned let customer: Customer init(number: UInt64, customer: Customer) { self.number = number self.customer = customer } deinit { println("Card #\(number) is being deinitialized") } }
-
-
Note
-
The number property of the CreditCard class is defined with a type of UInt64 rather than Int, to ensure that the number property's capacity is large enough to store a 16-digit card number on both 32-bit and 64-bit systems.
var john: Customer? john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
-
john = nil // prints "John Appleseed is being deinitialized" // prints "Card #1234567890123456 is being reinitialised"
-
-
- Unowned References and Implicitly Unwrapped Optional Properties
-
Third scenario which both properties should have a value
-
Combine unowned property on one class with an implicit unwrapped optional property on the other class
-
Enables both properties to be accessed directly without optional unwrapping once init is complete, while avoiding reference cycle
class Country { let name: String let capitalCity: City! init(name: String, capitalName: String) { [self.name][2] = name self.capitalCity = City(name: capitalName, country: self) } } class City { let name: String unowned let country: Country init(name: String, country: Country) { [self.name][2] = name self.country = country } } var country = Country(name: "Canada", capitalName: "Ottawa") println("\(country.name)'s capital city is called \(country.[capitalCity.name][3])") // prints "Canada's capital city is called Ottawa"
-
Strong Reference Cycles for Closures
-
Can occur if you assign a closure to a property of a class instance, and the body of that closure captures that instance, such as
- self.someProperty or
- self.someMethod()
- Captures self creating a strong reference cycle
-
Closure are reference types, when you assign to a property, you are assigning a reference to that closure
- Class instance and a closure keeping each other alive
-
Solution to use a "closure capture list"
class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { if let text = self.text { return "<\(self.name)>\(text)</\(self.name)>" } else { return "<\(self.name) />" } } init(name: String, text: String? = nil) { [self.name][2] = name self.text = text } deinit { println("\(name) is being deinitialized") } } var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") println(paragraph!.asHTML()) // prints "<p>hello, world</p>
Resolving Strong Reference Cycles for Closures
-
Define a capture list as part of the closure's definition
-
Capture list defines the rules to use when capturing one or more reference types within the closure's body
-
You can declare captured reference to be a weak or unknown reference
-
Note
- Write self.someProperty or self.someMethod, instead of removing the self to help you remember that it's possible to capture self by accident
-
Defining Capture List
lazy var someClosure: (Int, String) -> String = { [unowned self] (index: Int, stringToProcess: String) -> String in // closure body goes here }
- Or
lazy var someClosure: () -> String = { [unowned self] in // closure body goes here }
-
Weak and Unknowned References
- If captured reference will never become nil, it should be captured as an unowned reference, rather than a weak reference
Optional Chaining
Overview
- Process of querying and calling properties, methods, and subscripts on an optional that might currently be nil
- If the called entity is nil, it will return nil if not the value
- Multiple queries can be chained together, and fail gracefully if any of the link chain is nil
- Note
- Optional chaining in Swift similar to messaging nil in Obj-C, but that works for any type, and that can be checked for success or failure.
Optional Chaining as an Alternative to Forced Unwrapping
-
Place a question mark (?) after the optional value to chain
class Person { var residence: Residence? } class Residence { var numberOfRooms = 1 } let john = Person() let roomCount = john.residence!.numberOfRooms // this trigger a runtime error
-
Use optional chaining:
if let roomCount = john.residence?.numberOfRooms { println("John's residence has \(roomCount) room(s).") } else { println("Unable to retrieve the number of rooms.") }
Defining Model Classes for Optional Chaining
-
Optional chaining can be more than on level
-
Model classes
class Person { var residence: Residence? } class Residence { var rooms = [Room]() var numberOfRooms: Int { return rooms.count } subscript(i: Int) -> Room { get { return rooms[i] } set { rooms[i] = newValue } } func printNumberOfRooms() { println("The number of rooms is \(numberOfRooms)") } var address: Address? } class Room { let name: String init(name: String) { [self.name][2] = name } } class Address { var buildingName: String? var buildingNumber: String? var street: String? func buildingIdentifier() -> String? { if buildingName != nil { return buildingName } else if buildingNumber != nil { return buildingNumber } else { return nil } } }
Accessing Properties Through Optional Chaining
-
Use optional chaining to access property on optional value or to check if property access is successful
let john = Person() if let roomCount = john.residence?.numberOfRooms { println("John's residence has \(roomCount) room(s).") } else { println("Unable to retrieve the number of rooms.") }
Calling Methods Through Optional Chaining
-
Use optional chaining to call a method on an optional value or to check whether a method call is successful, even if the method does not define a return value
* Residence#printNumberOfRooms func printNumberOfRooms() { println("The number of rooms is \(numberOfRooms)") }
-
Method has no return tip, bbut have an implicit value of Void
- Means they return a value of () or an empty tubule.
-
If you call this on an optional value with optional chaining, the return type will be Void/ not Void, because the return values are always of an optional type when called with optional chaining
if john.residence?.printNumberOfRooms() != nil { println("It was possible to print the number of rooms.") } else { println("It was not possible to print the number of rooms.") } // prints "It was not possible to print the number of rooms.
Accessing SubscriptsThrough Optional Chaining
-
Overview
-
To retrieve and set a value of a subscript on an optional value, and to check if the subscript call is successful
-
Note
- Place the question mark before the subscript's braces, not after
if let firstRoomName = john.residence?[0].name " { ... }
-
-
Accessing Subscripts of Optional Type
-
If a subscript returns a value of optional type, such as the key subscript of Swift's Dictionary type, place question mark after the subscript's closing bracket to chain on its optional return value.
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] testScores["Dave"]?[0] = 91 testScores["Bev"]?[0]++ testScores["Brian"]?[0] = 72 // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
-
Linking Multiple Levels of Chaining
- You can link multiple levels of optional chaining
-
If the type you are trying to retrieve is not optional, it will become optional because of optional chaining
-
If the type you are trying to retrieve is already optional, it will not become more optional because of the chaining
-
Thus if you retrieve an Int through optional chaining, and Int? is always returned
if let johnsStreet = john.residence?.address?.street { println("John's street name is \(johnsStreet).") } else { println("Unable to retrieve the address.") } // prints "Unable to retrieve the address. let johnsAddress = Address() johnsAddress.buildingName = "The Larches" johnsAddress.street = "Laurel Street" john.residence!.address = johnsAddress if let johnsStreet = john.residence?.address?.street { println("John's street name is \(johnsStreet).") } else { println("Unable to retrieve the address.") } // prints "John's street name is Laurel Street.
-
Chaining on Methods with Optional Return Values
-
Use optional chaining to call a method that returns a value of optional type, and to chain the method's return value if needed
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { println("John's building identifier is \(buildingIdentifier).") } // prints "John's building identifier is The Larches." if let beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") { if beginsWithThe { println("John's building identifier begins with \"The\".") } else { println("John's building identifier does not begin with \"The\".") } } // prints "John's building identifier begins with "The"."
Type Casting
Overview
- A way to check the type of an instance or
- To treat that instance as if it is a different superclass/subclass from somewhere else in its own hierarchy
- Implemented with "is" and "as" operators.
- To check the type of a value or cast a value to a different type
- Also used to check whether a type conforms to a protocol
Defining a Class Hierarchy for Type Casting
class MediaItem {
var name: String
init(name: String) {
[self.name][2] = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
let library = [
Movie(name: "Casablanca", director: "Michael Curtiz"),
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Welles"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
Checking Type
-
Use type check operator (is) to check whether an instance is a certain subclass type
var movieCount = 0 var songCount = 0 for item in library { if item is Movie { ++movieCount } else if item is Song { ++songCount } }
Downcasting
-
Downcast a subclass type with type operator (as)
- Downcasting can fail, there are two types:
- as? returns optional value
- as, attempts to downcast and force-unwraps result as a single compound action
for item in library { if let movie = item as? Movie { println("Movie: '\(movie.name)', dir. \(movie.director)") } else if let song = item as? Song { println("Song: '\(song.name)', by \(song.artist)") } }
- Downcasting can fail, there are two types:
Type Casting for Any and AnyObject
- Swift provide two special type aliases for working with non-specific types
- AnyObject: an instance of any class type
- Any: an instance of any type at all apart from function types
- Note
- Use Any and AnyObject when you explicitly need the behaviour and capabilities.
- It is always better to be specific with the types you work with.
- AnyObject
-
When you work with CocoaAPI, it is common to receive an array of [AnyObject]
-
Because Obj-C does not have an Array of explicitly typed objects
-
Use as to downcast each item in the array
let someObjects: [AnyObject] = [ Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"), Movie(name: "Moon", director: "Duncan Jones"), Movie(name: "Alien", director: "Ridley Scott") ] for object in someObjects { let movie = object as Movie println("Movie: '\(movie.name)', dir. \(movie.director)") }
- Shorter version
for movie in someObjects as [Movie] { println("Movie: '\(movie.name)', dir. \(movie.director)") }
-
- Any
-
To work with a mix of different types including non-class types
var things = [Any]() things.append(0) things.append(0.0) things.append(42) things.append(3.14159) things.append("hello") things.append((3.0, 5.0)) things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
-
Contains Int, Double, String, (Double, Double), Movie
for thing in things { switch thing { case 0 as Int: println("zero as an Int") case 0 as Double: println("zero as a Double") case let someInt as Int: println("an integer value of \(someInt)") case let someDouble as Double where someDouble > 0: println("a positive double value of \(someDouble)") case is Double: println("some other double value that I don't want to print") case let someString as String: println("a string value of \"\(someString)\"") case let (x, y) as (Double, Double): println("an (x, y) point at \(x), \(y)") case let movie as Movie: println("a movie called '\(movie.name)', dir. \(movie.director)") default: println("something else") } }
-
Note
- The case of a switch statement, use the forced version of type cast operator, as.
-
Nested Types
Overview
- Enums often created to support a specific class or structure's functionality
- It can be convenient to define utility classes and structures purely for use within the context of a more complex type
- Define nested types:
- Nest supporting enumerations, classes, structures within the definition of the type they support
Nested Types in Action
struct BlackjackCard {
// nested Suit enumeration
enum Suit: Character {
case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣"
}
// nested Rank enumeration
enum Rank: Int {
case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King, Ace
struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .Ace:
return Values(first: 1, second: 11)
case .Jack, .Queen, .King:
return Values(first: 10, second: nil)
default:
return Values(first: self.toRaw(), second: nil)
}
}
}
// BlackjackCard properties and methods
let rank: Rank, suit: Suit
var description: String {
var output = "suit is \(suit.toRaw()),"
output += " value is \(rank.values.first)"
if let second = rank.values.second {
output += " or \(second)"
}
return output
}
}
let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades)
println("theAceOfSpades: \(theAceOfSpades.description)")
// prints "theAceOfSpades: suit is ♠, value is 1 or 11
Referring to Nested Types
let heartsSymbol = BlackjackCard.Suit.Hearts.toRaw()
// heartsSymbol is "♡"
Extensions
Overview
- Add new functionality to existing class, structure or enumeration type
- Extend types for which you do not have access to the original source code (retroactive modelling)
- Extension similar to categories in Obj-C, except it does not have names.
- Extensions in Swift can:
- Add computed properties and computed static properties
- Define instance and type methods
- Provide new inits
- Define subscripts
- Define and use new nested types
- Make an existing type conform to a protocol
- Note
- Extensions can add new functionality, but can not override existing functionality
Extension Syntax
extension SomeType {
// new functionality to add to SomeType goes here
}
// Extend with protocols
extension SomeType: SomeProtocol, AnotherProtocol {
// implementation of protocol requirements goes here
}
- If you define an extension, the new functionality will be available to all instances, even if they were created before the extension was defined.
Computed Properties
-
Can add computed properties and computed type properties to existing types:
extension Double { var km: Double { return self * 1_000.0 } var m: Double { return self } var cm: Double { return self / 100.0 } var mm: Double { return self / 1_000.0 } var ft: Double { return self / 3.28084 } } let oneInch = [25.4.mm][4] println("One inch is \(oneInch) meters") // prints "One inch is 0.0254 meters" let threeFeet = 3.ft println("Three feet is \(threeFeet) meters") // prints "Three feet is 0.914399970739201 meters let aMarathon = 42.km + 195.m
-
Extensions add new computed properties but they cannot add stored properties, add property observers to existing properties
Initializers
-
Extensions can add new convenience initialisers to a class, but they cannot add new designated initialisers or deinitializers to a class
-
Designated initialisers and deinitializer must always be provided by original class implementation
struct Size { var width = 0.0, height = 0.0 } struct Point { var x = 0.0, y = 0.0 } struct Rect { var origin = Point() var size = Size() } let defaultRect = Rect() let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0)) extension Rect { init(center: Point, size: Size) { let originX = center.x - (size.width / 2) let originY = center.y - (size.height / 2) self.init(origin: Point(x: originX, y: originY), size: size) } } let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
Methods
-
Add new instance methods and type methods to existing types
extension Int { func repetitions(task: () -> ()) { for i in 0..<self { task() } } } 3.repetitions({ println("Hello!") })
-
Mutating Instance Methods
-
Instance methods added with extension can also modify (mutate the instance itself
extension Int { mutating func square() { self = self * self } }
-
Subscripts
extension Int {
subscript(var digitIndex: Int) -> Int {
var decimalBase = 1
while digitIndex > 0 {
decimalBase *= 10
--digitIndex
}
return (self / decimalBase) % 10
}
}
746381295[0] // returns 5
746381295[1] // returns 9
746381295[2] // returns 2
746381295[8] // returns 7
746381295[9] // returns 0, as if you had requested:
0746381295[9]
Nested Types
extension Int {
enum Kind {
case Negative, Zero, Positive
}
var kind: Kind {
switch self {
case 0:
return .Zero
case let x where x > 0:
return .Positive
default:
return .Negative
}
}
}
func printIntegerKinds(numbers: [Int]) {
for number in numbers {
switch number.kind {
case .Negative:
print("- ")
case .Zero:
print("0 ")
case .Positive:
print("+ ")
}
}
print("\n")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7]) // prints "+ + - 0 - 0 +"
Protocols
Overiew
- Defines a blueprint of methods, properties, and other quirements to suit a particular task or piece of functionality.
- Does not provide an implementation, only describes what an implementation will look like
- Can be adopted by a class, structure or enumeration
- Any type that satisfies the requirements conform to that protocol
Protocol Syntax
protocol SomeProtocol {
// protocol definition goes here
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
Protocol Requirements
-
Getter & setter requirements
protocol SomeProtocol { var mustBeSettable: Int { get set } var doesNotNeedToBeSettable: Int { get } }
-
Type property protocol, use "static" keyword for structure or enumeration
protocol AnotherProtocol { class var someTypeProperty: Int { get set } }
-
Protocol with a single instance property requirements:
protocol FullyNamed { var fullName: String { get } }
-
Simple structure conforms to FullyNamed protocol
struct Person: FullyNamed { var fullName: String } let john = Person(fullName: "John Appleseed") // john.fullName is "John Appleseed"
-
Class conforms to the protocol:
class Starship: FullyNamed { var prefix: String? var name: String init(name: String, prefix: String? = nil) { [self.name][2] = name self.prefix = prefix } var fullName: String { return (prefix != nil ? prefix! + " " : "") + name } } var ncc1701 = Starship(name: "Enterprise", prefix: "USS") // ncc1701.fullName is "USS Enterprise"
-
Method Requirements
-
Protocol can require specific instance methods or type methods to be implemented
-
Use the same syntax as normal methods, but are not allowed to specify default values for method params.
protocol SomeProtocol { class func someTypeMethod() } protocol RandomNumberGenerator { func random() -> Double }
Mutating Method Requirements
-
If you mark a protocol instance method requirements as mutating, you do not need to write he mutating keyword when writing an implementation of that method for a class
-
Mutating only used by structures and enumerations
protocol Togglable { mutating func toggle() } enum OnOffSwitch: Togglable { case Off, On mutating func toggle() { switch self { case Off: self = On case On: self = Off } } } var lightSwitch = OnOffSwitch.Off lightSwitch.toggle() // lightSwitch is now equal to .On
Initializer Requirements
-
Same way as normal inits, but without curly braces or an init body:
protocol SomeProtocol { init(someParameter: Int) }
-
Class implementations of Protocol initialisers requirements
-
You can implement a protocol initialiser requirement on a conforming class as either a designated initialiser requirement or a convenience initializer.
-
In both cases you must mark the initialiser implementation with the "required" modifier:
class SomeClass: SomeProtocol { required init(someParameter: Int) { // initializer implementation goes here } }
-
Required modifier ensures that you provide explicit or inherited implementation of the initialiser requirement on all subclasses.
-
Note
- You do not need to mark protocol init with required modifier on classes marked with the final modifier, because final classes can not be subclassed.
-
-
Subclass implementation, note the override:
protocol SomeProtocol { init() } class SomeSuperClass { init() { // initializer implementation goes here } } class SomeSubClass: SomeSuperClass, SomeProtocol { // "required" from SomeProtocol conformance; "override" from SomeSuperClass required override init() { // initializer implementation goes here } }
Protocols as Types
-
Protocol you create will become a fully-fledged type for use in your code
-
Thus, you can use protocol in many places, including:
- As a parameter type or return type in a function, method or init
- As a type of a constant, variable or property
- As a type of items in an array, dictionary or other container
class Dice { let sides: Int let generator: RandomNumberGenerator init(sides: Int, generator: RandomNumberGenerator) { self.sides = sides self.generator = generator } func roll() -> Int { return Int(generator.random() * Double(sides)) + 1 } } var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator()) for _ in 1...5 { println("Random dice roll is \(d6.roll())") }
Delegation
- A design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type
- Implemented by defining a protocol that encapsulates the delegated responsibilities
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(game: DiceGame)
}
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
var delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
}
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
println("Started a new game of Snakes and Ladders")
}
println("The game is using a \(game.dice.sides)-sided dice")
}
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
++numberOfTurns
println("Rolled a \(diceRoll)")
}
func gameDidEnd(game: DiceGame) {
println("The game lasted for \(numberOfTurns) turns")
}
}
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
Adding Protocol Conformance with an Extension
-
You can extend existing type to adopt and conform to a new protocol, even if you do not have access to the source code.
-
Extensions can add new properties, methods, and subscripts to an existing type, and are therefore able to add any requirements that a protocol demand.
protocol TextRepresentable { func asText() -> String } extension Dice: TextRepresentable { func asText() -> String { return "A \(sides)-sided dice" } }
-
Declaring Protocol Adoption with Extension
-
If a type already conforms to a protocol, but has not adopted that protocol you can make it adopt the protocol with an empty extension:
struct Hamster { var name: String func asText() -> String { return "A hamster named \(name)" } } extension Hamster: TextRepresentable {} let simonTheHamster = Hamster(name: "Simon") let somethingTextRepresentable: TextRepresentable = simonTheHamster"
-
Note
- Types do not automatically adopt a protocol by just satisfying its requirements, they must explicitly declare their adoption of the protocol
-
Collections of Protocol Types
-
Protocol can be used as the type to be stored in collection as as array/dictionary:
let things: [TextRepresentable] = [game, d12, simonTheHamster] for thing in things { println(thing.asText()) }
Protocol Inheritance
-
Protocol can inherit one or more other protocols and can add further requirements on top of the requirements it inherits:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol { // protocol definition goes here } protocol PrettyTextRepresentable: TextRepresentable { func asPrettyText() -> String }
Class-Only Protocol
-
Limit protocol adoption to class types (and not structures or enumerations) by adding the class keyword to a protocol's inheritance list:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { // class-only protocol definition goes here }
-
Note
- Use a class-only protocol when the behaviour defined that protocol's requirements assumes or requires that a conforming type has reference semantics rather than value semantics.
Protocol Composition
-
You can combine multiple protocols into a single protocol composition.
- protocol<SomeProtocol, AnotherProtocol>
protocol Named { var name: String { get } } protocol Aged { var age: Int { get } } struct Person: Named, Aged { var name: String var age: Int } func wishHappyBirthday(celebrator: protocol<Named, Aged>) { println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!") } let birthdayPerson = Person(name: "Malcolm", age: 21) wishHappyBirthday(birthdayPerson) // prints "Happy birthday Malcolm - you're 21!"
-
Note
- Protocol compositions do not define a new, permanent protocol type, rather they define a temporary local protocol that has the combined requirements of all protocols in the composition.
Checking for Protocol Conformance
-
You can use is and as operators
@objc protocol HasArea { var area: Double { get } }
-
Note
- You can check protocol conformance only if your protocol is marked with the @objc attribute
- It indicates that protocol should be exposed to Objective-C Code
- @objc protocols can be adopted only by classes, not by structures or enumerations
class Circle: HasArea { let pi = 3.1415927 var radius: Double var area: Double { return pi * radius * radius } init(radius: Double) { self.radius = radius } } class Country: HasArea { var area: Double init(area: Double) { self.area = area } } for object in objects { if let objectWithArea = object as? HasArea { println("Area is \(objectWithArea.area)") } else { println("Something that doesn't have an area") } }
Optional Protocol Requirements
-
These requirements do not have to be implemented by types that conform to the protocol
-
Prefixed by "optional"
-
Optional protocol requirement can be called with optional chaining
-
You can check implementation of an optional requirement by writing a question mark after the name of the requirement
- someOptionalMethod?(someArgument)
-
Optional property/method requirements will return an optional value
-
Note
- Optional protocol can only be specified if the protocol is marked with the @objc attribute
- Even if you are not interoperating with Obj-C you need to mark your protocols with @objc
- @objc can only be adopted by classes, not structures/enumerations
@objc protocol CounterDataSource { optional func incrementForCount(count: Int) -> Int optional var fixedIncrement: Int { get } } @objc class Counter { var count = 0 var dataSource: CounterDataSource? func increment() { if let amount = dataSource?.incrementForCount?(count) { count += amount } else if let amount = dataSource?.fixedIncrement? { count += amount } } } class ThreeSource: CounterDataSource { let fixedIncrement = 3 } var counter = Counter() counter.dataSource = ThreeSource() for _ in 1...4 { counter.increment() println(counter.count) } class TowardsZeroSource: CounterDataSource { func incrementForCount(count: Int) -> Int { if count == 0 { return 0 } else if count < 0 { return 1 } else { return -1 } } } counter.count = -4 counter.dataSource = TowardsZeroSource() for _ in 1...5 { counter.increment() println(counter.count) } // -3 // -2 // -1 // 0 // 0
Generics
Overview
- Enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define.
- Avoid duplication and expresses its intent in a clear, abstracted manner
- Most powerful feature of Swift, much of the Swift standard library is built with generic code.
- You have been using generics throughoutLanguage Guide
- Swift's Array and Dictionary types are both generic collections
- You can create an array of any types
The Problem That Generic Solve
-
Fixed Type Example
func swapTwoInts(inout a: Int, inout b: Int) { let temporaryA = a a = b b = temporaryA } var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) println("someInt is now \(someInt), and anotherInt is now \(anotherInt)") // prints "someInt is now 107, and anotherInt is now 3 func swapTwoStrings(inout a: String, inout b: String) { let temporaryA = a a = b b = temporaryA } func swapTwoDoubles(inout a: Double, inout b: Double) { let temporaryA = a a = b b = temporaryA }
-
In all 3 functions, it is important that the types a and b are defined to be the same as each other.
Generic Functions
func swapTwoValues<T>(inout a: T, inout b: T) {
let temporaryA = a
a = b
b = temporaryA
}
-
Comparison
func swapTwoInts(inout a: Int, inout b: Int) func swapTwoValues<T>(inout a: T, inout b: T) var someInt = 3 var anotherInt = 107 swapTwoValues(&someInt, &anotherInt) // someInt is now 107, and anotherInt is now 3 var someString = "hello" var anotherString = "world" swapTwoValues(&someString, &anotherString) // someString is now "world", and anotherString is now "hello"
- Note
- swapTwoValues inspired by generic function called swap, which is part of Swift standard library, is automatically made available for you to use in your apps
- Note
Type Parameters
- In the swapTwoValues, the placeholder T is an example of a type parameter.
- Type parameter specify and name a placeholder type, and are written immediately after the function's name, between a pair of matching angle bracket (such as <T>)
- Once you specify the type parameter, you can use it to define the type of a function's parameter, function's return type and or as a type annotation within the body of the function
- You can provide more than one type parameter by writing type parameter name within the angle bracket.
- Naming Type Parameters
- Traditional to use single-character name T
- But, you can use any valid identifier for the type parameter name
- Complex generic functions, or generic types with multiple parameters, useful to provide more descriptive parameter names.
- Example:
- Dictionary uses KeyType and ValueType
- Note
- Always give type parameters UpperCamelCase to indicate they are placeholder for a type not a value
Generic Types
-
Custom classes, structures, and enumerations that can work with any type, similar to Array and Dictionary
-
Example, create a Stack
- The concept of stack is used by UINavigationController.
- Call pushViewController:animated and popViewControllerAnimated:
- Last in, first out
-
Integer Stack:
struct InStack { var items = [Int]() mutating func push(item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }
-
Generic Stack:
struct Stack<T> { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } } var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres")
Extending a Generic Type
-
When you extend a generic type, you do not provide a type parameter list as part of the extension's definition.
-
Type parameter list from the original type definition is available within the body of the extension
-
The original type parameter names are used to refer to the type parameters of the original definition
extension Stack { var topItem: T? { return items.isEmpty ? nil: items[items.count - 1] } }
Type Constraints
-
Overview
- To enforce certain type constraints on the types that can be used with generic functions and generic types
- Type constraints specify a type parameter must inherit from a specific class or conform to a protocol composition
- Dictionary places a limitation on the types that can be used as keys for a dictionary
- Keys must be hashable, conform to the Hashable protocol
-
Type Constraint Syntax
extension Stack { var topItem: T? { return items.isEmpty ? nil: items[items.count - 1] } }
-
Type Constraints in Action
-
Type specific version
func findStringIndex(array: [String], valueToFind: String) -> Int? { for (index, value) in enumerate(array) { if value == valueToFind { return index } } return nil } let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] if let foundIndex = findStringIndex(strings, "llama") { println("Found index: \(foundIndex)") }
-
Generic version
func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? { for (index, value) in enumerate(array) { if value == valueToFind { return index } } return nil } let doubleIndex = findIndex([3.14, 0.1, 0.25], 9.3) let stringIndex = findIndex(["Mike", "John", "Andrea"], "John")
-
-
Associated Types
-
Overview
- When defining a protocol, sometimes, it is useful to declare one or more associated types as part of the protocol's definition.
- Gives a placeholder name (alias) to a type that is used as part of the protocol
- Actual type to use for the associated type is not specified until the protocol is adopted
-
Associated Types in Action
protocol Container { typealias ItemType mutating func append(item: ItemType) var count: Int { get } subscript(i: Int) -> ItemType { get } }
-
Int Stack Implementation
struct IntStack: Container { // original IntStack implementation var items = [Int]() mutating func push(item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } // conformance to the Container protocol typealias ItemType = Int mutating func append(item: Int) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Int { return items[i] } }
-
Generic Type Implementation, Swift is able to infer that T refers to ItemAlias in the protocol:
struct Stack<T>: Container { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } // conformance to Container protocol mutating func append(item: T) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> T { return items[i] } }
-
Where Clause
-
Define requirements on the type parameters associated with a generic function or type
-
Define where clauses as part of a type parameter list
-
Where clause allows you to require that an associated type conforms to a certain protocol and/or certain taupe parameters an associated types to be the same
func allItemsMatch<C1: Container, C2: Container where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> (someContainer: C1, anotherContainer: C2) -> Bool { if someContainer.count != anotherContainer.count { return false } for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } return true } var stackOfStrings = Stack<String>() stackOfStrings.push("1") stackOfStrings.push("2") stackOfStrings.push("3") //var arrayOfStrings = ["1", "2", "3"] // Error cause array of String does not conform to Container protocol var arrayOfStrings = Stack<String>() arrayOfStrings.push("1") arrayOfStrings.push("2") arrayOfStrings.push("3") if allItemsMatch(stackOfStrings, arrayOfStrings) { println("All items match.") } else { println("Not all items match.") }
Access Control
Overview
- Restricst access to parts of your code from code in other source files and modules
- You can assign specific access levels to individual types (classes, structures, and enumerations), as well as properties, methods, initialisers, subscripts
- Protocols can be restricted to a certain context, as can global constants, variables and functions
- Reduces the need to specify explicit access control levels by providing default access levels for typical scenarios
- Note
- Various aspics of your code that can have access control (properties, types, functions, etc) are referred as entities
Modules and Source Files
- Swift's access control model is base on the concept of modules and source files
- Module is a single unit code of distributions
- A framework or application buildt and shipped as a single entity that can be imported by another module with Swift's import keyword
- Each build target (such as an app bundle or framework) in Xcode is treated as a separate module in Swift
- A source file is a single Swift source code within a module (in effect a single file within an app or framework
- Though it is common to define individual types in a separate source files, a single source file can contain multiple types, functions, and so on
Access Levels
- Overview
- 3 different access levels
- Public access
- Entities to be used within any source file from their defining module and also in a source file from another module that imports the defining module
- When specifying the public interface to a framework
- Internal access
- Entities to be used within any source file from their defining module, but not in any source file outside that module
- When defining an app's or framework's internal structure
- Private access
- Restrict the use of an entity to its own defining source file
- To hide implementation of a specific piece of functionality
- Public access
- 3 different access levels
- Guiding Principle of Access Levels
- No entity can be defined in terms of another entity that has a lower (more restrictive) access level
- Example:
- A public variable cannot be defined as having an internal or private type, because the type might not be available everywhere that the public variable is used
- A function cannot have a higher access level than its parameter types and return type
- Default Access Levels
- Internal access with a few exceptions
- Access Levels for Single-Target App
- Typically you only need an internal access, unless you want to mark some parts of your code as private in order to hide their implementation details from other app's module.
- Access Levels for Framework
- Public access, as you are building public facing interface
- Note
- Any internal implementation details can still be internal or private.
Access Control Syntax
-
Syntax
public class SomePublicClass {} internal class SomeInternalClass {} private class SomePrivateClass {} public var somePublicVariable = 0 internal let someInternalConstant = 0 private func somePrivateFunction() {}
-
Implicit - internal access
class SomeInternalClass {} // implicitly internal var someInternalConstant = 0 // implicitly internal
-
-
Custom Types
-
If you want to specify an explicit access level to a custom type, do at the point that you define the type.
- The new type can then be used whenever its access level permits.
- Access level of a type also affects the default access level of type's members (properties, methods, inits and subscripts)
- If type's access level i private, all its members default level is also private
-
Note
- A public type defaults to having internal members, not public members
- If you want type member to be public, you must explicitly mark it as such
- Ensure API is something you opt-in to publish
public class SomePublicClass { // explicitly public class public var somePublicProperty = 0 // explicitly public class member var someInternalProperty = 0 // implicitly internal class member private func somePrivateMethod() {} // explicitly private class member } class SomeInternalClass { // implicitly internal class var someInternalProperty = 0 // implicitly internal class member private func somePrivateMethod() {} // explicitly private class member } private class SomePrivateClass { // explicitly private class var somePrivateProperty = 0 // implicitly private class member func somePrivateMethod() {} // implicitly private class member }
-
Tuple Types
- Access level for a tuple type is the most restrictive access level of all types used in the tuple.
- If members of the tuple comprises of internal and private access, tuple will be private
- Note
- Tuple does not have a standalone definition in a way that classes, structures, enums and functions do
- Tuple type's access is deduced automatically when the tuple is used
-
Function Types
-
Access level for a function type is calculated as the most restrictive access level of the function's parameter types and return type
func someFunction() -> (SomeInternalClass, SomePrivateClass) { // function implementation goes here }
-
Because the function's return type is private, you must mark the function's overall access level with private:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) { // function implementation goes here }
-
-
-
Enumeration Types
-
Individual cases of an enums automatically receive the same access level as the enum they belong to
-
Can't specify different access level for individual enum cases
public enum CompassPoint { case North case South case East case West }
-
-
Raw Values and Association Type
- The types used for any raw values or associated values in enum must have an access level at least as high as the enum's access level
- You cannot use a private type as the raw value type of an enumeration with an internal access level for example.
-
Nested Types
- Nested types within a private type will be private
- Nested types in a public type or internal type will be internal
- Explicitly declare public type as public to be public
-
-
Subclassing
-
Subclass cannot have a higher level access level than its superclass
-
You can override any class member that is visible in a certain access context
- An override can make inherited class more accessible than its superclass
- public class A {
- private func someMethod() {}
- }
- internal class B: A {
- override internal func someMethod() {}
- }
- An override can make inherited class more accessible than its superclass
-
Valid for a subclass member to call a superclass member that has lower access permission, as long as the call takes place within the allowed access level context
- same source file as the superclass for a private member call
- within the same module for internal member call
public class A { private func someMethod() {} } internal class B: A { override internal func someMethod() { super.someMethod() } }
-
Constants, Variables, Properties, and Subscripts
- A contant, variable or property cannot be more public than its type
- If a class member makes use of a private type, the member must also be declared as private
- private var privateInstance = SomePrivateClass()
- Getters and Setters
- Getters and setters for class members (constants, vars, props, subscripts) automatically receive the same access they belong to
- You can give setter a lower access level than its corresponding getter to restrict the read-write scope of that member.
- By writing private(set) or internal(set) before the var or subscript introducer
- Note
-
This rule apply for stored and computed properties
struct TrackedString { private(set) var numberOfEdits = 0 var value: String = "" { didSet { numberOfEdits++ } } }
var stringToEdit = TrackedString() stringToEdit.value = "This string will be tracked." stringToEdit.value += " This edit will increment numberOfEdits." stringToEdit.value += " So will this one." println("The number of edits is (stringToEdit.numberOfEdits)") // prints "The number of edits is 3"
-
You can assign explicit access level for both getter and setter if required, e.g. numberOfEdits getter is public and set is private:
public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { numberOfEdits++ } } public init() {} }
-
Initializers
- Custom inits can be assigned access level <= type they initialise
- Except for required init must have the same access level as the class it belongs to
- As with functions/methods, types of params cannot be more private than the init
- Default Initializers
- Default init has the same access level as the type initializes
- For a public type, the default init is considered internal
- Declare as public if you need to expose it
- Default Memberwise Initializers for Structure Types
- Default member wise init for a structure is considered private if any of the structure's stored properties are private
- Otherwise it is internal
Protocols
- If you want to assign an explicit level to a protocol type, do so at the point that you define a protocol
- Access level of each requirement within a protocol definition is automatically set to the same access level as the protocol
- Cannot set a different access level than the protocol it supports
- Ensure that all the protocol's requirements will be visible on any type that adopts the protocol
- Note
- If you define public protocol, the protocol's requirements require a public access level for those requirements when they are implemented
- Protocol Inheritance
- Inherited protocol have the same access level with its parent.
- You cannot write a public protocol that inherits from an internal protocol
- Protocol Conformance
- A type can conform to a protocol with a lower access level than the type itself
- The context type conforms to a particular protocol is the min of the type's access level and the protocol's access level
- If a type is public, and protocol is internal, type's conformance to that protocol is also internal
- Implementation of each protocol requirement has at least the same access level as the type's conformance to that protocol
- If a public type conforms to a internal protocol, the type's implementation of each protocol requirement must be at least internal
- Note
- In Swift as in Obj-C protocol conformance is global, it is not possible for a type to conform to a protocol in two different ways within the same program
Extensions
- Any members added in an extension have the same default access level as the type members declared in the original type being extended.
- Adding a protocol conformance with an extension
- You cannot provide an explicit access level modifier for an extension if you are using that extension to add protocol conformance.
- Protocol's own access level is used to privde the default access for each of the protocol requirements.
Generics
- Access level for a generic type/function is the minimum of the access level of the generic type/function itself and the access level of any type constraints on its type parameters.
TypeAliases
- Any type aliases you define are treated as distinct types for the purpose of access control
- A type alias can have an access level less <= access level of the type it aliases.
- Note
- This rule also applies to type aliases for associated types used to satisfy protocol conformances