In this tutorial we cover the following topics
- Few words before we start
- Miscellaneous
- Constants and variables
- Tuples
- Optionals
- Control flow
- Enumerations
- Collections
- Working with sequences
Swift is a totally new language. Any experience from other programming languges can help to lern it, but it is not an extension as Objective-C is an extension of C (event if it's not an extension, at least it's syntax is familiar for C programmers). In this Swift tutorial we assume that reader has some imperative programming language experience, so very basic ideas, like how a conditional statement
if
works, what a type conversion is, or what an error handling is about and what try-catch block means will be skiped. We will focus on
- a new ideas (sometimes derived from Objective-C) specific for Swift language,
- a new syntax of a well known ideas
.
Working with Swift for a short time, my opinion is that Swift is not a good programming language to start learning programming. It mixes many ideas from compiled and strong typed programming languages with ideas from script programming languages. In addition it contains many modern concepts. This makes a mixture of syntax which is very difficult to understand. What is worse, for Swift's novelty we have to pay with its complexity. Sometimes I have a thought that some madman took all programming ideas and put them all together without any reflection to the bag under the name Swift - for example please compare, available on my page, description of a protocols in Objective-C and Swift and you will know what I'm talking about.
Let's start with things to short to devote a separate section but too important to ignore them.
- There is no
++
operator in Swift. - Swift provides two range operators unknown in C-like world, as a shortcut for expressing a range of values.
- The closed range operator (a...b) defines a range that runs from
a
tob
, including both values. The value ofa
must not be greater thanb
. - The half-open range operator (a..<b) defines a range that runs from
a
tob
, but does not includeb
. The value ofa
must not be greater thanb
. If the value ofa
is equal tob
, than the resulting range will be empty. - Starting from Swift 4 we can omit the upper or lower bound of a range specification to create a one-sided range
12345678// Open range expressionlet someArray = ["one", "two", "three", "four", "five"]print(someArray[3...])// In Swift 3, you had to writeprint(someArray[3..<someArray.endIndex])print(someArray[...2])print(someArray[..<2])
["four", "five"]
["four", "five"]
["one", "two", "three"]
["one", "two"]
- The closed range operator (a...b) defines a range that runs from
- Strings in Swift are value types which means that every time string is passed to a function or method, or when it is assigned to a constant or variable it's value is copied.
- Moreover, in Swift, Array and Dictionary as String are all value types. They behave much like a simple int value in C, acting as a unique instance of that data. We don’t need to do anything special -- such as making an explicit copy -- to prevent other code from modifying that data behind our back. Importantly, we can safely pass copies of values across threads without synchronization. In the spirit of improving safety, this model will help us write more predictable code in Swift.
- String interpolation is a method to construct a new string from a mixture of constants, variables, expressions and literals. Each item we combine into the string literal is wrapped in a pair of parentheses, prefixed by a backslash
\()
. - Multi-line string literals. We can now create multi-line string literals, making it easy to produce precisely formatted text (e.g., JSON or HTML) directly into our source.
123456789101112// Multi-line string literalslet justAText = "just a text"let multiline = """first line;second line;third \line;"quotation";\(justAText)"""print(multiline)
first line;
second line;
third line;
"quotation";
just a text
In Swift we use two keyword to say that ,,something'' is variable or constant (variables whose values cannot be changed): we use
let
to say that it is a constant,var
to say thay it is a variable.
As we will see, Swift makes extensive use of constants. We can provide a type annotation when we declare constant or variable or we have to assign a value to allow compiler infer the type of constant or variable. So, to avoid Type annotation missing in pattern
Swift compile error, instead of
1 2 |
var variable; let constant; |
we have to write
1 2 3 4 5 6 7 |
var variableWithTypeAnnotation: Int let constantWithTypeAnnotation: String // Some code goes here variableWithTypeAnnotation = 1 constantWithTypeAnnotation = "Message" |
or
1 2 |
var variable = 1; let constant = "Message"; |
As we can see, Swift does not require us to write a semicolon after each statemet in our code, but we can do so if we want.
What is very important, once we've declared constant or variable of a certain type, we can't redeclare it, or change it to store values of different type. Nor can we change a constant into a variable or a variable into constant.
We can define multiple related variables of the same type on a single line
1 |
var red, green, blue: Int |
Tuples is a well known concept from script programming languages and something I always want to have in C. Of course we can live without it and mimic with for example arrays or dictionaries but tuples are more natural. Tuples group multiple values into a single compound value. The values within a tuple can be of any type and do not have to be of the same type as each other.
1 2 3 4 5 |
// Tuple of type (Int, String) let warning = (123, "This is a critical warning") var (currentMessageCode, currentMessageText) = warning print ("Message text " + currentMessageText) print ("Message text \(currentMessageText)") |
If we don't care about the first element of a tuple, we can write it as
1 |
(_, currentMessageText) = warning |
We can also use index numbers starting at zero to get tuple's element
1 |
print ("Message text " + warning.1) |
To make our code more readable, we can name the individual elements in a tuple when the tuple is defined
1 2 3 4 |
let alert = (messageCode: 456, messageText: "This is an alert") let info = (messageCode: 456, messageText: "This is an information you can simply ignore") print ("Message text " + alert.messageText) |
Tuples are great for temporary usage. They are not suited to being use as a complex data structure persisting for a long time. In such a case structures and classes are better choice.
Optionals are used in situations where a value may be absent. For example an optional integer is written as
Int?
.
1 |
var thisMayBeEmpty: Int? |
We set an optional variable to a valueless state by assigning it the special value nil
. Notice that Swift's nil
is not the same as nil
in Objective-C. In Objective-C nil
is a pointer to a nonexistent object. In Swift, nil
is not a pointer. It is the absence of a value of a certain type. Optionals of any type we want can be set to nil
, not just object types. Conversely, nil
cannot be used with nonoptionals. If a variable (rather constant) in our code needs to take no value, we have to declare it as an optional value of the appropriate type.
The key queston is: how we can use optionals? And the answer is not so obvious. We have few options
- Simple guess and try approach
123456var thisMayBeEmpty: Int?print(thisMayBeEmpty)thisMayBeEmpty = 1print(thisMayBeEmpty)thisMayBeEmpty = nilprint(thisMayBeEmpty)
doesn't work - we haveExpression implicitly coerced from Int? to Any
Swift compile warning in all of threeprint
s and results are not such as we want (Optional(1)
instead of1
)
nil
Optional(1)
nil
- If we are sure (but we have to be sure if we don't want to crash our application) that optional contains a non-
nil
value we can force unwrap its value with an exclamation mark added at the end of the optional's name
1234567print(thisMayBeEmpty!)if (thisMayBeEmpty != nil){print("Have some nonempty value: " + String(thisMayBeEmpty!))} else {print("Nothing to print")} - When working with optionals,
if-else
is something we must use. There is a special syntax we can use in this context. Optional binding is used to find out whether an optional contains a value, and if so, to make that value available as a temporary constant or variable.
12345if let nonempty = thisMayBeEmpty {print("Have some nonempty value: " + String(nonempty))} else {print("Nothing to print")}
Notice that fornonempty
we don't have to use an exclamation mark.We can do even more: we can chain together multiple optional bindings, and the entire chain fails gracefully if any link in the chain is
nil
.1234567891011let number1: Int?let number2: Int?number1 = 2number2 = nilif let value1 = number1, let value2 = number2 {print(value1, value2)} else {print("One of values is nil")} - When we are sure that optional has some value every time we will use it, we can "remove" the need to check and unwrap the optional's value every time it is accessed thanks we can assume to have a value all of the time we need it. These kinds of optionals are defined as implicitly unwrapped optionals and we write it by placing an exclamation mark instead of question mark after the type we want to make optional.
For example, this code
12var implicitOptional: Int!print(implicitOptional)in contrast to
12var thisMayBeEmpty: Int?print(thisMayBeEmpty)doesn't generate any warning or error and we can use
implicitOptional
variable without need of any explicit unwraping with an exclamation mark12implicitOptional = 1print(implicitOptional) - Another handy tool we can use is the nil-coalescing operator
??
. When used, for example in expression(a ?? b)
, it unwraps an optionala
if it contains a value, or return a default valueb
ifa
isnil
. The expressionb
must match the type that is stored insidea
.12thisMayBeEmpty = nilprint(thisMayBeEmpty ?? 3)The nil-coalescing operator is a shorthand for
1a != nil ? a! : b
In Swift we may use all well-known control statements like
if-then
or while
but with different than C-like syntax. We assume that reader is familiar with a variety of control flow statements, so only syntax is given.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
for index in 1...3 { print(index, terminator: ", ") } print() // Doesn't work /* for index in 3...1 { print(index, terminator: ", ") } print() */ for index in stride(from: 3, through: 1, by: -1) { print(index, terminator: ", ") } print() for _ in 1...3 { print("*", terminator: "") } print() var i=0; while i<3 { print("+", terminator: "") i += 1 } print() repeat { print("=", terminator: "") i += 1 } while i<6 print() |
1, 2, 3,
3, 2, 1,
***
+++
===
In Swift there is also a handy
guard
statement. We use guard
to require that a condition must be true in order for the code after the guard
statement to be executed. With no guard
we do this with if
but using a guard
statement improves the readability.
For example, instead of
1 2 3 4 5 6 7 |
var someValue = 5 if (someValue != 5) { print("ERROR: someValue is NOT equal to 5") } print("someValue is equal to 5") |
we can write something similar to
1 2 3 4 5 6 |
guard someValue == 5 else { print("ERROR: someValue is NOT equal to 5") //'guard' body may not fall through, consider using a 'return' or 'throw' to exit the scope } print("someValue is equal to 5") |
Why similar but not exactly like that? Notice that in the above if
second print
will be executed regardless of if
's truth or falsity. With a guard
there is no way to execute the code following this guard if condition is false. Swift force us to write the code so ther is no option to execute the code following the guard. In this case Xcode reports a compile error: 'guard' body may not fall through, consider using a 'return' or 'throw' to exit the scope.
If 'guard' body may not fall through, consider using a 'return' or 'throw' to exit the scope message confuses you, you can try to compare this code
1 2 3 4 |
guard true else { print("Condition not met") } print("Condition met") |
and it's result
Condition met
with the following code
1 2 3 4 |
guard false else { print("Condition not met") } print("Condition met") |
Because in the last program, the guard condition evaluates to false, again you will get an error 'guard' body may not fall through, consider using a 'return' or 'throw' to exit the scope.
You know nothing about functions yet, but this should be easy to follow and illustrative enough for this problem
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func someFunction() { var someValue = 5 guard someValue == 5 else { print("ERROR: someValue is NOT equal to 5") return } print("someValue is equal to 5") } someFunction() print("Hello after function call") |
someValue is equal to 5
Hello after function call
In Swift we can use labeled statements for loops or conditionals to explicitly say which of many nested loops / ifs we want to break or continue.
1 2 3 4 5 6 7 8 9 10 |
for level1 in 1...3 { level2Loop: for level2 in 1...3 { for level3 in 1...3 { guard level3 <= 2 else { break level2Loop } print("\(level1) \(level2) \(level3)"); } } } |
1 1 1
1 1 2
2 1 1
2 1 2
3 1 1
3 1 2
In Swift there is no need of break
usage in switch
's case
s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// No implicit fall through let text = "one" switch text { // Can't use "empty" case //case "one": //case "One": // print("Case 1") //Note: //There is no need to use break statement case "one", "One": print("Case ONE") case "two", "Two": print("Case TWO") default: print("Default") } |
Case ONE
If we want to use C-style fall through bahaviour a
fallthrought
keyword must be used. The fallthrough
keyword causes code execution to move to the next case or default block.
1 2 3 4 5 6 7 8 9 |
switch text { case "one", "One": print("Case ONE") fallthrough case "two", "Two": print("Case TWO") default: print("Default") } |
Case ONE
Case TWO
Remember that doing that, the
fallthrought
does not check the case condition for the switch case that it causes execution to fall into.
1 2 3 4 5 6 7 8 9 10 |
var number = 2 switch number { case 1, 2: print("1 or 2") fallthrough case 3, 4: print("3 or 4") default: print("all other options") } |
1 or 2
3 or 4
Another improvement in Swift is an ability of
switch
's cases to check if their values are included in an interval.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Inteval matching let number = 12 switch number { case 1...10: print("Range one") case 11..<15: print("Range two") case 15: print("Range three") default: print("Out of range") } |
Range two
Also tuples can be tested by case statement which can be very handy and allows to simplify our code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var point2D: (Double, Double) point2D = (2.5, 2) switch point2D { case (0, 0): print("Origin") case (_, 0): print("Point is on the OX axis") case (0, _): print("Point is on the OY axis") case (1..<2, 1..<2), (2...3, 2...3): print("Point is inside the restricted area") default: print("Free 2D point") } |
Point is inside the restricted area
Other things which may be useful is the ability to bind the value a switch
matches to temporary constants or variables, to be used in the body of the case.
1 2 3 4 5 6 7 8 9 10 11 |
point2D = (0, 5) switch point2D { case (0, 0): print("Origin") case (let x, 0): print("Point (\(x),0) is on the OX axis") case (0, let y): print("Point (0, \(y)) is on the OY axis") case let (x, y): print("Free 2D point (\(x), \(y))") } |
Point (0, 5.0) is on the OY axis
1 2 3 4 5 6 7 8 9 10 11 12 13 |
point2D = (2, 3) switch point2D { case (0, 0): print("Origin") case (let x, 0): print("Point (\(x),0) is on the OX axis") case (0, let y): print("Point (0, \(y)) is on the OY axis") case let (x, y) where x > y: print("Point (\(x), \(y)) from a 2D subspace") default: print("Eeee...") } |
Eeee...
Enumerations should be well known to all programmers. An enumeration defines a common name for a group of related values and enables us to work with those values like any other type. In C language enumerations are very simple, allowing us to relate names to a set of integer values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <stdio.h> int main () { enum planets { Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune; }; enum planets planet1, planet2; planet1 = Mars; planet2 = Earth; if (planet1 > planet2) puts ("First planet is farther from the Sun than second is."); else puts ("Second planet is farther from the Sun than first is."); return 0; } |
In Java we can do a lot more with enumerations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/* Example from: https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html */ public enum Planet { MERCURY (3.303e+23, 2.4397e6), VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6), MARS (6.421e+23, 3.3972e6), JUPITER (1.9e+27, 7.1492e7), SATURN (5.688e+26, 6.0268e7), URANUS (8.686e+25, 2.5559e7), NEPTUNE (1.024e+26, 2.4746e7); private final double mass; // in kilograms private final double radius; // in meters Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } private double mass() { return mass; } private double radius() { return radius; } // universal gravitational constant (m3 kg-1 s-2) public static final double G = 6.67300E-11; double surfaceGravity() { return G * mass / (radius * radius); } double surfaceWeight(double otherMass) { return otherMass * surfaceGravity(); } public static void main(String[] args) { if (args.length != 1) { System.err.println("Usage: java Planet <earth_weight>"); System.exit(-1); } double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight/EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Your weight on %s is %f%n", p, p.surfaceWeight(mass)); } } |
$ java Planet 175
Your weight on MERCURY is 66.107583
Your weight on VENUS is 158.374842
Your weight on EARTH is 175.000000
Your weight on MARS is 66.279007
Your weight on JUPITER is 442.847567
Your weight on SATURN is 186.552719
Your weight on URANUS is 158.397260
Your weight on NEPTUNE is 199.207413
In Swift enumerations are much like Java's enumerations. They incorporate many features usually supported by classes, such as (we will show all of them in class section of Swift tutorial)
- computed properties to provide some additional informations about the current value of enumeration,
- instance methods to provide functionality related to the values the enumeration represents,
- initializers,
- can be extended,
- can conform to protocols.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
import Foundation enum Planet { case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune } var currentPlanet: Planet // The type of 'selectedPlanet' is inferred while it is initialized. var selectedPlanet = Planet.Earth // Once a variable is declared as 'Planet', we can set it to a different 'Planet' // value using a shorter notation selectedPlanet = .Mars switch selectedPlanet { case .Earth: print("You can live here") case .Mars: print("Maybe one day you can live here") default: print("No chance to live here") } enum Action { case turnLeftDegree (Double), turnRightDegree (Double), makeForwardSteps (Int), makeBackwardSteps (Int), saySomething (String) } var currentAction = Action.makeForwardSteps(10) switch currentAction { case .turnLeftDegree(let degree): print("Turn left by \(degree) degree") case let .turnRightDegree(degree): print("Turn right by \(degree) degree") case .makeForwardSteps(var steps): print("Make \(steps) step(s) forward") case var .makeBackwardSteps(steps): print("Make \(steps) step(s) backward") case let .saySomething(text): print("Say: \(text)") } enum Planet2: Int { case Mercury = -2, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune } let earthsOrder = Planet2.Earth.rawValue print(earthsOrder) // Initializing from a raw value var currentPlanet2 = Planet2(rawValue: 1) print("\(currentPlanet2 ?? Planet2.Earth) is selected") |
Maybe one day you can live here
Make 10 step(s) forward
0
Mars is selected
Swift provides three primary collection types: arrays, sets and dictionaries. In Objective-C we have different types for mutable and immutable collections. In Swift every collection assigned to a variable is mutable and every collection assigned to a constant is immutable.
An array stores values of the same type in an indexed order. The same value can appear in an array multiple times at different positions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import Foundation // Declare an array var mutableArrayDeclaration: [String] let immutableArrayDeclaration: [String] // Define an empty array var mutableArray1 = [String]() var mutableArray2 = Array<String>() let immutableArray = [String]() // We can do mutableArray1.append("Zero") mutableArray1.append("One") mutableArray1.append("Two") // or mutableArray2 = ["zero", "one", "two"] // but can't do //mutableArrayDeclaration.append("Zero") // or //immutableArray.append("Zero") print(mutableArray1) print(mutableArray2) |
["Zero", "One", "Two"]
["zero", "one", "two"]
In Swift, differently than in Objective-C, we can't create an array that has pre-allocated memory but does not contain elements. We can't create an array of fixed, predefined size. A good news is that we can easily concatenate arrays.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// We can't //var mutableArray3 = [String](repeating: nil, count: 10) // but we can var mutableArray3 = [String](repeating: "", count: 5) print(mutableArray3) mutableArray3[3] = "THREE" print(mutableArray3) mutableArrayDeclaration = mutableArray2 + mutableArray3 print(mutableArrayDeclaration) mutableArrayDeclaration += ["FIVE", "SIX"] print(mutableArrayDeclaration) |
["", "", "", "", ""]
["", "", "", "THREE", ""]
["zero", "one", "two", "", "", "", "THREE", ""]
["zero", "one", "two", "", "", "", "THREE", "", "FIVE", "SIX"]
We can use subscript syntax to change a range of values at once, even it the replacement set of values has a different length than the range we are replacing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
mutableArrayDeclaration[1...2] = ["ONE", "TWO", "THREE"] print(mutableArrayDeclaration) mutableArrayDeclaration[4...6] = ["***"] print(mutableArrayDeclaration) mutableArrayDeclaration.remove(at: 4) print(mutableArrayDeclaration) // Iterating over an array for item in mutableArrayDeclaration { print("value: \(item)") } for (index, value) in mutableArrayDeclaration.enumerated() { print("at index: \(index) value: \(value)") } |
["zero", "ONE", "TWO", "THREE", "", "", "", "THREE", "", "FIVE", "SIX"]
["zero", "ONE", "TWO", "THREE", "***", "THREE", "", "FIVE", "SIX"]
["zero", "ONE", "TWO", "THREE", "THREE", "", "FIVE", "SIX"]
value: zero
value: ONE
value: TWO
value: THREE
value: THREE
value:
value: FIVE
value: SIX
at index: 0 value: zero
at index: 1 value: ONE
at index: 2 value: TWO
at index: 3 value: THREE
at index: 4 value: THREE
at index: 5 value:
at index: 6 value: FIVE
at index: 7 value: SIX
In Swift, to creating a multi-dimensional array we have to add another set of brackets. For example, to turn [Int]
array into an array of arrays, we would just write [[Int]]
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Example 1 // This is our array of arrays var array2DEx1 = [[Int]]() // We create three simple 1D arrays of Int var row1 = [11, 12, 13, 14] var row2 = [21, 22, 23] var row3 = [31, 32] // Add them all to the "main" array array2DEx1.append(row1) array2DEx1.append(row2) array2DEx1.append(row3) print(array2DEx1) array2DEx1[1].append(24) print(array2DEx1) array2DEx1[1][1] = 0 print(array2DEx1) |
[[11, 12, 13, 14], [21, 22, 23], [31, 32]]
[[11, 12, 13, 14], [21, 22, 23, 24], [31, 32]]
[[11, 12, 13, 14], [21, 0, 23, 24], [31, 32]]
1 2 3 4 5 6 7 8 9 |
// Example 2 var array2DEx2 : [[Int]] = [] array2DEx2 = Array(repeating: Array(repeating: 0, count: 2), count: 3) // or //var array2DEx2 : [[Int]] = Array(repeating: Array(repeating: 0, count: 10), count: 10) print(array2DEx2) array2DEx2[1][1] = 1 print(array2DEx2) |
[[0, 0], [0, 0], [0, 0]]
[[0, 0], [0, 1], [0, 0]]
1 2 3 |
// Example 3 var array2DEx3 : [[Int]] = [[1, 2, 3], [4, 5, 6]] print(array2DEx3) |
[[1, 2, 3], [4, 5, 6]]
We have to be very careful working with arrays in Swift -- generally, substitution with =
operator makes a copy of an array.
For simple types such as integers and other structureswe have
1 2 3 4 5 6 7 |
var numbers = [1, 2, 3, 4, 5] var numbersCopy = numbers numbers[0] = 100 print(numbers) // Prints "[100, 2, 3, 4, 5]" print(numbersCopy) // Prints "[1, 2, 3, 4, 5]" |
If the elements in an array are instances of a class, the semantics are the same, though they might appear different at first.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// An integer type with reference semantics class IntegerReference { var value = 10 } var firstIntegers = [IntegerReference(), IntegerReference()] var secondIntegers = firstIntegers // Modifications to an instance are visible from either array firstIntegers[0].value = 100 print(secondIntegers[0].value) // Prints "100" // Replacements, additions, and removals are still visible // only in the modified array firstIntegers[0] = IntegerReference() print(firstIntegers[0].value) // Prints "10" print(secondIntegers[0].value) // Prints "100" |
More we can read in Array (Modifying Copies of Arrays).
A set stores distinc values of the same type in a collection with no defined ordering. We can use a set instead of an array when the order of elements is not relevant, or when we need to ensure that an element only appears once. A type of objects stored in a set must be hashable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import Foundation var setOfDigitsNames1 = Set<String>() setOfDigitsNames1.insert("one") setOfDigitsNames1.insert("two") print(setOfDigitsNames1) var setOfDigitsNames2: Set<String> = ["one", "two"] var setOfDigitsNames3: Set = ["one", "two"] print(setOfDigitsNames2) print(setOfDigitsNames3) if !setOfDigitsNames3.isEmpty { if setOfDigitsNames3.contains("two") { setOfDigitsNames3.remove("two") } if !setOfDigitsNames3.contains("three") { setOfDigitsNames3.insert("three") } } print(setOfDigitsNames3) var setOfInts1: Set = [1, 2, 3, 4] var setOfInts2: Set = [3, 4, 5, 6] print(setOfInts1) print(setOfInts2) print(setOfInts1.union(setOfInts2)) print(setOfInts1.intersection(setOfInts2)) print(setOfInts1.subtracting(setOfInts2)) print(setOfInts1.symmetricDifference(setOfInts2)) |
["one", "two"]
["one", "two"]
["one", "two"]
["one", "three"]
[2, 3, 1, 4]
[5, 6, 3, 4]
[5, 6, 2, 3, 1, 4]
[3, 4]
[2, 1]
[5, 6, 2, 1]
To test set membership or equality we can use
- To test if two sets contain exactly the same values, we use
==
operator. - To test if all of the values of a set are contained in the specified set, we use
isSubset(of:)
method. We useisStrictSubset(of:)
if we want to exclude equality of both sets. - To test if a set contains all of the values from a specified set, we use
isSuperset(of:)
method. We useisStrictSuperset(of:)
if we want to exclude equality of both sets. - To test if both sets have no common values, we use
isDisjoint(with:)
method.
A dictionary stores associations between keys (all of the same type) and values (all of the same but possible different than keys type) with no defined order. A dictionary key type must be hashable.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import Foundation var dictionary1 = [String:String]() // shorthand form var dictionary2 = Dictionary<String, String>() // full form var dictionary3 = ["digit0": "zero", "digit1": "one", "digit2": "two"] print(dictionary1) print(dictionary2) print(dictionary3) dictionary3["digit2"] = "TwO" print(dictionary3) |
[:]
[:]
["digit0": "zero", "digit2": "two", "digit1": "one"]
["digit0": "zero", "digit2": "TwO", "digit1": "one"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// updateValue(_:forKey:) method returns an optional value of the dictionary's // value type; in our case this is String? or "optional String" var oldValue = dictionary3.updateValue("TWO", forKey: "digit2") print(dictionary3) print("Old value: \(oldValue!) (\(oldValue))") dictionary3["digit3"] = "three" print(dictionary3) // Iterating over a dictionary for (key, value) in dictionary3 { print("key: \(key) value: \(value)") } for key in dictionary3.keys { print("key: \(key)") } for value in dictionary3.values { print("value: \(value)") } |
["digit0": "zero", "digit2": "TWO", "digit1": "one"]
Old value: TwO (Optional("TwO"))
["digit0": "zero", "digit2": "TWO", "digit1": "one", "digit3": "three"]
key: digit0 value: zero
key: digit2 value: TWO
key: digit1 value: one
key: digit3 value: three
key: digit0
key: digit2
key: digit1
key: digit3
value: zero
value: TWO
value: one
value: three
Swift 4 brings a number of improvements to make dictionaries more powerful, useful and usable.
Sequence-based initializer.
We can now create dictionaries from a sequence of key-value pairs.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let dict01 = Dictionary(uniqueKeysWithValues: zip( 1..., words) ) print(dict01) let streamOfTuples = [("one", 1), ("two", 2), ("three", 3), ("four", 4)] let dict02 = Dictionary( uniqueKeysWithValues: streamOfTuples) print(dict02) |
[2: "two", 3: "three", 1: "one", 4: "four"]
["three": 3, "four": 4, "one": 1, "two": 2]
Merging
Dictionary now includes an initializer that allows us to merge two dictionaries together. If we want to merge one dictionary into another, Swift also provides a merge(_:uniquingKeysWith:)
method. Both allow us to specify a closure to resolve merge conflicts caused by duplicate keys.
1 2 3 4 5 6 7 8 |
var dictionary = [("a", 1), ("b", 2), ("a", 3), ("c", 4)] var afterMerge = Dictionary(dictionary, uniquingKeysWith: { (current, _) in current }) print(afterMerge) // Keeping existing value for key "a": afterMerge.merge(zip(["a", "c"], [3, 4])) { (current, _) in current } print(dictionary) |
["b": 2, "a": 1, "c": 4]
[("a", 1), ("b", 2), ("a", 3), ("c", 4)]
More details can be found for example in How to work with Dictionaries in Swift 4.
In Swift we can use
zip(_:_)
function to create a sequence of pairs built out of two underlying sequences.
1 2 3 4 5 6 |
let words = ["one", "two", "three", "four"] let numbers = 1...4 for (word, number) in zip(words, numbers) { print("\(word): \(number)") } |
one: 1
two: 2
three: 3
four: 4
If the two sequences passed to zip(_:_:) are different lengths, the resulting sequence is the same length as the shorter sequence. In this example, the resulting array is the same length as words:
1 2 3 |
let naturalNumbers = 1... let zipped = Array(zip(words, naturalNumbers)) print(zipped) |
[("one", 1), ("two", 2), ("three", 3), ("four", 4)]
We can use map(_)
function which returns an array containing the results of mapping the given closure over the sequence’s elements.
1 2 3 4 |
let lowercaseNames = words.map { $0.uppercased() } let letterCounts = words.map { $0.count } print(lowercaseNames) print(letterCounts) |
["ONE", "TWO", "THREE", "FOUR"]
[3, 3, 5, 4]
We can use filter(_:)
function which returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
1 2 |
let shortNames = words.filter { $0.count > 3 } print(shortNames) |
["three", "four"]
We can use reduce(_:_:)
function which returns the result of combining the elements of the sequence using the given closure.
1 2 3 4 5 6 7 8 |
let items = [1, 2, 3] let total = items.reduce(4, +) print(total) total = items.reduce(4, { x, y in x + y }) print(total) |
10
10
More details about map
, filter
and reduce
can be found in Swift Guide to Map Filter Reduce