Swift Introduction/Printable version
This is the print version of Swift Introduction You won't see this message or any elements not part of the book's content when you print or preview this page. |
The current, editable version of this book is available in Wikibooks, the open-content textbooks collection, at
https://en.wikibooks.org/wiki/Swift_Introduction
Introduction
Introduction
editWhat is Swift?
editSwift is an open source programming language, introduced by Apple in 2014. It is used for their operating systems macOS for desktops and iOS for mobile devices, for instance iPhone or iPad. Since 2015 Swift has also been available on Linux under the Apache Licence 2.0[1]. It is a modern language that includes multiple paradigms like object orientation, functional- and imperative programming. Apple included useful features from other languages like C#, Ruby, Python or Haskell.
Swift on Linux
editInstall
editBefore you can start with Swift, you first have to download and install the compiler and other components[2]. Those can be downloaded from Swift's download page. On Linux, clang[3] has to be installed too using following command:
$ sudo apt-get install clang
IDE for Linux
editXcode is only available on macOS. However, there are alternatives which provide similar functionality:
- JetBrain's CLion offeres a Swift extension which features code highlighting and completion and also a debugger[4].
- The Open-Source Editor Atom offeres a plugin that works similar to Xcode's playground where you can easily try new code and immediatly see what it does[5].
First example
editBefore we dive right into Swift's basic and advanced features, let's have a look at the world-famous "Hello World" example.
print("Hello World")
Now, let's have a first glance at what variables and constants look like.
var name: String = "John"
let greeting: String = "Welcome to the world of Swift, "
print(greeting + name)
// Welcome to the world of Swift, John
References
edit- ↑ Apache Software Foundation [online][accessed: 18.09.2017] | https://www.apache.org/licenses/LICENSE-2.0
- ↑ Swift.org [online][accessed: 18.09.2017] | https://swift.org/getting-started/
- ↑ clang [online][accessed: 18.09.2017] | https://clang.llvm.org
- ↑ JetBrain Swift Plugin [online][accessed: 18.09.2017] | https://blog.jetbrains.com/clion/2015/12/swift-plugin-for-clion/
- ↑ Atom.io Swift Playground [online][accessed: 18.09.2017] | https://atom.io/packages/swift-playground
SwiftBasics
Swift Basics
editIn this chapter, you will learn how to use the basic concepts of Swift[1], including variables, constants and arrays. Another important topic covered in this chapter is how functions and classes are written in Swift.
Variables and Constants
editLike in most other languages, Swift uses variables to store values and to refer to them by an unique name. The values of these variables can be mutable or immutable. It's good practice to use constants whenever the value of it does not have to be changed in the code later. This also makes the code you writer safer.
Declaring a variable
editIn Swift, variables are declared with the keyword var
. It is recommended
to use var
only if you need to change the value of the variable later.
var greeting = "Good morning!"
Declaring a constant
editIf it is not necessary to change a variable's value it can be declared with
the keyword let
.
let answerToEverything = 42
Type Annotations
editType annotations can be used to be clear about what kind of values a constant or a variable can store. This is necessary if there is no initial value. If an initial value is provided, Swift infers which type the variable should have.
var greeting: String
//Declaring a single variable
let alpha, beta, gamma: Double
//Declaring 3 variables in one line
Changing Values
editIf a variables value should be changed, it has to be of the same type
of the original value. In this example, the value of greeting is changed to
another String
"Good evening".
var greeting = "Good morning!"
greeting = "Good evening!"
//greeting has now the value "Good evening!"
let pi = 3.1415
pi = 3.2
// Compile-time error: This value cannot be changed
Datatypes
editType Safety and Type Inference
editSwift is a type-safe language, which means for example it is not possible to assign a number
to a variable that stores a String
or to call a function with parameters
of the wrong type. At compile-time Swift performs type checks
and finishes without error only when there are no mismatched types.
If you declare a variable or a constant without specifying the type Swift uses
Type Inference to find out the appropriate type. If you provide a floating-point number
when declaring a variable, Swift inferes it to be a value of the type Double
.
let absolutZero = -273.15
// absolutZero is inferred to be of type Double
Integer
editIntegers are whole numbers like 45 or -37. They do not have a fractional component and
are signed or unsigned. If an Integer is signed it can be zero, a positive or a negative number. Unsigned Integers can only be zero or positive. Swift provides Integer Types
in 8, 16, 32 and 64 bit form. Although it is possible to pick a specific size, for
example UInt8
or Int32
, in most cases the Int
type is used.
It has the same size as the platform's native word size. In modern Operating Systems
it has a size of 64 Bit. Uint
is an unsigned Integer Type which also has the size
of the platform's word size.
Floating Point Numbers
editFloating-point numbers which have a fractional component can represent values
which are much larger or much smaller than the possible values of an Int
.
Numbers like -0.0001 or 19.99 can be stored in variables of the type Double
or Float
.
Double
, a 64 Bit floating-point number, has a precision of at least 15 decimal
digits. Float
provides a precision of 6 decimal digits.
Booleans
editIn Swift, the Boolean datatype is called Bool
and can have the constant values
true
or false
.
var switchedOn = true
// switchedOn is now true and of type Boolean
switchedOn = false
Boolean values work great with control flow operations like if statements:
if switchedOn{
print("The light is on!")
// Will be executed if switchedOn = true
}
else{
print("It's dark as a dungeon!")
}
Optionals
editOptional variables have two possible states: There is a value or there is no value. For
declaring a variable with an optional datatype a ?
is used.
var salary: Double?
// salary is set to nil automatically and contains no value
salary = 1923.12
// salary now has a value but is still of type Double?
salary = nil
// salary is set back to nil and contains no value
To find out whether a value has already been set or not you can use an if statement and the ==
(equal to) or !=
(not equal to) operators.
if salary == nil{
print("Salary for this employee has not been set yet!")
} else {
print("This employee's salary is \(salary!)")
}
In the example above you can see an exclamation mark at the end of the optionals name.
It is used to unwrap the value of the optional to use it. If a !
is used on
an optional which contains no value a runtime error will be triggered.
Optional Binding
editOptional binding checks whether an optional is set, and if so, its value is set to a new variable or constant which is temporarily available.
if let netSalary = salary{
print("This emplyee's net salary is \(netSalary*0.85)")
} else {
print("Salary for this employee has not been set yet!")
}
The new constant netSalary
is only available within the if-Statement and does
not have to be unwrapped.
Collection Types
editIn Swift there are three primary collection types.
- Array for an ordered collection of values
- Set for an unordered collection of unique values
- Dictionary for unordered key - value pairs
Just like variables and constants, all three collection types are type safe, which means it is not possible to insert a value of the wrong type. The positive aspect of this type safety is, that you always know which type the value you get from a collection has.
Arrays, sets and dictionaries that are assigned to a variable are mutable, which means values can be added, changed or deleted after they were created. If you need a immutable collection type it has to be assigned to a constant.
Array
editArrays provide an ordered list of elements of the same type. There are two ways to declare
an array, Array<Element>
or [Element]
.
Element is the type of the values that should be stored in the array. Arrays can be created empty or
filled with values. It's also possible to create the Array with a default value.
var intArray = Array<Int>()
// creates an empty Integer Array
var preinitialized = Array(repeating: 1, count: 5)
print(preinitialized)
// prints "[1, 1, 1, 1, 1]"
Arrays can also be initialized with array literals. This is a list of values, separated by commas.
let fibonacci = [1,1,2,3,5,8,13]
// creates an immutable Integer Array
Properties and Methods of Arrays
editintArray.append(9)
// Adds '9' to intArray
intArray += [1,2,3]
// emptyInt now contains 9, 1, 2 and 3
print(intArray.count)
// prints the number of elements in the array, 4
if(intArray.isEmpty){
print("I'm an empty Array! :( ")
} else {{
print("I'm storing \(intArray.count) elements!")
}
Subscript syntax is used to access the elements in an array. The index starts at zero, which means for accessing the first element of your array you have to add [0] to the array's name.
var firstValue = intArray[0]
// firstValue is now 9
Set
editA set stores values of the same type and is used when the order of the items is not important. Sets also ensure, that no duplicate values appear. Values can only be stored in a set, if they are hashable. Swift's basic types like String, Int and Bool are all hashable by default.
var devices = Set<String>()
// creates a new empty set
devices.insert("iPhone")
print(devices)
// prints ["iPhone"]
var webSuffixes: Set<String> = [".html", ".js", ".css"]
print(webSuffixes)
// prints [".js", ".html", ".css"]
print("There are \(webSuffixes.count) common suffixes in web development.")
// prints There are 3 common suffixes in web development.
if webSuffixes.contains(".py"){
print("Yeah, Python made it to the top 3 :)")
} else {
print("Python is not in the top 3 :( ")
}
// prints "Python is not in the top 3 :( "
for suffix in webSuffixes{
print ("\(suffix)")
}
// .css
// .js
// .html
Sets offer a huge amount of set operations. These operations implement the most important rules of the mathematical set theory.
- intersection
- symmetricDifference
- union
- subtracting
The following snippet shows how these operations work.
let favSongs: Set = ["Enter Sandman", "Bohemian Rapsody", "Blitzkrieg Bop", "Painkiller"]
let songsFrom90s: Set = ["Raining Blood","Enter Sandman","Painkiller","Wonderwall"]
var playList = Set<String>()
playList = favSongs.union(songsFrom90s)
/* union combines the values of both sets, values which are in both sets will be displayed once.
["Blitzkrieg Bop", "Raining Blood", "Bohemian Rapsody", "Enter Sandman", "Painkiller", "Wonderwall"]*/
playList = favSongs.intersection(songsFrom90s)
/* intersect creates a new list which contains all values that exist in both Sets ["Enter Sandman", "Painkiller"] */
playList = favSongs.symmetricDifference(songsFrom90s)
/* symmetricDifference stores all values of both sets into a new set, except those which were in both sets.
["Blitzkrieg Bop", "Raining Blood", "Bohemian Rapsody", "Wonderwall"]*/
playList = favSongs.subtracting(songsFrom90s)
/*subtracting creates a new set which only includes values which are not in the second set. ["Blitzkrieg Bop", "Bohemian Rapsody"] */
Dictionary
editDictionaries store Key - Value pairs in an unordered collection. The key is
an unique identifier for the value. Similar to arrays, there are two ways to declare
a dictionary.
Dictionary<Key, Value>
or [Key: Value]
. Moreover dictionaries can
also be created using dictionary literals.
var offeredStudies = [String: String]()
// creates an empty [String: String] dictionary
offeredStudies["SWD"] = "Software-Design"
offeredStudies["ITM"] = "Internettechnik"
// adds two key-value pairs to the dictionary
offeredStudies["ITM"] = "Internettechnik und Mediendesign"
// changes the value of "ITM"
print(offeredStudies)
// prints ["SWD": "Software-Design", "ITM": "Internettechnik und Mediendesign"]
var locations: [String: String] = ["K": "Kapfenberg", "G": "Graz", "B": "Bad Gleichenberg"]
// creates a dictionary with a dictionary literal
print("FH Joanneum has \(locations.count) locations in Styria.")
// prints "FH Joanneum has 3 locations in Styria."
for (shortcut, location) in locations{
print("\(shortcut) is a shortcut for \(location)")
// G is a shortcut for Graz
// K is a shortcut for Kapfenberg
// B is a shortcut for Bad Gleichenberg
}
Control Flow
editSwift provides a lot of ways to control the way your code is executed.
For-In Loop
editUsing a For-In Loop is an easy way to iterate over an array, a set or any other type of sequence or range.
let grades: [Int: String] = [1: "Sehr Gut", 2: "Gut", 3: "Befriedigend", 4: "Genuegend", 5: "Nicht genuegend"]for (grade, word) in grades.sorted(by: <){
print("\(grade) is a \"\(word)\"")
}
// 1 is a "Sehr Gut"
// 2 is a "Gut"
// 3 is a "Befriedigend"
// 4 is a "Genuegend"
// 5 is a "Nicht genuegend"
In a typical For-In Loop the counter always increases by 1. If you want to make smaller or bigger steps, you have to use stride.
let max = 100
for quarters in stride(from: 25, to: max, by: 25){
print(quarters)
}
// 25
// 50
// 75
While Loop
editWhile Loops are very useful if you do not know how many iterations you need. A while loop executes statements as long as a condition is true. There are two types of while loops.
- while checks whether the condition is true before the statements are executed.
- repeat-while executes the statements and checks if the condition is still true at the end.
var cardValues = [Int]()
for value in 1...10{
cardValues.append(value)
// creates an array with values 1 to 10 - simulates a card deck
}
var bankCount = 0
while bankCount < 21 {
var randomIndex = Int(arc4random_uniform(UInt32(cardValues.count)))
// creates a random number - simulates picking a random card
bankCount += cardValues[randomIndex]
print(bankCount)
if(bankCount > 21){
print("The bank loses!")
}
else if(bankCount == 21){
print("The bank wins!")
}
}
If
editThe if statement is the simplest way to decide which statements should be executed, based on certain conditions. It is used if there are just a few possible conditions. The if statemant can stand alone, but it's best practice to always provide an else statement as well, because it makes the code more readable and understandable. === Switch ===
var switchedOn = true
if(switchedOn == true){
print("All lights are on")
} else {
print("Can someone please turn on the light?")
}
It is also possible to provide different code paths for more than just one condition:
var score = 88;
if(score > 90 && score <= 100){
print("Perfect!")
} else if(score > 80 && score <= 90){
print("Good Job!")
} else if(score > 70 && score <= 80){
print("Not Bad!")
} else if(score > 60 && score <= 70){
print("Puh, that was close!")
} else{
print("Good luck next time")
}
As you can see, a growing number of conditions leads to a lot of duplicated code. A switch statement can be used to reduce the amount of duplicated code.
Switch
editA switch statement usually has several possible cases for the condition it checks. All switch statements have to be exhaustive, which means every possibe value of the condition has to have a case. Therefor a default statement should be provided which is executed if none of these cases fit.
var grade = "A"
switch grade{
case "A":
print("Excellent")
case "B":
print("Above average")
case "C":
print("Satisfactory")
case "D":
print("Below Average")
case "F":
print("Failure")
default:
print("Test not attempted")
}
Functions
editFunctions are an important part of your code. They have an identifying name - it's best practice to use a name which describes what the function does - which is used when you call the function. They can have zero to many parameters. Those input values are passed in as soon as you call the function.
Defining and Calling Functions
editIn the code snippet below, you can see the definition of a function begins with fund followed by the name and an optional parameter list. The ->
operator specifies the return type of this function. A function which does not have the arrow in the definition does not have a return value.
func combineStrings(begin: String, end: String) -> String{
let combinedString = begin + end
return combinedString
}
print(combineStrings(begin: "Let's get ", end: "swifty!"))
// prints "Let's get swifty!"
Parameters and Return Values
editFunctions can have zero to many parameters and also zero to many return values. In the example below, you can see a function which takes two integer values as arguments and returns two integer values. The second function has no arguments and no return value.
func sumAndDifference(value1: Int, value2: Int) -> (sum: Int, difference: Int){
let sum = value1 + value2
let difference = value1 - value2
return (sum, difference)
}
func printTimestamp(){
let date = Date() //gets the current date
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
print(String(hour) + ":" + String(minutes))
}
printTimestamp()
// prints hours and minutes whenever it is called
print(sumAndDifference(value1: 10, value2: 5))
// prints "(sum: 15, difference: 5)"
Argument Labels and Parameter Names
editIn Swift, parameters have an argument label which is used when calling the function and a parameter name, which is used in the implementation.
func getsFined(allowed speed: Double, measured value: Double) -> String {
if(speed < value){
return "Driver was too fast - that's gonna be expensive"
}
else{
return "Good boy"
}
}
print(getsFined(allowed: 100, measured: 120))
// prints "Driver was too fast - that's gonna be expensive"
It is also possible to write functions without argument labels.
func add2Numbers(_ number1: Int, _ number2: Int) ->Int{
return number1 + number2
}
print(add2Numbers(4,8))
// 12
Variadic Parameters
editThese parameters accept a variable number of arguments of the same type. It is especially useful when you do not know the exact number of arguments you want to pass to the function or the number of required arguments changes from one function call to the next.
func calcCart(_ prices: Double...) -> String {
var sum: Double = 0
for price in prices{
sum += price
}
return String(sum)
}
print("The items in your cart cost " + calcCart(10.99, 9.99, 5.69))
// prints "The items in your cart cost 26.67"
Function Types
editA function's type consists of the parameters types and its return type. Let's take a look at the function type of one of the functions from the snippets above.
func getsFined(allowed speed: Double, measured value: Double) -> String
This function is made up from two arguments of type Double
and a return type String
. Therefor the function type is (Double, Double) -> String
Functions can also have function types as return values.
func calcTaxFood(_ prices: Double...) -> String {
var sum: Double = 0
for price in prices{
sum += price*0.1
}
return String(sum)
}
func calcTaxNonFood(_ prices: Double...) -> String {
var sum: Double = 0
for price in prices{
sum += price*0.2
}
return String(sum)
}
func chooseTaxCalculator(isFood: Bool) ->(Double...) -> String {
return isFood ? calcTaxFood : calcTaxNonFood
// if isFood is true, calcTaxFood will be returned
// it it is false, calcTaxNonFood will be returned
}
let taxFood = chooseTaxCalculator(isFood: true)(19.99, 12.99, 6.79)
let taxNonFood = chooseTaxCalculator(isFood: false)(9.99, 1.99, 14.99)
print("You paid " + taxFood + "Euro taxes for your food and " + taxNonFood + "Euro for the rest.")
Nested Functions
editFunctions which are defined inside another function are called nested functions. They are not visible outside the function. However, they can be used outside the function if the enclosing function returns it.
func itemCounter(incoming: Bool) -> (Int) -> Int {
func increaseCount(count: Int) -> Int{
print("\(count) items were added to our current stock")
return itemsOnStock + count
}
func decreaseCount(count: Int) -> Int{
print("\(count) items were shipped to customers")
return itemsOnStock - count
}
return incoming ? increaseCount : decreaseCount
}
var itemsOnStock = 8
let itemsIncoming = itemCounter(incoming: true)
let itemsOutgoing = itemCounter(incoming: false)
print("There are \(itemsOnStock) items in the warehouse")
// There are 8 items in the warehouse
itemsOnStock = itemsIncoming(10)
// 10 items were added to our current stock
itemsOnStock = itemsOutgoing(7)
// 7 items were shipped to customers
print("There are \(itemsOnStock) items in the warehouse")
// There are 11 items in the warehouse
Classes and Structures
editAs an object-oriented language, Swift also provides classes, the construction plan for objects or instances, and structures, a similar construct. Interfaces, which are used to make the class or the structure available for other code parts, are available automatically.
Classes and structures share a lot of features, for example:
- Properties to store values
- Methods which provide functionality
- Both can be extended
- Initializers to set up their initial state
However, there are some features only classes provide:
- Inheritance
- Check and interpret a classes type at runtime
- Class can free up ressources using Deinitializers
Class or Structure?
editBefore deciding whether a class or a structure suits better for your needs, a few characteristics of both constructs have to be considered. One of the most important differences is, that class are always passed by reference whereas structures are passed by value.
Apple suggests[2] using structs in these situations:
- The primary purpose of the structure is to encapsulate few simple data values.
- It is reasonable to expect that the values will be copied and not referenced.
- All properties within the structure are value types.
- The structure does not need to inherit properties or behavior from other existing types.
In the snippet below you can see two structures, SoftTyre and HardTyre, which store values that describe the characteristics of a tyre. As you can see, only simple values like Integers and Bools are stored. The class Racecar also includes some simple data values like weight or teamName, but also an instance of the SlickTyre structure.
struct DryTyre{
var forWetCondition = false
var grip = 3
var durability = 3
}
struct WetTyre{
var forWetCondition = true
var grip = 4
var durability = 2
}
class Racecar{
let teamName = "Red Bull Racing"
var tyre = DryTyre()
var weightEmpty = 650
var weightWithDriver = 728
}
Accessing Properties
editProperties of classes and structures can be accessed using dot syntax.
var car = Racecar()
// create an instance
print("\(car.weightEmpty)")
// prints "650"
car.weightWithDriver = 732
// assign a new value using dot syntax
print("This tyre suits for wet conditions: \(car.tyre.forWetCondition)\nand has a durability value of: \(car.tyre.durability)")
// This tyre suits for wet conditions: false
// and has a durability value of: 3
Memberwise initialization of Structure Types
editThe properties of a new structure instance can be initialized using memberwise initializers which are automatically generated.
let superSoft = SoftTyre(forWetCondition: false, grip: 4, durability: 2)
// create and initialize a new instance of the SoftTyre struct
car.tyre = superSoft
print("This tyre has a durability value of: \(car.tyre.durability)")
// This tyre has a durability value of: 2
Value Type vs. Reference Type
editStructures, Enumerations and all basic types in Swift, for example integers, strings and arrays, are value types, which means the value is copied when it is passed to a function. Changes to a copied value of an integer inside a function do not affect the original value outside.
let ultraSoft = SoftTyre(forWetCondition: false, grip: 5, durability: 1)
var tyre = ultraSoft
tyre.durability = 4
print("Durability of tyre is now \(tyre.durability)")
// Durability of tyre is now 4
print("Durability of ultraSoft ist still \(ultraSoft.durability)")
// Durability of ultraSoft ist still 1
Classes are reference types, which means they are not copied when they are passed to a function. Instead, references to already existing instances are used. In the snippet below, an instance of Racecar is assigned constant called rb13
. After assigning the properties raceWins and weightEmpty rb13
is assigned to a new constant rb14. As the instance of the Racecar class is passed by reference, changes in rb14
automatically affect the properties in rb13
.
let rb13 = Racecar()
rb13.raceWins = 39
rb13.weightEmpty = 680
let rb14 = rb13
rb14.raceWins = 42
rb14.weightEmpty = 700
print("rb13 now also has \(rb13.weightEmpty) kg and \(rb13.raceWins) wins")
// rb13 now also has 700 kg and 42 wins
References
edit- ↑ Apple Inc. | 2017 | Swift programming language | [online][accessed: 18.09.2017] | https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-ID0
- ↑ Apple Inc. | 2017 | Swift - Classes and Structs | [online][accessed: 18.09.2017] | https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-ID82
SwiftAdvanced
Swift Advanced
editClosure Expressions
editClosure expressions are unnamed, anonymous functions which can interact with values from their surrounding contextref name="error">Apple Inc. | 2017 | Closure Expressions | [online][accessed: 18.09.2017] | https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID94</ref>. They are short function-like constructs without a declaration or name.
In this snippet, you can see the general syntax of a closure expression. It starts with opening curly brackets, a parameter list and a return value. They keyword in
marks the beginning of the closure's body. After the return statement the expression is closed with a curly bracket.
// basic syntax
let add = {(int1: Int, int2: Int) -> Int in return int1 + int2}
var result = add(20,5) // 25
If a closure is assigned to a function type, a shorthand syntax can be used because Swift can infer the data types from the context. In the shortest version, you only have to tell Swift what to do with the parameters. $0
is short syntax for the first passed in argument, $1
for the second, and so on.
let subtract: (Int, Int) -> Int = { int1, int2 in return int1 - int2 }
let multiply: (Int, Int) -> Int = { $0 * $1 }
result = subtract(20,5) // 15
result = multiply(20,5) // 100
Trailing Closures
editClosures can be passed to functions as arguments. A trailing closure can be used as the function's final argument. It's not written within the functions parentheses but right after them.
In the snippet below, you can see how a closure is passed to Swift's map function. The map
function can be called on an array, the closure is then executed on every item of the array. In this example, the map functions checks whether a year is a leapyear or not. All years are converted to a string and " is a leap year" is appended if the prerequisites are fulfilled.
// create an Array with values from 1950 to 2020
var years = [Int]()
for year in stride(from: 1950, to: 2020, by: 1){
years.append(year)
}
let leapYears = years.map{ (year) -> String in
var output = ""
let year = year
if(year%400 == 0){
output.append(String(year)+" is a leap year")
}
else if(year % 100 != 0 && year % 4 == 0){
output.append(String(year) + " is a leap year")
}
else{
output.append(String(year) + " is not a leap year")
}
return output
}
for year in leapYears{
print(year)
}
Properties
editIn Swift, there are two main types of properties. Stored properties are used to store values of variables and constants associated with a class or a structure. Computed properties are not used to store, but to compute values.
Stored Properties
editThese properties are a part of an instance of a class or a structure. It can have a variable or a constant value.
struct Animal{
let name: String
let legs: Int
var weight: Double
}
var sloth = Animal(name: "slothy", legs: 4, weight: 8.5)
print("Hi my name is \(sloth.name) and i have \(sloth.weight) kilos!")
sloth.weight = 9.2
print("Put one some weight... now i have \(sloth.weight) kilos :) !")
Computed Properties
editThese properties provide getters and setters to retrieve or set the value of a variable.
In this example, the structure Circle
has a computed property called diameter. It has a getter which returns the doubled value of the radius. The setter changes the value of the radius to the half of the new diameter
. area
is a read-only property, which means it does not have a setter.
struct Circle {
var radius: Double
var diameter: Double{
get {
let diameter = radius * 2
return diameter
}
set(newDiameter){
radius = newDiameter/2
}
}
var area: Double{
get{
return radius * radius * Double.pi
}
}
}
var smallCircle = Circle(radius: 3)
let initialDiameter = smallCircle.diameter
smallCircle.diameter = 10
print("The circle now has a diameter of \(smallCircle.diameter), a radius of \(smallCircle.radius) and a area of \(smallCircle.area)")
// prints "The circle now has a diameter of 10.0, a radius of 5.0 and a area of 78.53"
Property Observers
editProperty observers can be used to observe the state of a property and respond to changes. They are called whenever the value of a property is set. There are two types of observers. willSet
is called before a value is stored, didSet
is called after a value is stored.
class EctsCounter{
var ectsCount: Int = 0{
willSet(newCount){
print("About to set your count to \(newCount) points!")
}
didSet{
print("Added \(ectsCount - oldValue) points!")
}
}
}
let counter = EctsCounter()
counter.ectsCount += 10
// About to set your count to 10 points!
// Added 10 points!
counter.ectsCount += 4
// About to set your count to 14 points!
// Added 4 points!
Concurrency with GCD
editThe dispatch framework includes a lot of language features, runtime libraries and system enhancements which improve the support for concurrent code execution on hardware with more than one core. The Grand Central Dispatch (GCD) submittes work to dispatch queues managed by the system.
Concurrency vs. Parallelism
editParallelism is the term which describes the concept of executing two or more threads at the same time on multi-core processors. Devices with a single core can achieve concurrency with time-slicing. This is the process of switching between multiple threads through context switches. GCD manages a pool of threads. Codeblocks can be added to dispatch queue and the GCD decides what to execute.
Queues
editDispatchQueue represents the dispatch queues provided by GCD[1]. The tasks, which you submit to the queues, are executed in a FIFO order, which means the first submitted task will always be the first one that gets started. There are two types of queues. Serial queues can only execute one task at any time. Concurrent queues can start multiple tasks at the same time. They are started in the order they were added and can finish in any order. The scheduling is managed by GCD, which means it controls when tasks are started. GCD provides a main queue, which is a serial queue and runs on the main thread. Tasks on the main queue are executed immediately. It is good practice to put all tasks, which change or update the UI, on the main queue. This makes sure the UI is always responsive. The four global queues are concurrent, shared by the entire system and divided in priorities - high, default, low and background. Queues can also be created by the user and can be serial or concurrent.
The priority is not specified directly, but by using Quality of Service (QoS) classes.
.user-interactive
describes tasks which have to be done immediately, because otherwise the user experience would be bad. This QoS is used for UI updates and event handling.
.user-initiated
represents tasks, which can be performed asynchronously and are started from the UI. These tasks are mapped into the high priority queue, because it is used when users are waiting for immediate results or tasks require user interaction to continue.
.utility
represents long-running tasks. This class is used for I/O, networking and computations. Usually a progress indicator is used to make sure the user knows something is going on.
.background
describes tasks, which the user usually is not aware of. It is used for tasks that do not need user interaction and are not time-sensitive, for example maintenance or prefetchting.
Using queues in iOS
editThe following snipped can be found on Github and can be imported as Xcode project.
As soon as the "Start" button is pressed, the function download(size: Int, label: UILabel, timeout: Int)
is called twice with different input parameters. After they are put onto a global queue they asynchronously execute the simulation. After each iteration the amount is increased by 1.
Next, the label which displays the progress of the download has to be updated. To do this, it is necessary to put the task back on the main queue, which makes sure that it is executed immediately. After a short timeout the next iteration starts.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var task1: UILabel!
@IBOutlet weak var task2: UILabel!
@IBAction func start(_ sender: UIButton) {
download(size: 70, label: task1, timeout: 10)
download(size: 50, label: task2, timeout: 7)
}
func download(size: Int, label: UILabel, timeout: Int) -> Void{
DispatchQueue.global(qos: .userInitiated).async {
// puts the download simulation on a global queue
var amount = 0
for _ in stride(from: 0, to: size, by: 1){
amount += 1
DispatchQueue.main.async {
/* All actions which change the UI have to be put back on the main queue. */
label.text = String(amount)
}
// sets a timeout
usleep(useconds_t(timeout*10000))
}
}
}
}
Error Handling
editThere are a lot of situations where errors can occure during the execution of your code. For example, when trying to read a file from your harddisk errors can occur due to missing permission or non-existing files. Swift provides couple of ways[2] to deal with errors during code execution.
- Errors in a function can be propagated to the code calling the function
- do-catch statements
- optional values
- assert the error won't occur
Propagating Errors with throwing Functions
editThe keyword throws
is used in a function's declaration after the parameterlist. This function is then called a "throwing function".
In the example below, an enum is used to define two types of possible errors. Whenever the makeCoffee()
function is called, the number of beans in the machine is decreased and a counter is increased. As soon as the beans are empty, an outOfBeans
error is thrown. If a certain number of cups was served, the needsMaintenance
error is thrown.
enum CoffeeMachineError: Error {
case outOfBeans
case needsMaintenance
}
class CoffeeMachine{
var beans = 20
var count = 1
func makeCoffee() throws{
if(count < 6){
if(beans > 0){
print("Enjoy your cup of coffee!")
beans -= 1
count += 1
} else{
throw CoffeeMachineError.outOfBeans
}
} else{
throw CoffeeMachineError.needsMaintenance
}
}
}
var machine = CoffeeMachine()
for _ in stride(from: 0, to: 7, by: 1){
try machine.makeCoffee()
}
Do-Catch
editDo-Catch statements are used to execute different statements depending on what type of error is thrown. For example, here a needsMaintenance
error is caught, the machine asks for maintenance and the counter is set back to 0.
var coffeeMachine = CoffeeMachine()
for run in stride(from: 0, to: 25, by: 1){
do{
try coffeeMachine.makeCoffee()
} catch CoffeeMachineError.outOfBeans{
print("Out of Beans!")
} catch CoffeeMachineError.needsMaintenance{
print("Machine needs Maintenance!")
// Machine is maintained, counter gets set back to 0
coffeeMachine.count = 0
}
}
Converting Errors to Optional Values
editWith the try?
keyword, an error is converted to an optional value. The value of an expression is nil
whenever an error is thrown during the execution. In the snippet below, an error is thrown as soon as the digit that should be returned is no longer greater than zero.
enum DigitError: Error{
case outOfDigitsError(String)
}
var currentDigit = 9
func getDigit() throws -> Int {
if(currentDigit > 0){
let tmp = currentDigit
currentDigit -= 1
return tmp
}
else{
throw DigitError.outOfDigitsError("Sorry, no digits left...")
}
}
for _ in stride(from: 0, to: 10, by: 1){
if let digit = try? getDigit(){
print(digit)
}
}
Accessing data from Sensors
editApple's mobile devices include a lot of sensors, including an Accelerometer, a Barometer, an Ambient light sensor and a Pedometer. iOS developers can access these sensors data in their projects with Swift.
Pedometer
editAs an example, the snippet below shows how the Pedometer can be accessed and how data can be retrieved[3]. This sensor is used for instance to count a persons steps. This project can be downloaded from GitHub.
import UIKit
import CoreMotion
@IBDesignable
class ViewController: UIViewController {
@IBOutlet weak var stepsDisplay: UILabel!
@IBOutlet weak var stepsToComplete: UILabel!
let calendar = Calendar.current
let todayDate = Date()
var stepsToday: Int = 0
let pedometer = CMPedometer()
@IBInspectable
var targetSteps = 10000
func getStartOfDay(from date: Date) -> Date{
return Calendar.current.startOfDay(for: date)
}
func handler (_ data: CMPedometerData?, _ error: Error?) -> Void{
let steps = data?.numberOfSteps
stepsToday = steps as! Int
DispatchQueue.main.async(execute: {
// puts the closure into the Main Queue so it will be executed immediatly
self.stepsDisplay.text = String(self.stepsToday)
self.stepsToComplete.text = String(self.getStepsToGoal(target: self.targetSteps, actual: self.stepsToday))
})
}
func getStepsToGoal(target steps: Int, actual count: Int) -> Int{
return steps - count
}
@IBAction func getSteps(_ sender: UIButton){
if CMMotionActivityManager.isActivityAvailable(){
//checks if Activity Data is available
pedometer.queryPedometerData(from: getStartOfDay(from: todayDate), to: todayDate, withHandler: handler)
//queries data from the pedometer.
}
}
}
Unit Testing
editIn this section we will have a look at how simple unit tests can be implemented in Swift[4]. For this purpose a simple class with three functions will be tested. The class contains variables for two integer values and three functions which can add, subtract or multiply those values.
import Foundation
class Calculator {
var a: Int
var b: Int
init(a:Int, b:Int){
self.a = a
self.b = b
}
func add(a:Int, b:Int) -> Int {
return a + b
}
func sub(a:Int, b:Int) -> Int {
return a - b
}
func mul(a:Int, b:Int) -> Int {
return a * b
}
}
Next, let's have a look at how this class can be tested. First of all we have to import the XCTest
testing framework and the Calculator. In the test functions testAdd()
, testSub()
and testMul()
an instance of the Calculator class is used to call the methods add, subtract and multiply to compare the result to an expected value.
import XCTest
@testable import Calculator
class CalculatorTests: XCTestCase {
let calc = Calculator(a:0, b:0)
override func setUp() {
// called before every test method
super.setUp()
}
override func tearDown() {
// called at the end of every test method
super.tearDown()
}
func testAdd() {
XCTAssertEqual(calc.add(a: 1, b: 1), 2)
XCTAssertEqual(calc.add(a: 1, b: 2), 3)
XCTAssertEqual(calc.add(a: 5, b: 4), 9)
}
func testSub(){
XCTAssertEqual(calc.sub(a: 5, b: 2), 3)
XCTAssertEqual(calc.sub(a: 3, b: 3), 0)
XCTAssertEqual(calc.sub(a: 6, b: 7), -1)
}
func testMul(){
XCTAssertEqual(calc.mul(a: 2, b: 4), 8)
XCTAssertEqual(calc.mul(a: 9, b: 9), 81)
XCTAssertEqual(calc.mul(a: 0, b: 4), 0)
}
}
References
edit- ↑ Kodeco | 2017 | Grand Central Dispatch Tutorial | [online][accessed: 18.09.2017] | https://www.kodeco.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2
- ↑ Apple Inc. | 2017 | Error Handling | [online][accessed: 18.09.2017] | https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508
- ↑ Apple Inc. | 2017 | [online][accessed: 18.09.2017] | https://developer.apple.com/documentation/coremotion/cmpedometer
- ↑ Nolan, G. (2017), Agile Swift: Swift Programming Using Agile Tools and Tech- niques, Springer Science+Business Media New York, New York
Conclusion
Conclusion
editOpen Issues
editThis book includes many of the most important concepts of Swift. It explains the basics like datatypes, control flow and collection types. It also deals with advanced topics like concurrency and unit testing. Of course there are a lot of other language features which are not described here, for instance inheritance, generics and protocols.
Consequences and Suggestions
editTo take advantage of all language features I recommend reading Apple's Language Guide[1] which describes all basic features as well as a lot of advanced topics. Moreover, I can suggest video blogs or video courses from Stanford University . There you can find a lot of free tutorials for iOS development.
References
edit- ↑ Apple Inc. | 2017 | Swift programming language | [online][accessed: 18.09.2017] | https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-ID0