Learn Swift by Examples Beginner Level
Learn Swift by Examples Beginner Level
Learn Swift by Examples Beginner Level
Edition: 1
First published: 1.0, January 2022 (planned)
This edition: 1.0, June 2021 (early access)
Build number: 202106251400
While the author has used good faith efforts to ensure that the information and
instructions contained in this work are accurate, the author disclaim all responsibility
for errors or omissions, including without limitation responsibility for damages
resulting from the use of or reliance on this work. Use of the information and
instructions contained in this work is at your own risk. The author makes no warranty,
express or implied, with respect to the material contained herein.
If any code samples, software or any other technology this work contains or describes
is subject to open source licenses or the intellectual property rights of others, it is your
responsibility to ensure that your use thereof complies with such licenses and/or
rights.
All trademarks and registered trademarks appearing in this book are the property of
their own respective owners.
Release Notes
Edition 1.0
release: June 2021
• List of topics and terms (N)
• Chapter 2: Variables and functions (A) [add escaping closures]
• Chapter 9: Protocols and generics (A) [add more examples]
• Chapter 10: Miscellaneous topics (A) [add case-let pattern]
Edition 1.0
release: May 2021
• Chapter 7: Code completion (A) [add solutions to tasks]
• Chapter 9: Protocols and generics (A) [add image, add new subsections]
Edition 1.0
release: April 2021
• Chapter 1, Section 1: Battleship game (N)
• Chapter 6: Properties, dictionaries and sets (N)
• Chapter 7: Code completion (N)
• Chapter 8: Structures, inheritance and errors handling (N)
• Chapter 9: Protocols and generics (N)
• Chapter 10: Miscellaneous topics (N)
Edition 1.0
release: March 2021
• Preface (N)
• Chapter 1: Initial steps (N)
• Chapter 2: Variables and functions (N)
• Chapter 3: Arrays and enumerations (N)
• Chapter 4: Type methods, guards and string interpolations (N)
• Chapter 5: Tuples, switch and extensions (N)
Table of contents
Preface ......................................................................................................xv
Initial steps ..............................................................................................23
Battleship game ...............................................................................25
Create project ..................................................................................29
Variables and functions ...........................................................................37
Variables..........................................................................................39
Optionals .........................................................................................49
Functions .........................................................................................57
More about functions – closures .....................................................71
Arrays and enumerations ........................................................................87
Arrays ..............................................................................................89
Enumerations ................................................................................101
Range operators ............................................................................107
Game code – add initializer ..........................................................109
Type methods, guards and string interpolation.....................................115
Type methods .................................................................................117
Guards ...........................................................................................123
String interpolation .......................................................................129
Game code .....................................................................................133
Tuples, switch and extensions ...............................................................137
Tuples ............................................................................................139
switch - case statement .................................................................143
Extensions .....................................................................................149
Game code......................................................................................157
vii
viii
ix
x
List of topics and terms
array force unwrap (!)
- predefined size function (see also: method)
- concatenate - anonymous function
- range - argument label
- iterate over - default parameter
case - in-out parameter
- bind values - nested function
- case-le - parameter name
- for-case-le - return implicit
- guard-case-le - throwing function
- if-case-le
- type
- pattern matching
- unwanted result (_)
- switch-case
- variadic parameter
class generic
- access control guard
- class method (type method) inheritance
- final initializer
- function (method, action) instance method 114
- instance method (action)
- instance method - class (type) method
- object - instance method
closure
- mutating method
- trailing closure - override
- escaping closure (@escaping) operator
constant
- closed range operator (...)
defer (see also: error) - half-open range operator (..<)
dictionary optional
enumeration (enum)
- binding
- enumeration associated values - implicitly unwrapped optionals (!)
error - multiple optional chaining
- throw - nil-coalescing operator (??)
- do-catch polymorphism
extension property (field)
fall through
- computed property
xi
xii
xiii
xiv
Preface
Early access
This book is a work in progress, presented in early access version. Early
access allows to publish and share some ideas before the final version
appears. This way, participating in an early access, you may contribute
xv
how the final version will look like. English is not my native language
and I know that I make a lot of mistakes but I hope that text is more
than readable and at least a little bit understandable. I believe that
everything can be better and there is always a space for improvements. I
can say that the Toyota Way is the way I live and work focusing
on continuous improvement, and respect for people. That is
why I would be very grateful if you somehow contribute improving this
book. Any comments, corrections and suggestions are more than
welcome. I write this book not for myself but to share what I know with
others, so help me make it’s contents better.
I want this book to be available for free in full version. However if you
want to financially support me you can choose a paid option.
I need money to finish it and to make it better. Everything costs. The
most precious is time. I, as all other people, have to work to live and to
support my family. And this consumes most of my days. Then I have a
choice: play with kids or write a book. I choose playing with kids. I don’t
want to reduce a time spent with my family because I don’t want to be a
virtual parent. So I have just a little time for book. Paying for a book you
allow me to reduce my job engagement (from full-time to half-time) and
spending more time on book without sacrificing my family life.
Moreover, having money I can pay for professional translation, text
correction or simply buy better images.
I believe there is no book like this on the market and I want to make it
better and better. I can do this. I don’t have to ask publisher if they
agree to prepare another version. If something deserves for
improvement I simply do this and publish right after that.
xvi
Preface This is what you are reading right now. Here I explain what
you can expect in this book. I also try to convince you to actively
participate in shaping it’s contents.
Chapter 1 Initial steps. Battleship game is a simple game but there are
few variants so I will present rules you have to preserve during
implementation. Next you will create a project. In this chapter you will
learn how to create a project.
Chapter 2 Variables and functions. You will learn about the most
fundamental bricks of every code: constants, variables and functions.
You will also get knowledge about optional types and create your first
class "stub".
xvii
xviii
Final word
If some part of text or code is not clear and you have problems to
understand it, please let me know. I will try to rewrite it to make it more
clear. Remember: this book is written by me for you not just for
my complacency.
Give this book a try, and please let me know what you think. Any
feedback is very much encouraged and welcomed! If you think that my
time is worth this effort, you can support what I’m doing now and help
me finalize this project. Please use email (book@fulmanski.pl) or
GitHub (https://github.com/fulmanp/Learn-Swift-by-examples-
beginner/issues) to give your positive or negative, but in all cases
constructive, feedback.
Piotr Fulmański
xix
Italic
Indicates new terms.
Italic
Indicates old terms but for some reason I want to distinguish them from
normal text flow, definitions, citations.
Constant width
Indicates filenames, file extensions, text of Xcode messages.
Constant width
Indicates commands or any other text that you should type literally (as
it is given), for example text filed input.
Constant width
Indicates anything which is related to source code but is placed inline.
Bold
Indicates application names, menu. It also indicates statements which
you need to pay special attention to.
xx
N OTE
Note block
xxi
xxii
CHAPTER 1
Initial steps
Battleship game is a simple game but there are few variants so I will
present rules you have to preserve during implementation. Next you
will create a project.
24
SECTION 1
Battleship game
Showing you all Swift's concept I try to wrap them in one consistent
form. I decide to build my narration around simple game – simple
enough not to have to explain complex rules and at the same time giving
enough flexibility to be able to show as much as possible.
Before play begins, each player secretly arranges their ships on their
primary grid. Each ship occupies a number of consecutive cells on the
grid, arranged either horizontally or vertically. The number of cells for
each ship is determined by the type of the ship. The ships cannot
overlap (i.e., only one ship can occupy any given cell in the grid) or
touch themselves (there must be minimum one cell separating two cells
25
Correct Incorrect
1 1
1234567890 1234567890
1..XX.....X 1D......X..
2.......... 2D.........
3.XX..X.... 3.EEE......
4.....X.X.. 4.....AA..X
5...X.X.... 5.....A.....
6...X.X..X. 6.....A.....
7...X...... 7.X........
8.......... 8......B&..
9XX....XXX. 9F......C..
10...X...... 10FG.....C..
• Ship B is of size 3 and overlaps (cell marked with & character) with
ship C of size 3.
After the ships have been positioned, the game proceeds in a series of
rounds. In each round, each player takes a turn to announce a target cell
in the opponent's grid which is to be shot at. The opponent replies
whether or not the cell is occupied by a ship and in consequence wether
it is a hit or miss. Both players mark result (hit or miss) on their
26
respective grids. The attacked player do this to know how many ships
are still under his or her command. The attacking player marks shot
results, in order to build up a picture of the opponent's fleet.
When all of the cells of a ship have been hit, the ship's owner announces
its sinking. If all of a player's ships have been sunk, the game is over and
their opponent wins.
27
28
SECTION 2
Create project
Creating a project is quite simple and involves just a few steps. To play
with Swift you have to use right tool. If you have an Apple computer,
Xcode application is a right choice.
29
30
4. Now you have to select location where you want to save your project:
31
If you want to keep the code under source control, check Create Git
repository on my Mac.
This was the last step and main Xcode window will appear:
32
Sometimes firs run takes more time than you expect. Please be patient
and be sure that Console Output window is displayed in Debug
Area. If not, it can be turn on with a button located in the right-bottom
corner of a main window. It’s the last icon on the lower right side of the
panel:
Hello, World!
Program ended with exit code: 0
33
That's all. Now your project is ready and you can work on it.
34
35
36
CHAPTER 2
You will learn about the most fundamental bricks of every code:
variables and functions.
38
SECTION 1
Variables
39
40
Then select macOS and Swift File and accept it pressing Next button.
Press Create to create the file. Initial contents of this file is almost
empty except import declaration:
import Foundation
Import lets you have an access to symbols that are declared in another
module/library/framework (different names are used). The
Foundation is one of the fundamental framework. It provides a base
layer of functionality for apps and other frameworks, including data
storage and persistence, text processing, date and time calculations,
sorting and filtering, and networking.
41
class Board {
class Board {
private let rows, cols: Int
private var board: [[CellType]]
}
Is's worth to note that in Swift the term instance is preferred over the
term object, mostly because it is right both in case of instances of
classes, typically in other programming languages named an objects,
and in case of instances of structures.
42
This two simple "additions" defining three properties: rows, cols and
board, require a lot of explanations to understand what you are doing
and to fix errors which you can see know:
According to Xcode's errors, there are two problems you have to solve.
First error is related to properties (variables) you have in Board class –
Swift doesn't have all the information it needs to correctly instantiate
them. To fix this you have to either provide explicite values for your
variables or provide a special method called initializer (known as
constructor in other programming languages) with code initializing
variables. Second error is related to unknown CellType type. To fix
both errors you have to:
43
As you can see, at the beginning there is a lot to learn. Please don't run
away and let me explain all those topics in a simple words. Please, give
me a chance!
class Board {
//private let rows, cols: Int
//private var board: [[CellType]]
}
44
Remarks:
• Both variables and constants are called field or property if they are
used in a class (for example, rows is a property of Board class).
Every object in Swift must have a type and this type must be known
during compilation. Types in Swift fall into one of two categories:
• reference types, where instances share a single copy of the data, and
the type is usually defined as a class.
From this code Swift knows that both rows and cols are of integer
type (Int) while board is a two-dimensional array (because of double
square brackets pairs [[]]) of objects of type CellType.
45
var foo = 1
In Swift every object must have a value. The following code possible to
compile in C will not compile in Swift (I skip here the matter of different
syntax):
#include <stdio.h>
int main() {
int x;
printf("%d", x);
return 0;
}
var x
print(x)
46
47
48
SECTION 2
Optionals
As you know from previous section, every object must have a value or
must be explicitly defined as optional which is a way to indicate that this
object may not have a value. You set an optional variable to a valueless
state by assigning it the special value nil. In Swift, nil is used in case
of the absence of a (correct) value of a certain type. You may say, that
nil is a value which tells that there is no correct value. If it sounds a
little bit crazy, recall NaN "number" which is a sequence of bits
interpreted not as a number but as incorrect (non-existing) numeric
value. Optionals of any type you want can be set to nil, not just object
types. Conversely, nil cannot be used with non-optionals. If a variable
(rather constant) in your code needs to take no value, you have to
declare it as an optional value of the appropriate type.
DO NOTHING
Do nothing and treat optional as any other type:
49
print(thisMayBeEmpty)
thisMayBeEmpty = 1
print(thisMayBeEmpty)
thisMayBeEmpty = nil
print(thisMayBeEmpty)
nil
Optional(1)
nil
Probably this is not what you want, as you have Optional(1) instead
of 1. Moreover, a warning message is displayed: Expression
implicitly coerced from Int? to Any which is a clear signal
that something is not used correctly.
FORCE UNWRAP
If you are sure (but you have to be sure if you don't want to crash your
application) that optional contains a non-nil value, you can force
unwrap its value with an exclamation mark ! added at the end of the
optional variable's name:
print(thisMayBeEmpty!)
if (thisMayBeEmpty != nil){
print("Have some nonempty value: " + String(thisMayBeEmpty!))
} else {
print("Nothing to print")
}
50
Notice that for nonempty you don't have to use an exclamation mark.
You can do even more: you can chain together multiple optional
bindings, and the entire chain fails gracefully if any link in the chain is
nil:
number1 = 2
number2 = nil
51
a != nil ? a! : b
in contrast to:
implicitOptional = 1
print(implicitOptional)
Implicitly unwrapped optional are fine if you know what you are doing.
They require you to be absolutely sure there is a non-nil value before
you use it. If you try to use a value that contains nil, your application
will crash. You can’t catch the error and you can’t stop it from
happening: your code will crash immediately.
52
2. How it is now
Today things has changed. When you use an exclamation mark ! your
code will compile but you will get a warning: Coercion of
implicitly unwrappable value of type 'Int?' to 'Any'
does not unwrap optional along with number of possible fixes:
The warning comes from the way modern Swift handles IUO’s. See
[IOU:1-2] for the details. According to those documents, the appearance
of ! at the end of a property or variable declaration’s type no longer
indicates that the declaration has IUO type; rather, it indicates
that
You can read Int! as: this value has the type Optional<Int> as it
would have in case of Int? declaration but also carries additional
information saying that it can be implicitly unwrapped if needed”.
Example 2.2.1
class A {
53
var n: Int? = 1
}
class B {
var a: A? = A()
}
let b: B? = B()
print(b?.a?.n ?? 0)
Please note:
• The following will not compile as compiler will not implicitly unwrap
optionals:
print(b.a.n)
Instead you will see the error: Value of optional type 'B?'
must be unwrapped to refer to member 'a' of wrapped
base type 'B'
Example 2.2.2
class A {
var n: Int! = 1
}
class B {
var a: A! = A()
}
let b: B! = B()
print(b.a.n)
54
Please note:
• In statement:
print(b.a.n)
print(b!.a!.n)
print(b.a.n!)
or
print(b.a.n ?? 0)
print(b?.a?.n)
55
56
SECTION 3
Functions
57
10. If a parameter has an argument label, it must be used when you call
the function.
11. Although arguments have their labels, you cannot change arguments
order.
func functionSimplestForm(){
print("functionSimplestForm")
}
functionSimplestForm()
If you want to test it, you can simply paste this code into main.swift
file:
import Foundation
func functionSimplestForm(){
print("functionSimplestForm")
}
functionSimplestForm()
functionSimplestForm
58
We may also explicitly define, with Void keyword following right arrow
->, that function returns no value:
but this is not Swifty style. Remember: in Swift type only what is really
necessary. If your function returns something, for example String,
this must be specified:
print(functionReturningString())
Of course if you want, you can type it but remember: in Swift type only
what is really necessary:
59
If you missing argument label param1 in call, you will get an error:
Missing argument label 'parameter1:' in call.
func functWithMultipleParams(
param1: String,
param2: String
) -> String {
return "Parameter value: " + param1 + " : " + param2
}
Notice how I format source code lines which are too long. Of course it is
not a rule but it is a good habit to keep your code neat, clean and
consistent.
60
The following example shows how you can use tuples to return two
values (I explain you tuples in Chapter 5: Tuples, switch and extensions,
Section 1: Tuples):
func functionWithMultipleReturnValues(
val1: Int,
val2: Int
) -> (sum: Int, product: Int) {
let sum = val1 + val2
let prod = val1 * val2
func functionLabelsTest(
argumentLabel parameterName: String, // 2
parameterWithDefaultLabel: String, // 5
_ parameterWithNoArgumentLabel: String, // 8
justALabel parameter1: String, // 6, 7
justALabel parameter2: String // 6, 7
) {
print(
parameterName + ":" + // 4
61
parameterWithDefaultLabel + ":" + // 4
parameterWithNoArgumentLabel + ":" + // 4
parameter1 + ":" + parameter2) // 4
}
functionLabelsTest(
argumentLabel: "first", // 3, 10
parameterWithDefaultLabel: "second", // 5
"third", // 9
justALabel: "fourth", // 3, 10
justALabel: "fifth") // 3, 10
You may ask, why do we have both argument label and parameter
name? My answer is: to make developers life easier and to keep your
code neat, clean and consistent. Argument label is for callee – it should
be descriptive so the person who calls function knows for what
argument is used, what it should be etc. Notice that correct function
name in Swift contains both function name and all argument labels. So
functionLabelsTest
functionLabelsTest(argumentLabel:parameterWithDefaultLabel:_:ju
stALabel:justALabel:)
62
Without any example it may still sound strange, so below I'm giving one
wich will clarify the case:
greeting
nor
greeting(who:)
but
greeting(for:)
For me, the last one sounds best and this is why in Swift you may use
two different names for the same thing: first is used outside and should
"sounds good" while second is visible only internally.
N OTE
Argument or parameter?
63
#include <stdio.h>
main(int argc, char *argv[])
{
...
}
As you can see there is no paramc but argc and not paramv but
argv.
Naming the first parameter argc isn't a mistake or error. At run time
the value you use is an argument. We reserve the term parameter for
situations when discussing subroutine definitions.
Swift elegantly solves this naming problem using both argument label
and parameter name.
64
func fooFunction(
noDefaultParameter: String,
defaultParameter: String = "defaultValue"
) {
return noDefaultParameter + " " + defaultParameter)
}
fooFunction(noDefaultParameter: "mustBeDefinedByUser")
fooFunction(
noDefaultParameter: "mustBeDefinedByUser",
defaultParameter: "alsoDefinedByUser")
If you have default parameters, you can omit any default parameter you
want:
func funcWithDefaults(
param1: Int = 2,
param2: Int = 3
) {
print(param1 * 3 + param2 * 5)
}
funcWithDefaults()
// Prints: 2 * 3 + 3 * 5 = 6 + 15 = 21
funcWithDefaults(param1: 4)
// Prints: 4 * 3 + 3 * 5 = 12 + 15 = 27
funcWithDefaults(param2: 5)
// Prints: 2 * 3 + 5 * 5 = 6 + 25 = 31
VARIADIC PARAMETERS
A variadic parameter accepts zero or more values of a specified type.
You use a variadic parameter to specify that the parameter can be
65
The values passed to a variadic parameter are made available within the
function’s body as an array of the appropriate type. For example, a
variadic parameter with a name of numbers and a type of Int... is
made available within the function’s body as a constant array called
numbers of type [Int]. I will explain you arrays in Chapter 3: Arrays
and enumeration, Section 1: Arrays, but I hope the following example
will. be intuitively understandable for you.
Note that the first parameter that comes after a variadic parameter must
have an argument label to avoid any ambiguity which arguments are
passed to the variadic parameter and which arguments are passed to the
parameters that come after the variadic parameter.
func calculate(
sumOf numbersSequence1: Int...,
productOf numbersSequence2: Int...,
multipliedBy multiplier: Int
) -> (sum: Int, product: Int) {
var sum = 0
for number in numbersSequence1 {
sum += number
}
var product = 1
for number in numbersSequence2 {
product *= number
}
66
// Prints:
// 30
print(result.product)
// Prints:
// 12
IN-OUT PARAMETERS
Function parameters are constants by default. Trying to change the
value of a function parameter from within the body of that function
results in a compile-time error. If you want a function to modify a
parameter’s value, and you want those changes to persist after the
function call has ended, define that parameter as an in-out parameter
instead.
var x = 3, y = 4
swap(integer: &x, withInteger: &y)
print(x)
// Prints: 4
print(y)
// Prints: 3
UNWANTED RESULTS
Sometimes, this is rare but may happen, you may not need result
returned by a function:
67
print(newValue)
return newValue
}
doSomethingWithInteger(arg: 3)
If you leave code as it is given above, you will have to tolerate annoying
warning: Result of call to
'doSomethingWithInteger(arg:)' is unused. This warning is
a valuable information for developer: This function returns something
but you don't use it. Maybe this is OK, but maybe you forget about
something. If you really don't wan't to use result, you have to tell it to
Swift with underscore mark _:
_ = doSomethingWithInteger(arg: 3)
You can treat it as a way to say: I don't care about this value.
I will discuss them in the next section. If it is first time you read about
Swift you may consider those topics as too complicated. You may safely
skip next chapter now and return when you will be ready to know more
about functions.
68
69
70
SECTION 4
At first read you may safely skip this chapter and return when you will
be ready to know more about functions.
Functions as type
Every function in Swift has a specific function type consisting of the
parameter types and the return type of the function.
func foo(){
// Some code goes here
}
71
return x + y
}
action = add
print(add(x: 3, y: 5))
// Prints: 8
print(action(3, 5))
// Prints: 8
You can use function type as a parameter type for another function:
func doSomethingWithTwoInts(
_ int1: Int,
_ int2: Int,
_ action: (Int, Int) -> Int
) {
let result = action(int1, int2)
print(result)
}
doSomethingWithTwoInts(3, 5, add)
// Prints: 8
An interesting question is: How you can use a function type as the
return type of another function? An answer: As any other existing type.
For example, the function selectAction(_) defined as:
func selectAction(
_ decision: String = "add"
) -> ((Int, Int) -> Int) {
switch decision {
case "sub":
return sub
default:
72
return add
}
}
returns a function of type (Int, Int) -> Int. You can use this
function as it is showed below:
action = selectAction()
print(action(3, 5))
// Prints: 8
// or
print(selectAction()(3, 5))
// Prints: 8
Nested functions
Swift allowed you to do more with functions: it can be seen as strange
and awkward but you can define functions inside the bodies of other
functions – this way you have so called nested functions to distinguish
from previously discussed global functions. Nested functions are hidden
from the outside, but of course can be called by and used by enclosing
function. An enclosing function can also return one of its nested
functions which allow the nested function to be used in another scope.
func selectAction(
_ decision: String = "add"
) -> ((Int, Int) -> Int) {
switch decision {
case "sub":
return sub
default:
return add
}
73
action = selectAction("sub")
print(action(4,5))
// Prints: -1
Closures
Closures are self-contained blocks of code that can be passed around
and used in your code. Closures in Swift are similar to blocks in
Objective-C and to lambdas in other programming languages. In short,
closures are Swift’s anonymous functions.
GENERAL IDEA
To understand how it works, let's analyze the following example:
func foo() {
var x=1 // 2
func nested() -> Int { x += 1; return x } // 3
74
f = nested // 4
let result = f() // 5
print("Inside foo, call to f(): \(result)") // 6
}
foo() // 7
print("Call to f(): \(f())") // 8
foo() // 7
and in consequence:
also works?! Now you are outside of foo() function and you try to call
f(). How it could be that f() knows value of the x variable which is
visible only within the scope of foo() function? This is when closure
comes into play.
75
f = nested // 4
''saves'' function along with all the outer environment needed to execute
this function (in this case, x variable).
return innerFunction
}
print(moveFrom3By(7))
// Prints: 10
print(moveFrom5By(9))
// Prints: 14
76
BASIC USAGE
General closure expression syntax has the following form:
This syntax is very close to function syntax but without name (for this
reason we say about anonymous functions). The parameters can be in-
out, named variadic parameter and tuples. We cannot use default values
for them.
Having previously defined doSomethingWithTwoInts(_:_:)
function you can use it as follow:
doSomethingWithTwoInts(
5,
7,
{(a: Int, b: Int) -> Int in return a + b}
)
// Prints: 12
77
This is not the end, because even this short expression can be
shorthanded! In Swift a shorthand argument names $0, $1 and so on
can be used to refer to the values of the closure's first, second and so on
arguments. If the argument is a tuple $0.0 is a key, and $0.1 is a value
of the closure's first argument.
If you think that there is no option to write shorter expression, you are
wrong. You can use operator methods. From their definition Swift can
infer the number and the types of arguments and return value:
doSomethingWithTwoInts(5, 7, +)
If the closure is a final argument of a function you are going to use, and
the closure expression is long, you can write it as a trailing closure. A
trailing closure is a closure which is written after the function call's
parentheses (but it is still an argument to the function):
doSomethingWithTwoInts(5, 7){a, b in
a + b
}
or
78
In the example below you will see very basic but useful example of
closure usage. Please have in mind that in the code below you will create
two constants, which in some sense will behaves like variables. This is
because functions and closures are reference types. So event if we define
a constant related with a closure this relation is constant (doesn't
change) but the closure this constant refers to is still able to change
some captured variables.
return incrementer
}
print(incrementerBy_plus_2())
// Prints: 2
print(incrementerBy_minus_2())
// Prints: -2
print(incrementerBy_plus_2())
// Prints: 4
print(incrementerBy_minus_2())
// Prints: -4
print(incrementerBy_plus_2())
// Prints: 6
print(incrementerBy_minus_2())
// Prints: -6
MULTIPLE CLOSURES
It is perfectly legal for a function to take more than one closure:
func doSomethingWithTwoInts(
_ int1: Int,
_ int2: Int,
79
In this case you don't want to only execute some action on two integers
but also allow user to make an action in case of success or (what is more
important) any problems. When your function takes multiple closures,
you omit the argument label for the first trailing closure and you label
the remaining trailing closures. For example, if you implement divide
function:
80
"""
}
result = doSomethingWithTwoInts(
15,
0,
add,
onSuccess: nil,
onFailure: nil
)
print(result!)
// Prints:
// 15
ESCAPING CLOSURES
A closure is said to escape a function when the closure is passed as an
argument to the function, but is called after the function returns. By
prefix any closure argument with @escaping, you convey the message
to the caller of a function that this closure can outlive (escape) the
function call scope. By default a closure is non-escaping, and its lifecycle
end along with function scope.
81
func processWaitForResult(
data: ProcessingData,
action: (ProcessingData) -> ResultData
) -> ResultData {
let result = action(data)
return result
}
class ProcessingData {
// Some code goes here
var data = 0
}
class ResultData {
// Some code goes here
var data = 0
}
82
The question is: When closure escapes? One of the simples way that a
closure can escapes is by being stored from a function in a variable
that’s defined outside the function (batchQueue):
func addProcessingStep(
action: @escaping (ProcessingData) -> ResultData
) {
batchQueue.append(action)
}
for i in 1..<batchQueue.count {
let data = convertResultToData(result)
result = batchQueue[i](data)
}
return result
}
83
addProcessingStep(action: process1)
addProcessingStep(action: process2)
addProcessingStep(action: process3)
N OTE
84
Happily, having the knowledge from the current chapter, extended with
information from the next one, you will easily fix this.
85
86
CHAPTER 3
In this part you will create a very basic classes with properties,
initializers and methods. One of them you will use to represent game
board and another one to be an entry point to the whole game logic.
88
SECTION 1
Arrays
One-dimensional arrays
You declare or define one-dimensional array this way:
// Declare an array
var mutableArrayDeclaration: [String]
let immutableArrayDeclaration: [String]
With declaration you only say: One day this would be an array. With
definition you say: This is an array. If your array is defined, it exists,
you can add something to it:
// You can do
mutableArray1.append("Zero")
mutableArray1.append("One")
mutableArray1.append("Two")
// or
mutableArray2 = ["zero", "one", "two"]
// but can't do
//mutableArrayDeclaration.append("Zero")
89
// or
//immutableArray.append("Zero")
print(mutableArray1)
// Prints: ["Zero", "One", "Two"]
print(mutableArray2)
// Prints: ["zero", "one", "two"]
mutableArray1[1] = "NEW"
print(mutableArray1)
// Prints: ["Zero", "NEW", "Two"]
<?php
$array = array();
$array[5] = 3;
print_r($array);
/* Prints:
Array
(
[5] => 3
)
*/
?>
mutableArray1[5] = "NEW"
print(mutableArray1)
you will get a run time error: Fatal error: Index out of range.
If you remember what I said in the Chapter 2: Variables and functions,
Section 1: Variables about variables, you shouldn't be surprised: in Swift
every object must have a type and value. So initially mutableArray1
exists (is not nil) but has no values – is declared as an array of size 0.
When you append something, behind a scene, Swift creates new array
with size extended to be able to append new elements. In this sense it is
90
dynamic. But all the time it has strictly defined number of elements so
you cannot freely get access at any index you want; otherwise you will
"jump" out of the range of possible indices.
#include <stdio.h>
int main() {
int x[10];
x[5] = 4;
printf("%d\t%d", x[5], x[2]);
// Prints: 4 0
return 0;
}
You can't create an empty array of fixed, predefined size; instead you
can create an array of predefined size filled with specified element –
remember: in Swift every object must have a type and value:
mutableArray3[3] = "THREE"
print(mutableArray3)
// Prints: ["", "", "", "THREE", ""]
print(mutableArrayDeclaration)
// Prints:
// ["zero", "one", "two", "", "", "", "THREE", ""]
91
print(mutableArrayDeclaration)
// Prints:
// ["zero", "one", "two", "", "", "", "THREE", "", "FIVE",
"SIX"]
You can do even more – with subscript syntax you can change a range of
values at once, even it the replacement set of values has a different
length than the range we are replacing. The number of indices defined
by a range may be lower than number of elements you want to use:
In this case index range 1..2 define two indices: 1 and 2, while you try
to use array with three elements. In such a case Swift starts at first index
given by your range and, if necessary, extends array to put all new
values to a new array (previous array had 10 elements, while current has
11 – two elements was replaced by three, so there is one element more).
Opposite, the number of indices defined by a range may be greater than
number of elements you want to use:
mutableArrayDeclaration[4...6] = ["***"]
print(mutableArrayDeclaration)
// Prints:
// ["zero", "ONE", "TWO", "THREE", "***", "THREE", "", "FIVE",
"SIX"]
In this case index range 4...6 define three indices: 4, 5 and 6, while
you try to use array with only one element. In such a case Swift starts at
first index and replace all subsequent elements as long as there are
values in replacement array. Next all elements without defined
replacement are removed (previous array had 11 elements, while current
has 9 – three elements was replaced by one element, so there are two
elements less).
92
mutableArrayDeclaration.remove(at: 4)
print(mutableArrayDeclaration)
// Prints:
// ["zero", "ONE", "TWO", "THREE", "THREE", "", "FIVE", "SIX"]
93
array2DEx1.append(row1)
array2DEx1.append(row2)
array2DEx1.append(row3)
print(array2DEx1)
// Prints:
// [[11, 12, 13, 14], [21, 22, 23], [31, 32]]
array2DEx1[1].append(24)
print(array2DEx1)
// Prints:
// [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32]]
array2DEx1[1][1] = 0
print(array2DEx1)
// Prints:
// [[11, 12, 13, 14], [21, 0, 23, 24], [31, 32]]
94
array2DEx2[1][1] = 1
print(array2DEx2)
// Prints:
// [[0, 0], [0, 1], [0, 0]]
95
print(arrayCopy) // 5
// Prints "[1, 2, 3, 4, 5]"
• In the first line (line marked as 1) you define array of integers with five
elements.
• You prove it in line number three where you change value of array at
index 0.
• Now when you print array (line number four) you see [0, 2, 3,
4, 5], while printing arrayCopy (line number five) you see "initial"
values: [1, 2, 3, 4, 5].
In the second example I use a reference type to fill array with values:
class referenceType {
var value = 1
}
var array = [referenceType(), referenceType()]
var arrayCopy = array
96
array[0].value = 0
print(array[0].value)
// Prints: 0
print(arrayCopy[0].value)
// Prints: 0
array[0] = referenceType()
print(array[0].value)
// Prints "0"
print(arrayCopy[0].value)
// Prints "1"
Remember, Swift also makes a copy of an array every time you pass an
array as an argument of a function. This is something totally different
than you may know from for example C programming language. The
following code compiles and executes without any problems:
#include <stdio.h>
int main() {
int array[] = {1, 2, 3, 4, 5};
printf("%d\t", array[0]);
// Prints: 1
display(array);
printf("%d", array[0]);
// Prints: 0
return 0;
}
97
This means that if an array is sharing storage with other copies, the
first mutating operation on that array incurs the cost of
copying the array. An array that is the sole owner of its storage can
perform mutating operations in place.
98
99
100
SECTION 2
Enumerations
Enumeration (or simply enum) is a user defined data type, mainly used
to assign names to integer constants. The idea behind enums is to
replace all "magic numbers" – meaningless numbers, with descriptive
names. The names used in place of numbers make a program easy to
read and maintain, enables you to work with those values in a type-safe
way within your code.
• initializers;
• can be extended;
101
At the introductory level of this book, I will jump over all enumeration's
advanced features and will show you the most basic usage – as a
"mapper" from name to integer.
enum Planet {
case Mercury, Venus, Earth, Mars,
Jupiter,Saturn, Uranus, Neptune
}
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")
}
// Prints:
// Maybe one day you can live here
102
With enum associated values you can add additional details to your
enumeration:
enum Action {
case turnLeftDegree (Double),
turnRightDegree (Double),
makeForwardSteps (Int),
makeBackwardSteps (Int),
saySomething (String)
}
With this you can for example specify not only that you are going
forward, but also how many steps (Chapter 5: Tuples, switch and
extensions, Section 2: switch - case statement for more details about
switch-case):
switch currentAction {
case .turnLeftDegree(let degree):
print("Turn left by \(degree) degree")
case let .turnRightDegree(degree):
print("Turn right by \(degree) degree")
case .makeForwardSteps(let steps):
print("Make \(steps) step(s) forward")
case let .makeBackwardSteps(steps):
print("Make \(steps) step(s) backward")
case let .saySomething(text):
print("Say: \(text)")
}
103
You may extract each associated value as a constant (with the let
prefix) or a variable (with the var prefix) for use within the switch
case’s body. If you look carefully into above code, you will notice that I
put let in two different places. If you have more than one associated
value, then some of them may be extracted as constants while other as
variables. In such a case you have to put var or let prefixes before
corresponding name (in brackets):
104
105
106
SECTION 3
Range operators
• The closed range operator a...b defines a range that runs from a
to b, including both values. The value of a must not be greater than
b.
• The half-open range operator a..<b defines a range that runs from
a to b, but does not include b. The value of a must not be greater
than b. If the value of a is equal to b, then the resulting range will be
empty.
print(someArray[...2])
// Prints: ["one", "two", "three"]
print(someArray[..<2])
// Prints: ["one", "two"]
print(someArray[2..<4])
// Prints: ["three", "four"]
107
//print(someArray[2<..4])
108
SECTION 4
class Board {
private let rows, cols: Int
private var board: [[CellType]]
}
If you have both lines uncommented, you will see two errors:
Add at the end of class, before closing curly bracket }, enum type:
enum CellType {
case none, empty, hit, notAllowed, rescue, ship, shot
}
109
You define a new type CellType "hiding" under the names none,
empty, hit, etc integer values. Saying the truth you pay greater
attention to names than values.
• none – nothing, none cell should have this value; use to signal
unexpected problems;
• empty – empty cell; cell where you can put a ship or you can shot;
Now you will add initializer where you will give values to all properties
in your class (both constants and variables). Initializer is a class method
with reserved name init (you can put this method just after enum
CellType):
prepareBoard()
}
Notice:
110
• Defining board you add 2 to cols and rows – this is because a frame
enclosing all game fields (see Chapter 1: Initial steps, Section 1:
Battleship game).
• To have code clean, all other steps needed to initialize game board you
"delegate" to prepareBoard method which you should implement as
next to avoid this error: Cannot find 'prepareBoard' in
scope.
func prepareBoard() {
for i in 0...rows+1 {
board[i][0] = .notAllowed
board[i][cols+1] = .notAllowed
}
111
for i in 0...cols+1 {
board[0][i] = .notAllowed
board[rows+1][i] = .notAllowed
}
for r in 1...rows {
for c in 1...cols {
board[r][c] = .empty
}
}
}
At this step all errors are gone but nothing happens because you haven't
instantiated Board class – there is no variables of Board type.
func printBoard() {
Create new file, name it Engine.swift, and paste the following code:
import Foundation
class Engine {
private var boardPlayer: Board
private var boardOpponent: Board
func printBoards() {
112
print("PLAYER")
boardPlayer.printBoard()
print("OPPONENT")
boardOpponent.printBoard()
}
}
Final step
The final step is to create Engine object and call its printBoards
method. Paste the following code to main.swift file (comment all
other code, being an effect of previous tests and experiments, you may
have in this file):
import Foundation
You may now safely compile and run your code. You will see:
PLAYER
OPPONENT
Program ended with exit code: 0
which is not very spectacular result but, what is most important, fully
correct result.
113
114
CHAPTER 4
In this part you will start implement method printing game board.
116
SECTION 1
Type methods
111111
123456789012345
+++++++++++++++++
1+...............+
2+...............+
3+...............+
4+...............+
5+...............+
6+...............+
7+...............+
8+...............+
9+...............+
10+...............+
11+...............+
12+...............+
+++++++++++++++++
ss#*********111111 12 spaces
ss#123456789012345 3 spaces
ss+++++++++++++++++ 2 spaces
117
s1+...............+ 1 space
s2+...............+ 1 space
s3+...............+ 1 space
s4+...............+ 1 space
s5+...............+ 1 space
s6+...............+ 1 space
s7+...............+ 1 space
s8+...............+ 1 space
s9+...............+ 1 space
10+...............+ 0 spaces
11+...............+ 0 spaces
12+...............+ 0 spaces
+++++++++++++++++
To make it, you need a method determining the number of digits needed
to print the largest row number so you could correctly compute the
number of spaces printed in the place of s characters. Such a method,
you may name it determineNumberOfDigits, is not a strict method
of Board class – it is rather universal method which may be used by
many other classes or methods. This is why you will put its code in
separate class where you will "collect" all helper or useful method which
don't belong to only one class. Create the Utils class with the frame of
our method
class Utils {
class func determineNumberOfDigits(number: Int) -> Int {
[... PUT METHOD CODE HERE ...]
}
}
118
many different types and is not something typical for board, ship or any
other battleship game object but rather for integer numbers, no matter
where they are used. That is why separating code of this method in a
versatile class collecting different utility methods seems to be
reasonable.
class Transformer {
var x = 5
To use it, you simply have to create its instance and then call a method
on this instance:
You may want to make this class more versatile, so you add another
method:
print(transformer.calculateDoubleValue(ofNumber: 7))
119
It's not bad but a bit weird. To transform number 7 you have to create
an object storing other values. You do this (create an object) only to be
able to call method; you don't need any value stored in this object! As
you can see, such a method of proceeding is not the most appropriate. It
would be nice to have a method you can use without need of
instantiating it. Happily this is what you have in Swift – you can use
type method prepending your method with class keyword:
print(Transformer.calculateDoubleValue(ofNumber: 7))
Note that it looks similarly, but now you use class name (starting with
capital letter t) in front of calculateDoubleValue(ofNumber:)
method.
Going back to our game code, at the first attempt you may implement
determineNumberOfDigits(number:) method as:
if number > 0 {
for digits in 1...10 {
if (value > number) {
return digits
}
value *= 10
}
}
return 0
}
This is not bad but also not in Swift style. You will change it in next
section. Going bac to the main topic of this section: if you have
120
121
122
SECTION 2
Guards
func myFunction() {
// CODE PRECEDING CONDITION
if condition {
// SOME CONDITIONAL CODE
}
123
func myFunction() {
// CODE PRECEDING CONDITION
if importantCondition {
if condition {
// SOME CONDITIONAL CODE
}
In this short snippet it looks acceptable but for longer code, maybe with
more nested conditions of this type, you will get few level of code
indentation and set of closing curly brackets which may make the code
less readable. The main idea of if, similar to "outer" condition in above
code, is to check if some, strictly required, conditions are met. If not,
you should immediately escape this function as it may not be
possible to execute subsequent statements. That is so important that we
clearly "mark" such an important places in our code that in Swift you
have special guard statement dedicated to check all necessary
conditions and to be used in place of ifs. With guard code looks more
natural and let you keep the code that handles a violated requirement
next to the requirement.
func myFunction() {
// CODE PRECEDING CONDITION
if condition {
// SOME CONDITIONAL CODE
}
124
guard, unlike conditional statement if, always is used with else part
because Swift always needs to know what to do in case of condition
failure. This action, denoted as ESCAPE in the above code, must
transfer control to exit the code block in which the guard
statement appears. If you want to make some action and further
proceed with executing your function, you will get an error. Look into
this code, where only print statement is used in case of condition
failure:
if y < 6 {
// SOME CONDITIONAL CODE
}
You can't compile this code because Swift complains: 'guard' body
must not fall through, consider using a 'return' or
'throw' to exit the scope. So you must exit the scope, and
ESCAPE must be a set of statements exiting your function. In most cases
it is simply return, but you may put something more "elaborated":
if y < 6 {
// SOME CONDITIONAL CODE
}
125
Of course you may live without guard and replace it with if:
if y < 6 {
// SOME CONDITIONAL CODE
}
126
return 0
}
127
128
SECTION 3
String interpolation
If you have a variable or constant of String type you can simply print
it:
var x = 5
print(x)
// Prints: 5
Now you may ask, how to put together both String and Int so it could
be printed? Assume, that your goal is to print:
You have to build this string manually, concatenating string with integer
previously "transformed" into string. If you want to create string with
more part this could be troublesome and in special cases extremely
unreadable.
129
let age = 12
message = "If you are \(age), you are \(age < 30 ? "young" :
"middle-aged")"
print(message)
// Prints: If you are 12, you are young
In the example above, the value of age variable (number 12) is inserted
into a string literal in place of \(age). The value of age is also part of a
compound expression later in the string where ternary conditional
operator is used.
130
if condition {
// CONDITION MET
} else {
// CONDITION UNMET
}
131
132
SECTION 4
Game code
func printBoard() {
let leadingPadding = Utils.determineNumberOfDigits(number:
rows)
var leadingPaddingString = ""
var line = ""
var number = 0
for _ in 1...leadingPadding {
leadingPaddingString += "s" // Replace with space
}
print(line)
// END Print first line
}
133
• line you will use to build each line you want to print.
PLAYER
ss#*********111111
OPPONENT
ss#*********111111
You can compare this result with example given in Section 1 of this
chapter. As you can see, output:
ss#*********111111
134
print(line)
// END Print second line
For a game board with 12 rows and 15 columns this will print:
ss#*********111111
ss#123456789012345
To save space I show only a part of output related to one game board
(board both for player and opponent are printed identically).
135
136
CHAPTER 5
In this part you will finish implementing printing game board method.
You will also create a class related to ships and implement one method
to use with this type.
138
SECTION 1
Tuples
As in many cases before, if you don't care about some element of a tuple,
you can use underscore character in place of variables corresponding to
tuple's element:
139
print(letter)
// Prints: a
You can also use index numbers starting at zero to get tuple's element:
print (x.5)
// Prints: a
To make your code more readable, you can name the individual
elements in a tuple when the tuple is defined:
But you don't have to provide name for every individual elements:
print(y.3.1)
// Prints: 5
print(y.compoundElement.1)
// Prints: 5
print(y.compoundElement.second)
// Prints: 5
print(y.3.second)
// Prints: 5
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. You may find them useful when
you want to return more than one value from a function:
140
func doSomething(
withInteger int: Int
) -> (square: Int, double: Int) {
let square = int * int
let double = int + int
let x = doSomething(withInteger: 3)
print(x.0) // Prints: 9
print(x.1) // Prints: 6
print(x.square) // Prints: 9
print(x.double) // Prints: 6
141
142
SECTION 2
switch text {
case "one", "One":
print("Case ONE")
case "two", "Two":
print("Case TWO")
default:
print("Default")
}
// Prints: Case ONE
What may catch your eye is lack of breaks which are needed in most
programming languages to prevent from fall into next case. In Swift, if
you fall into case, then only this case's code is executed. If you want to
use C-style fall through behavior a fallthrought keyword must be
used. The fallthrough keyword causes code execution to move to the
next case or default block on a case-by-case basis. Other words, this
is not "global" behavior for all cases within a given switch but
concerns only the case inside which fallthrought is used:
switch text {
143
Remember that doing that, the fallthrought does not check the case
condition for the switch case that it causes execution to fall into. The
fallthrough keyword simply causes code execution to move directly
to the statements inside the next case (or default case) block without
any case matching, as it is done in C:
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")
}
// Prints:
// 1 or 2
// 3 or 4
NO EMPTY CASES
As you saw, you can specify multiple values to match in one case:
and in Swift you can't leave empty case – a case without any
instruction (which is typical for C-like code):
case "one":
case "One":
print("Case ONE")
144
INTERVAL MATCHING
Another improvement in Swift is an ability of switch's cases to check if
their values are included in an interval:
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")
}
// Prints: Range two
TUPLE MATCHING
Also tuples can be tested by case statement which can be very handy
and allows to simplify your code:
145
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))")
}
// Prints: Point (0, 5.0) is on the OY axis
If all of the values for a case are binded as constants, or if all are
binded as variables, you can place a single var or let annotation before
the case name, for brevity:
point2D = (2, 3)
switch point2D {
146
147
148
SECTION 3
Extensions
extension TypeYouExtend {
// New functionality to add to TypeYouExtend goes here
}
149
extension Int {
func constrain(toRangeFrom min: Int, to max: Int) -> Int {
if self > max {
return max
} else if self < min {
return min
}
return self
}
}
This way you add new method to an existing Int type. This method
behaves as any other method made by Int class creators. Particularly,
self represents the current instance of a given type – it is an object on
which you call this method (for more informations about self see:
Chapter 3: Arrays and enumerations, Section 4: Implementing
initializers). Now you can call it:
var x = 12
x = x.constrain(toRangeFrom: 5, to: 10)
print(x)
// Prints: 10
With self and extension you can do even more. The following code
is intended to calculate square of an integer:
extension Int {
func square() -> Int {
return self * self
}
}
let x = 3
var y = x.square()
print(x)
// Prints: 3
print(y)
// Prints: 9
You can turn this code to mutate (change in-place) given integer
calculating its square:
150
extension Int {
func square() -> Int {
return self * self
}
let x = 3
var y = x.square()
print(x)
// Prints: 3
print(y)
// Prints: 9
y.squareMe()
print(y)
// Prints: 81
Now you can use this concept to implement another helpful method.
This method should enlarge specified string to a given length left
padding it with spaces by default or any other character if specified. For
example, if string 12 should be transformed into four-character string,
this method should return ..12 where dots . are used in a place of
spaces to make it visible. Create the Extensions.swift file and put
inside the following code:
extension String {
func leftPadding(
toLength: Int,
withPad: String = " "
) -> String {
guard toLength > self.count else {return self}
151
You can test this extension placing for a while the following code in
main.swift file:
let x = 12
let s1 = String(x).leftPadding(toLength: 4, withPad: "*")
let s2 = "\(x)".leftPadding(toLength: 4, withPad: "#")
print(s1)
// Prints: **12
print(s2)
// Prints: ##12
for _ in 1...leadingPadding {
leadingPaddingString += " "
}
152
if r == 0 || r == rows+1 {
line += leadingPaddingString
} else {
line += String(r).leftPadding(toLength: leadingPadding,
withPad: "s")
// Replace with space
}
for c in 0...cols+1 {
switch board[r][c] {
case .empty:
line += "."
case .hit:
line += "!"
case .ship:
line += "X"
case .shot:
line += "*"
case .none:
line += "?"
case .notAllowed:
line += "+"
case .rescue:
line += "O"
}
}
print(line)
}
// END Print all game board rows
153
For a game board with 12 rows and 15 columns this will print:
ss#*********111111
ss#123456789012345
ss+++++++++++++++++
s1+...............+
s2+...............+
s3+...............+
s4+...............+
s5+...............+
s6+...............+
s7+...............+
s8+...............+
s9+...............+
10+...............+
11+...............+
12+...............+
ss+++++++++++++++++
To save space I show only a part of output related to one game board
(board both for player and opponent are printed identically).
Finally you can search for every places marked in your code as Replace
with space and replace s, # and * with space character to get final
result:
111111
123456789012345
+++++++++++++++++
1+...............+
2+...............+
3+...............+
4+...............+
5+...............+
6+...............+
7+...............+
8+...............+
9+...............+
10+...............+
11+...............+
12+...............+
+++++++++++++++++
154
155
156
SECTION 4
Game code
• size so you know how many successive cells the ship occupies;
start column
|
|
.........
....u....
....u....
..llXrr..---start row
....d....
....d....
.........
where
157
Create a new class, as you did it before, and name it Ship. As for now,
this class will have only one component: Direction enumeration used
to uniquely identify or position a ship on a game board:
class Ship {
enum Direction {
case up, down, left, right
}
}
func mayPlaceShip(
size: Int,
anchor: (row: Int, col: Int),
direction: Ship.Direction
) -> Bool {
var modifier = (forRow: 0, forCol: 0)
var r = 0
var c = 0
switch direction {
case .up:
modifier = (forRow: -1, forCol: 0)
case .down:
modifier = (forRow: +1, forCol: 0)
case .left:
modifier = (forRow: 0, forCol: -1)
case .right:
modifier = (forRow: 0, forCol: +1)
158
for i in 0...size-1 {
r = anchor.row + i * modifier.forRow
c = anchor.col + i * modifier.forCol
if board[r][c] != .empty {
return false
}
}
return true
}
This is not a final version of this method; you will update it soon. In this
form you should have no problems to understand how it works.
class Test {
// For 10 x 10 game board should prints:
// possible, impossible, possible, impossible
class func testMayPlaceShip(board: Board) {
var x = board.mayPlaceShip(size: 4,
anchor: (row: 2, col: 2),
direction: .down)
x = board.mayPlaceShip(size: 4,
anchor: (row: 9, col: 2),
direction: .down)
x = board.mayPlaceShip(size: 4,
anchor: (row: 5, col: 7),
direction: .right)
159
x = board.mayPlaceShip(size: 4,
anchor: (row: 5, col: 8),
direction: .right)
Depending on your game board size this method prints different results;
for 10 by 10 game board you will see: possible, impossible,
possible, impossible.
func test() {
Test.testMayPlaceShip(board: boardPlayer)
}
Finally add this line at the end of main.swift file and run your code:
game.test()
import Foundation
PLAYER
1
1234567890
++++++++++++
1+..........+
2+..........+
3+..........+
4+..........+
5+..........+
6+..........+
160
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
OPPONENT
1
1234567890
++++++++++++
1+..........+
2+..........+
3+..........+
4+..........+
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
possible
impossible
possible
impossible
Program ended with exit code: 0
Last four lines (excluding exit status code line) are result of your test
method – you will see there: possible, impossible, possible,
impossible.
161
Now you can print game board and check if some location is a good
place to put there a ship.
162
163
164
CHAPTER 6
Properties, dictionaries
and sets
In this part you will finish implementing printing game board method.
You will also create a class related to ships and implement one method
to use with this type.
166
SECTION 1
Property types
Computed properties
Classes, structures and enumerations can define computed properties.
This type of properties do not actually store a value. Instead, they
provide a getter (programmers jargon term for get method) and an
optional setter (programmers jargon term for set method) to retrieve
and set this and other properties indirectly. So computed properties, in
contrast to stored properties, calculate rather than store value. Simply
speaking, sometimes you don't need to store explicitly (permanently)
some value – for example it may be to expensive (taking into
consideration memory usage) or this value may be computed based on
other values.
To define computed property you use get and set keyword. In case
you want to have a read only computed property you can skip the get
keyword (and of course there must be no set because this is read only
property) or even return if the entire body of the function is a single
expression (see also Chapter 2: Variables and functions, Section 3:
Functions, subsection: Function with an implicit return).
167
class Square {
var edge = 2.0
var s = Square()
print("\(s.edge) \(s.getArea())")
// Prints:
// 2.0 4.0
s.edge = 5
print("\(s.edge) \(s.getArea())")
// Prints:
// 5.0 25.0
class Square {
var area = 4.0
var s = Square()
print("\(s.getEdge()) \(s.area)")
// Prints:
// 2.0 4.0
s.area = 25
print("\(s.getEdge()) \(s.area)")
// Prints:
// 5.0 25.0
168
As you can see, it's quite natural to consider both edge and area as a
property of a square; it's quite natural to have an access of the form:
s.edge and s.area. So another approach to implement this might
take a form:
class Square {
var edge: Double
var area: Double
init(edge: Double) {
self.edge = edge
area = edge * edge
}
}
This looks good as long as you only use values from instantiated square:
var s = Square(edge: 2)
print("\(s.edge) \(s.area)")
// Prints:
// 2.0 4.0
s.edge = 5
print("\(s.edge) \(s.area)")
// Prints:
// 5.0 4.0
class Square {
var edge = 2.0
var area: Double {
edge * edge
}
}
var s = Square()
print("\(s.edge) \(s.area)")
// Prints:
169
// 2.0 4.0
s.edge = 5
print("\(s.edge) \(s.area)")
// Prints:
// 5.0 25.0
This fixes Square class in "one direction" – get direction, because if you
try to set properties' values, you will get an error:
// Error:
// Cannot assign to property: 'area' is a get-only property
s.area = 36
class Square {
var edge = 2.0
var area: Double {
get {
edge * edge
}
set (newValue) {
edge = newValue.squareRoot()
}
}
}
var s = Square()
print("\(s.edge) \(s.area)")
// Prints:
// 2.0 4.0
s.edge = 5
print("\(s.edge) \(s.area)")
// Prints:
// 5.0 25.0
s.area = 36
print("\(s.edge) \(s.area)")
// Prints:
// 6.0 36.0
It was just a kind of mental experiment and I hope it has convinced you
that computed properties might be useful. Of course, you can live
without them, but their existence simplifies code and make it more
170
class ComputedPropertyTest {
var simpleProperty = 3
var computedProperty: Int {
get {
return simpleProperty * 2
}
set (newValue) {
simpleProperty = newValue / 2
}
}
var readOnlyComputedProperty: Int {
simpleProperty * 3
}
}
cpt.computedProperty = 8
test = (sp: cpt.simpleProperty,
cp: cpt.computedProperty,
rocp: cpt.readOnlyComputedProperty)
print("\(test.sp) \(test.cp) \(test.rocp)")
// Prints: 4 8 12
171
class Action {
init() {
print("Init Action class") // 1
}
func doSomething(){
print("Do something for Action instance") // 2
}
}
class LazyAction {
init() {
print("Init LazyAction class") // 3
}
func doSomething(){
print("Do something for LazyAction instance") // 4
}
}
class ActionExecutor {
var action = Action()
lazy var lazyAction = LazyAction()
func executeAction(){
action.doSomething() // 5
lazyAction.doSomething() // 6
}
}
let ae = ActionExecutor() // 7
ae.executeAction() // 8
172
173
N OTE
Property observers
Property observers are really great things. They monitor to any changes
in a property's value. Every time a property's value is set, even if the new
value is not really new, observer is called. You can add property
observer to any stored properties, except for lazy properties.
• willSet is called just before the value is stored. This observer gets
new property value as a constant parameter with a default name of
newValue. You can specify your own name if you don't like this one.
• didSet is called just after the new value is stored. This observer gets
old (previous)) property value as a constant parameter with a default
name of oldValue. You can specify your own name if you don't like
this one.
174
class ClassWithPropertyObservers {
var firstPropertyToObserve:Int = 3 {
willSet{
print("firstPropertyToObserve is going to get new value
of: \(newValue)")
}
didSet{
print("An old value of firstPropertyToObserve (\
(oldValue)) has just been replaced by a new one")
}
}
var secondPropertyToObserve:Int = 7 {
willSet(newValueToBeSet){
print("secondPropertyToObserve is going to get new value
of: \(newValueToBeSet)")
}
didSet(oldValueToBeReplaced){
print("An old value of secondPropertyToObserve (\
(oldValueToBeReplaced)) has just been replaced by a new one")
}
}
}
cwpo.secondPropertyToObserve = 9
// Prints:
// secondPropertyToObserve is going to get new value of: 9
// An old value of secondPropertyToObserve (7) has just been
// replaced by a new one
N OTE
175
Type properties
Type properties are properties related with particular type rather than
particular instance of that type. They are like static variables or
constants in C or Java.
class ClassWithTypeProperty {
static var storedTypeProperty = 22
var computedProperty: Int {
return ClassWithTypeProperty.storedTypeProperty * 3
}
}
print("\(ClassWithTypeProperty.storedTypeProperty)")
// Prints: 22
ClassWithTypeProperty.storedTypeProperty = 222
print("\(ClassWithTypeProperty.storedTypeProperty)")
// Prints: 222
print("\(obj1.computedProperty), \(obj2.computedProperty)")
// Prints: 666, 666
class A {
var v = 0 {
willSet {
A.sum += newValue
}
}
176
// Error:
// Static member 'sum' cannot be used on instance of type 'A'
//print("\(obj1.v) \(obj2.v) \(obj3.v) \(A.sum)")
obj1.v = 5
obj2.v = 7
obj3.v = -4
obj2.v = 2
Property wrappers
Discussing this topic is beyond the scope of this book. If you are curious,
please read for example [SD:1, SD:2].
177
178
SECTION 2
Dictionaries
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. As a key you can use any hashable type
print(dictionary1)
// Prints: [:]
print(dictionary2)
// Prints: [:]
print(dictionary3)
// Prints:
// ["digit2": "two", "digit0": "zero", "digit1": "one"]
179
You can easily combine two dictionaries into one. This is almost as easy
as for arrays but you cannot use simply + operator because you must
resolve somehow conflicts caused by duplicate keys. Instead use
merge(_:uniquingKeysWith:) method where you specify a closure
to deal with conflicts:
print(dictionary3)
// Prints:
// ["digit2": "TwO", "digit0": "zero", "digit1": "one"]
var oldValue = dictionary3.updateValue("TWO", forKey: "digit2")
print(dictionary3)
180
// Prints:
// ["digit2": "TWO", "digit0": "zero", "digit1": "one"]
print("Old value: \(oldValue ?? "no value")")
// Prints: Old value: TwO
dictionary3["digit3"] = "three"
print(dictionary3)
// Prints:
// ["digit3": "three", "digit2": "TWO", "digit0": "zero",
"digit1": "one"]
You can easily pick-up elements according to filter criteria. You provide
a closure taking the key and value for each element, and any dictionary
key-value pair you return true for is included in a resulting dictionary:
181
// [3: 3, 2: 4]
182
183
184
SECTION 3
Sets
print(setOfDigitsNames1)
// Prints: ["one", "two"]
print(setOfDigitsNames2)
// Prints: ["one", "two"]
print(setOfDigitsNames3)
// Prints: ["one", "two"]
Actions you can perform on sets are typical for sets: you can check if
elements is in set, find an intersection of two sets (elements which
belong to both sets), their union ("sum" – which is a result of combining
both sets and removing duplicates) etc.:
if !setOfDigitsNames3.isEmpty {
if setOfDigitsNames3.contains("two") {
setOfDigitsNames3.remove("two")
}
if !setOfDigitsNames3.contains("three") {
setOfDigitsNames3.insert("three")
}
}
185
print(setOfDigitsNames3)
// Prints: ["one", "three"]
print(setOfInts1)
// Prints: [1, 4, 2, 3]
print(setOfInts2)
// Prints: [3, 4, 6, 5]
print(setOfInts1.union(setOfInts2))
// Prints: [1, 4, 6, 5, 2, 3]
print(setOfInts1.intersection(setOfInts2))
// Prints: [3, 4]
print(setOfInts1.subtracting(setOfInts2))
// Prints: [1, 2]
print(setOfInts1.symmetricDifference(setOfInts2))
// Prints:[1, 5, 2, 6]
There are few methods you can use to test set membership or equality:
1. To test if two sets contain exactly the same values, you use ==
operator.
2. To test if all of the values of a set are contained in the specified set,
you use isSubset(of:) method. You use
isStrictSubset(of:) if you want to exclude equality of both
sets.
3. To test if a set contains all of the values from a specified set, you use
isSuperset(of:) method. You use isStrictSuperset(of:)
if you want to exclude equality of both sets.
186
enum Message {
case warning, error, info
}
if importantMessages.contains(msg) {
print("!!!")
}
// Prints:
// !!!
importantMessages.contains(msg)
187
188
SECTION 4
Game code
enum Status {
case damaged, destroyed, ready
}
Status means:
189
init(size: Int,
position: [(row: Int, col: Int, status: Status)]
) {
self.size = size
self.position = position
self.readyLevel = size
}
return false
}
190
class Ships {
var ships = [String: Ship]()
var shipsAtCommand: Int {
var shipsNumber = ships.count
return shipsNumber
}
}
In consequence, you may write the iteration code either in the form:
191
shipsNumber -= 1
}
}
Because in your case you know that value is a Ship object, so you use
name ship instead to make code more readable. The first tuple's
element, key is not needed in your code, what is singled by Xcode with
the message Immutable value 'key' was never used;
consider replacing with '_' or removing it. To silent this
warning, key is finally replaced by underscore character _ which is the
way you say to Swift: Swift, I know that there is something here, but I
don't need it and so I don't care about it.
ships = Ships()
func placeShip(
192
size: Int,
anchor: (row: Int, col: Int),
direction: Ship.Direction
) {
var modifier = (forRow: 0, forCol: 0)
var r = 0
var c = 0
var position = [(row: Int, col: Int, status: Ship.Status)]()
switch direction {
case .up:
modifier = (forRow: -1, forCol: 0)
case .down:
modifier = (forRow: +1, forCol: 0)
case .left:
modifier = (forRow: 0, forCol: -1)
case .right:
modifier = (forRow: 0, forCol: +1)
}
for i in 0...size-1 {
r = anchor.row + i*modifier.forRow
c = anchor.col + i*modifier.forCol
board[r][c] = CellType.ship
position.append((row: r,
col: c,
status: Ship.Status.ready))
}
ships.ships["\(anchor)"] =
Ship(size: size, position: position)
}
for i in 0...size-1 {
...
position.append((row: r,
col: c,
status: Ship.Status.ready))
}
ships.ships["\(anchor)"] = ship
193
ships is a dictionary with keys of the String type and Ship as a value
type. Key should uniquely identify every ship. Combining each ship
anchor coordinates (its first cell's row and column coordinate) you get a
unique ship key because no more than one ship can start in a given cell
(anchor). Such a string may be simply accomplish with "\(anchor)"
phrase.
There is one simplification, still also present in both methods: you don't
care about cells surrounding the ship. Thus, as for now, two ships may
be placed so they "touch" – this will be fixed in next chapter.
enum Who {
case player, opponent
}
return boardOpponent
}
194
func mayPlaceShip(
who: Who,
size: Int,
anchorRow: Int,
anchorCol: Int,
direction: Ship.Direction
) -> Bool {
func placeShip(
who: Who,
size: Int,
anchorRow: Int,
anchorCol: Int,
direction: Ship.Direction
) {
195
if x {
engine.placeShip(who: who,
size: size,
anchorRow: anchorRow,
anchorCol: anchorCol,
direction: direction)
return true
}
return false
}
196
After so many changes you may test if your code works as it is expected:
PLAYER
1
1234567890
++++++++++++
1+..........+
2+..........+
3+..........+
4+..........+
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
OPPONENT
1
1234567890
++++++++++++
1+..........+
2+..........+
3+..........+
4+..........+
197
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
OK
ERROR
ERROR
OK
PLAYER
1
1234567890
++++++++++++
1+..........+
2+..........+
3+...X......+
4+...X......+
5+...X......+
6+...X......+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
OPPONENT
1
1234567890
++++++++++++
1+..........+
2+.....X....+
3+.....X....+
4+.....X....+
5+.....X....+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
Program ended with exit code: 0
198
Code summary
In this chapter you made a lot of changes in different files. In reward
you can now populate game board with ships.
199
200
CHAPTER 7
Code completion
In this part you will try to verify your knowledge and understanding of
Swift you acquire so far. I will formulate a tasks for you, and you can try
to implement them and next compare your solution with mine.
202
SECTION 1
Task 1
According to assumptions from Chapter 1: Initial steps,Section 1:
Battleship game, no ship can "touch" other ship. Your task is to modify
placeShip(size:anchor:direction:) function from Board class
in such a way that all cells directly surrounding ship are of
Board.CellType.notAllowed type to prevent other ships to be
placed too close (see figure below).
NOW SHOULD BE
1 1
1234567890 1234567890
++++++++++++ ++++++++++++
1+..........+ 1+..........+
2+..........+ 2+..........+
3+..........+ 3+..+++.....+
4+...X......+ 4+..+X+.....+
5+...X......+ 5+..+X+.....+
6+..........+ 6+..+++.....+
7+..........+ 7+..........+
8+..........+ 8+..........+
9+..........+ 9+..........+
10+..........+ 10+..........+
++++++++++++ ++++++++++++
203
Task 2
When ship is totally destroyed (sunken) all its surrounding cells should
be marked as it is showed at the figure below:
1 1 1
1234567890 1234567890 1234567890
++++++++++++ ++++++++++++ ++++++++++++
1+..........+ 1+..........+ 1+..........+
2+..........+ 2+..........+ 2+..........+
3+..........+ 3+..........+ 3+..OOO.....+
4+...X......+ 4+...!......+ 4+..O!O.....+
5+...X......+ ===> 5+...X......+ ===> 5+..O!O.....+
6+..........+ hit at 6+..........+ hit at 6+..OOO.....+
7+..........+ (4, 4) 7+..........+ (5, 4) 7+..........+
8+..........+ 8+..........+ 8+..........+
9+..........+ 9+..........+ 9+..........+
10+..........+ 10+..........+ 10+..........+
++++++++++++ ++++++++++++ ++++++++++++
Task 3
Implement a set of "shootting" methods:
204
Task 4
In Board class implement a method to automatically position all ships
of a defined sizes. Signature of this method is given below:
func shipsAutoSetup(
shipsSize: [Int],
maxTriesPerShip: Int
) -> Int {
// Try to position all ships
}
205
if from == to {
return from
}
if let ex = excluding {
if ex.contains(candidate) {
return candidate
}
} else {
return candidate
}
}
return nil
}
206
207
208
SECTION 2
When you complete all your tasks you should somehow verify them.
Add to Test class the following code:
if r {
engine.placeShip(who: player,
size: 2,
anchorRow: 2,
anchorCol: 4,
direction: .down)
r = engine.boardPlayer.mayShot(row: 3, col: 4)
if r {
engine.boardPlayer.shot(row: 3, col: 4)
} else {
print("Shot is not possible")
}
engine.printBoards()
r = engine.boardPlayer.mayShot(row: 2, col: 4)
if r {
engine.boardPlayer.shot(row: 2, col: 4)
} else {
print("Shot is not possible")
}
209
engine.printBoards()
} else {
print("Ship may not be placed")
}
}
import Foundation
PLAYER
1
1234567890
++++++++++++
1+..........+
2+...X......+
3+...!......+
4+..........+
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
OPPONENT
1
1234567890
++++++++++++
1+..........+
2+..........+
3+..........+
4+..........+
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
PLAYER
1
210
1234567890
++++++++++++
1+..OOO.....+
2+..O!O.....+
3+..O!O.....+
4+..OOO.....+
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
OPPONENT
1
1234567890
++++++++++++
1+..........+
2+..........+
3+..........+
4+..........+
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
Program ended with exit code: 0
In this method you try to put 10 ships: one of size 4, two of size 3, three
of size 2 and four of size 1. All of them in a random locations and in a
random direction.
import Foundation
211
If you run your code, you should see result similar to the following (auto
setup is a random process, so result after every run will be different):
PLAYER
1
1234567890
++++++++++++
1++X+.....+++
2++++.....+X+
3+X+....+++X+
4+++.++++X+X+
5+X+.+XX+X+++
6+++.++++++X+
7++++.+++++++
8++X+.+XXXX++
9++X+.+++++++
10++X+.+XX+..+
++++++++++++
OPPONENT
1
1234567890
++++++++++++
1+..........+
2+..........+
3+..........+
4+..........+
5+..........+
6+..........+
7+..........+
8+..........+
9+..........+
10+..........+
++++++++++++
Program ended with exit code: 0
212
213
214
SECTION 3
Task 1
func placeShip(
size: Int,
anchor: (row: Int, col: Int),
direction: Ship.Direction
) {
var modifier: (forRow: Int, forCol: Int)!
var r: Int!
var c: Int!
var position = [(row: Int, col: Int, status: Ship.Status)]()
switch direction {
case .up:
modifier = (forRow: -1, forCol: 0)
case .down:
modifier = (forRow: +1, forCol: 0)
case .left:
modifier = (forRow: 0, forCol: -1)
case .right:
modifier = (forRow: 0, forCol: +1)
}
for i in 0...size-1 {
r = anchor.row + i*modifier.forRow
c = anchor.col + i*modifier.forCol
board[r][c] = CellType.ship
position.append((row: r,
col: c,
status: .ready))
215
r = anchor.row-(modifier.forCol) + i*modifier.forRow
c = anchor.col-(modifier.forRow) + i*modifier.forCol
board[r][c] = .notAllowed
// END: To add border along ship
}
r = anchor.row+(modifier.forCol) + (-1)*modifier.forRow
c = anchor.col+(modifier.forRow) + (-1)*modifier.forCol
board[r][c] = .notAllowed
r = anchor.row-(modifier.forCol) + (-1)*modifier.forRow
c = anchor.col-(modifier.forRow) + (-1)*modifier.forCol
board[r][c] = .notAllowed
r = anchor.row+(modifier.forCol) + (size)*modifier.forRow
c = anchor.col+(modifier.forRow) + (size)*modifier.forCol
board[r][c] = .notAllowed
r = anchor.row-(modifier.forCol) + (size)*modifier.forRow
c = anchor.col-(modifier.forRow) + (size)*modifier.forCol
board[r][c] = .notAllowed
// END: To add borders at the ends of ship
ships.ships["\(anchor)"] = Ship(
size: size,
position: position)
}
Task 2
private func markWhenShipDestroyed(ship: Ship) {
let allCellsArround = [
(rowModifier: -1, colModifier: 0),// top
(rowModifier: -1, colModifier: +1),// top-right
(rowModifier: 0, colModifier: +1),// right
(rowModifier: +1, colModifier: +1),// bottom-right
(rowModifier: +1, colModifier: 0),// bottom
(rowModifier: +1, colModifier: -1),// bottom-left
(rowModifier: 0, colModifier: -1),// left
216
if board[row][col] == Board.CellType.empty ||
board[row][col] == Board.CellType.shot ||
board[row][col] == Board.CellType.notAllowed {
board[row][col] = Board.CellType.rescue
}
}
}
}
Task 3
func mayShot(row: Int, col: Int) -> Bool {
let yes: Set<Board.CellType> = [.empty,
.ship,
.notAllowed]
if yes.contains(board[row][col]) {
return true
}
if no.contains(board[row][col]) {
return false
}
return false
}
217
Task 4
func shipsAutoSetup(
shipsSize: [Int],
maxTriesPerShip: Int
) -> Int {
var shipDirection = Ship.Direction.up
var possible = true
var success: Bool
var positioned = 0
var anchor: (row: Int, col: Int)
if possible {
placeShip(
size: size,
anchor: anchor,
direction: shipDirection)
positioned += 1
218
success = true
break
}
}
}
}
if !success {
return positioned
}
}
return positioned
}
219
220
SECTION 4
self.shipsSize = shipsSize
self.rows = rows
self.cols = cols
}
221
return Who.opponent
}
return Who.player
}
func shipsAutoSetup(
shipsSize: [Int],
maxTriesPerShip: Int,
who: Who
) -> Bool {
let boardWho = getWhoBoard(who: who)
let number = boardWho.shipsAutoSetup(
shipsSize: shipsSize,
maxTriesPerShip: maxTriesPerShip
)
if shipsSize.count == number {
return true
}
return false
}
222
Task 5
To make playing game possible you need a method used to check if
there is a winner and, if the answer is yes, who is this. This method
should have a following signature:
Task 6
Implement a very simple shooting method. In this case you try at most
maxTries times to find randomly an acceptable cell (that is a cell you
are allowed to shoot on). If you fail, you systematically, row by row and
column by column, search until you find acceptable cell. Signature of
this method is given below:
func getShotCoordinatesForOpponent(
maxTries: Int
) -> (row: Int, col: Int)? {
Task 7
Delete all the contents from main.swift file leaving only import
Foundation. Next add to this file function with the following
signature:
223
func getIntFromCommandLine(
message: String,
rangeMin: Int,
rangeMax: Int
) -> Int {
This function should print a message and then ask a (human) player to
provide integer laying in closed interval [rangeMin, rangeMax]. It
should keep asking as long as number entered by a player is outside a
given range.
Task 8
Based on function from Task 7 define in main.swift file a function
getting shot coordinates from human player. This function should have
a following signature:
func getShotCoordinatesForPlayer(
engine: Engine,
maxRows: Int,
maxCols: Int
) -> (row: Int, col: Int)? {
224
• An instance of game engine:
Now you are ready to implement final part of code – a main game loop:
if opponentReady, playerReady {
game.printBoards()
while(true){
print("\n\n\n TURN")
print(whoseTurn)
if whoseTurn == Engine.Who.player {
coordinates = getShotCoordinatesForPlayer(
engine: game,
maxRows: 10,
maxCols: 10
)
225
if coordinates == nil {
print("Player can't shoot")
break
} else {
print("Shot at row \(coordinates!.row) and column \
(coordinates!.col)")
}
game.shot(
who: whoseTurn,
row: coordinates!.row,
col: coordinates!.col
)
whoseTurn = Engine.Who.opponent
} else {
coordinates = game.getShotCoordinatesForOpponent(
maxTries: 100
)
if coordinates == nil {
print("Opponent can't shoot")
break
} else {
print("Shot at row \(coordinates!.row) and column \
(coordinates!.col)")
}
game.shot(
who: whoseTurn,
row: coordinates!.row,
col: coordinates!.col
)
whoseTurn = Engine.Who.player
}
game.printBoards()
if let who = game.checkWhoWins() {
print("The winner is \(who)")
break
}
}
} else {
print("Can't position all ships")
}
Now you can run your code. Ships for player (human player) and his
opponent (computer player) should be automatically positioned on
game boards. Both players should have 10 ships. To make debug
possible, both boards are printed on the screen. Happy playing!
226
227
228
SECTION 5
Task 5
func checkWhoWins() -> Who? {
if boardPlayer.ships.shipsAtCommand == 0 {
return Who.opponent
} else if boardOpponent.ships.shipsAtCommand == 0 {
return Who.player
}
return nil
}
Task 6
func getShotCoordinatesForOpponent(
maxTries: Int
) -> (row: Int, col: Int)? {
var row, col: Int?
229
return nil
}
Task 7
func getIntFromCommandLine(
message: String,
rangeMin: Int,
rangeMax: Int
) -> Int {
print(message)
while(true) {
if let input = readLine() {
if let int = Int(input) {
if int >= rangeMin && int <= rangeMax {
return int
} else {
("\(input) is not in range [\(rangeMin)-\(rangeMax)].
Please try again")
}
} else{
print("\(input) is not a valid integer. Please try
again")
}
}
}
}
Task 8
func getShotCoordinatesForPlayer(
engine: Engine,
maxRows: Int,
maxCols: Int
) -> (row: Int, col: Int)? {
while(true) {
let row = getIntFromCommandLine(
message: "Enter row",
rangeMin: 1,
rangeMax: maxRows)
230
231
232
CHAPTER 8
Structures, inheritance
and errors handling
In this part you will extend your knowledge with information on more
advanced topics.
234
SECTION 1
Structures
235
• you can check and interpret the type of a class instance at runtime;
As you know, for classes you cannot leave properties uninitialized – you
have to:
class ClassOption1 {
var property = 0
}
class ClassOption2 {
var property: Int?
}
class ClassOption3 {
var property: Int
init(property: Int) {
self.property = property
236
}
}
// Cause an error:
// Class 'ClassOption4' has no initializers
class ClassOption4 {
var property: Int
}
The same rules are applied to structures but in this case you have also
another one option: automatically generated memberwise initializers.
struct StructOption1 {
var property = 0
}
struct StructOption2 {
var property: Int?
}
struct StructOption3 {
var property: Int
init(property: Int) {
self.property = property
}
}
// Cause NO error:
struct StructOption4 {
var property: Int
}
237
If class or struct is declared with var you can modify its properties:
ico1.property = 11
iso1.property = 21
let c = ClassOption1()
let s = StructOption1()
c.property = 11
// Cause an error:
// Cannot assign to property: 's' is a 'let' constant
s.property = 21
Mutating methods
For structures (and also enumeration) you can implement mutating
methods allowing to modify the properties of value type (structures and
enumerations but not classes which are reference type) from within its
instance methods. By default, the properties of a value type
cannot be modified from within its instance methods. You have
to use a special mutating keyword to implement such a method:
struct Item {
var item: String
238
print(i.item)
// Prints:
// Another text
print(i.item)
// Prints:
// Again new text
i.mutateInvalid("Try it")
239
240
241
242
SECTION 2
Inheritance
class A {
var a = 1
}
class B {
var b = 2
}
class C: A {
var c = 3
}
// Cause an error:
// Multiple inheritance from classes 'A' and 'B'
class D: A, B {
var d = 4
}
var a = A()
var c = C()
a.a = 5
243
c.c = 7
c.a = 9
class BaseClass {
var somePropertyInt: Int
init (){
somePropertyInt = 5
print("BC: init complete");
}
func doSomething(){
print("BC: doSomething")
}
func anotherOneFunction(){
print("BC: anotherOneFunction")
}
}
func callMethodFromSuperClass() {
print("SC: callMethodFromSuperClass")
super.notForSubclassing()
}
}
var sc = SubClass()
// Prints:
244
sc.doSomething()
// Prints:
// SC: doSomething
sc.notForSubclassing()
// Prints:
// BC: notForSubclassing
sc.anotherOneFunction()
// Prints:
// BC: anotherOneFunction
print("\(sc.somePropertyInt) \(sc.somePropertyString)")
// Prints:
// 5 text
sc.callMethodFromSuperClass()
// Prints:
// SC: callMethodFromSuperClass
// BC: notForSubclassing
var sc = subClass()
// Prints:
// baseClass: init complete
// subClass: init complete
245
246
247
248
SECTION 3
In Swift we have two special types for working with indefinite, or better
say: any type:
The most basic example of Any usage is an array to store items of any
type:
arrayOfAnyInstances.append(5)
arrayOfAnyInstances.append(1.23)
arrayOfAnyInstances.append("test")
arrayOfAnyInstances.append(
{(arg: String) -> String in
"Echo: \(arg)"
})
print(arrayOfAnyInstances)
// Prints: [5, 1.23, "test", (Function)]
The Any type represents values of any type, including optional types.
Swift gives you a warning if you use an optional value where a value of
type Any is expected. If you really do need to use an optional value as an
Any value, you can use the as operator to explicitly cast the optional to
Any:
249
class A {}
class B: A {}
class C: B {}
var iA = A()
var iB = B()
var iC = C()
var pointer: B
pointer = iB
250
// Error:
// // Cannot assign value of type 'A' to type 'B'
pointer = iA
pointer = iC
You can try to downcast to the subclass type with a type cast operator:
• in the conditional form as? when returns an optional value of the type
you try to downcast to;
• in the forced form as! to attempt the downcast and force unwraps the
result as a single compound action.
With pointers and casting you can treat an object being and instance of
a given class as an object of different class which is its ancestor. In
programming languages the existence of a single interface to entities of
different types or the use of a single symbol to represent multiple
different types is called polymorphism. This is one of the fundamental
building block of every object-oriented programming language. With
polymorphism your code can be agnostic as to which class in the
supported hierarchy (family of objects) it is operating on – the parent
class or one of its descendants.
See examples below (the name Test preceding class name in Test.B
and similar statements is the name of my project where I put this code):
if you think the first is to synthetic don't hesitate and jump to the second
which I hope is much more clear.
Example 1:
// Part 1
var b = B()
print("1: \(b) is of \(type(of: b)) type");
// Prints:
// 1: Test.B is of B type
// Part 2.1
251
if let c = x {
print("3: \(c) is of \(type(of: c)) type");
} else {
print("3: Casting problems")
}
// Prints:
// 3: Test.B is of B type
// Part 2.2
// Because conditional cast to the superclass always succeeds,
// you can use 'as'
var xForced = B() as A
print("4: \(xForced) is of \(type(of: xForced)) type");
// Prints:
// 4: Test.B is of B type
// Warning:
// 'is' test is always true
if xForced is A {
print("5: Is A class")
}
if xForced is B {
print("6: Is B class")
}
if xForced is C {
print("7: Is C class")
}
// Prints:
// 5: Is A class
// 6: Is B class
// Part 3
var y = B() as? C
print("8: \(y ?? C()) is of \(type(of: y)) type");
// Prints:
// 8: Test.C is of Optional<C> type
if let c = y {
print("9: \(c) is of \(type(of: c)) type");
252
} else {
print("9: Casting problems")
}
// Prints:
// 9: Casting problems
// Part 4
var pA: A
pA = iB
var t1 = pA as? B
if let c = t1 {
print("10: \(c) is of \(type(of: c)) type");
} else {
print("10: Casting problems")
}
// Prints:
// 10: Test.B is of B type
var t2 = pA as? C
if let c = t2 {
print("11: \(c) is of \(type(of: c)) type");
} else {
print("11: Casting problems")
}
// Prints:
// 11: Casting problems
Example 2:
Imagine that you have a secret keeping application you store your
passwords, credit cards data, various access code, etc. Consider a
following hierarchy of objects:
class Secret {
var name: String
init(name: String) {
self.name = name
}
}
253
self.password = password
super.init(name: name)
}
func printEmail() {
print("email: \(email)\npassword: \(password)")
}
}
super.init(name: name)
}
func printCreditCard() {
print("number: \(number)\ncvvCode: \(cvvCode)")
}
}
Having this you can create an array to keep some of your secrets and fill
it with mock data:
Now you can create a pointer and set it to point to one of your secrets:
254
If the result is not nil, then downcasting was successful and you may
treat your object as an instance of a given type:
if let e = t {
print("This is an email")
e.printEmail()
} else {
print("This is NOT an email")
}
// Prints:
// This is an email
// email: my.job@email.server.com
// password: 123abc
for s in allMySecrets {
switch s {
case is Email:
let e = s as! Email
e.printEmail()
case is CreditCard:
let cc = s as! CreditCard
cc.printCreditCard()
default:
print("Unknown secret")
}
}
255
256
257
258
SECTION 4
Access control
Access control restricts access to/from parts of your code. With this
feature you can hide the implementation details, and enable access to it
with a preferred interface through which that code can be used. Swift
provides five different access levels. These access levels are relative to
the source file in which an entity is defined, and also relative to the
module that source file belongs to.
259
• Classes with public access, or any more restrictive access level, can be
subclassed only within the module where they’re defined.
• Class members with public access, or any more restrictive access level,
can be overridden by subclasses only within the module where they’re
defined.
• Rule 2 The access control level of a type also affects the default access
level of that type’s members: properties, methods, initializers, and
subscripts. For example, having defined type with a private access
level, the default access level of its members will also be private.
• Rule 5 The access level for a function type is calculated as the most
restrictive access level of the function’s parameter types and return
type. You must specify the access level explicitly as part of the
260
• Rule 7 A subclass can’t have a less restrictive access level than its
superclass. For example, we can’t write a public subclass of an
internal superclass.
// Compile Error:
// Only classes and overridable class members can be declared
'open'; use 'public'
open var variableOpen = 0
public var variablePublic = 0
// Implicitly internal
var variableInternal = 0
fileprivate var variableFilePrivate = 0
private var variablePrivate = 0
261
// Rule 9
private var xx = classPrivate()
// Without private:
// Compile Error: Variable must be declared private
// or fileprivate because its type
// 'classPrivate.classPrivateNestedType' uses a private type
private var yy = xx.xx
// Compile Error: 'variableExplicitlyPrivate' is inaccessible
// due to 'private' protection level
//var ww = xx.variableExplicitlyPrivate
262
var zz = xx.variable
// Without private:
// Compile Error: Function must be declared private
// or fileprivate because its results uses a private type
// Rule 4 and 5
private func someFunction() -> (classPublic, classPrivate)
{ ... }
// Rule 7
// Compile Error: Class cannot be declared public
// because its superclass is private
public class classPublicWithPrivateSuperclass: classPrivate {
fileprivate func someMethod() {}
}
// Rule 8
internal class classInternalOverrides: classPublic {
override internal func methodFilePrivate() {
super.methodFilePrivate()
}
}
263
264
SECTION 5
Errors handling
throw ErrorColection.errorType1
• Error can be propagated from a function to the code that calls that
function.
265
ERROR PROPAGATION
You use the throws keyword to indicate that a function, method, or
initializer can throw an error. A function marked this way is called a
throwing function. Only throwing functions can propagate errors. Any
errors thrown inside a nonthrowing function must be handled inside
that function.
class classThrowingErrors {
func functionThrowingErrors(
forNumber number: Int
) throws -> Int {
if (number == 0) {
throw ErrorColection.errorType0
}
print("number: \(number)")
}
}
do {
try expression
// statements
} catch pattern_1 {
// statements
} catch pattern_2 where condition {
// statements
}
class classThrowingErrors {
func functionThrowingErrors(
forNumber number: Int
) throws -> Int {
if (number == 0) {
throw ErrorColection.errorType0
}
print("number: \(number)")
return number
}
266
do {
let x = try functionThrowingErrors(forNumber: 0)
print("No errors, result is \(x)")
} catch ErrorColection.errorType0 {
print("Error type 0")
} catch ErrorColection.errorType1 {
print("Error type 1")
} catch ErrorColection.errorType2 where number > 4 {
print("Error type 2")
} catch let error {
// Who knows, maybe there are more errors possible?
// We have to catch all of them.
print(error.localizedDescription)
}
}
}
var c = classThrowingErrors()
c.functionNoThrowingErrors(forNumber: 0)
Error type 0
267
ERROR CLEANING
In Java very common statements sequence is try-catch-finally.
The finally block always executes when the try block exits. This
ensures that the finally block is executed even if an unexpected
exception occurs. But finally is useful for more than just exception
handling — it allows the programmer to avoid having cleanup code
accidentally bypassed by a return, continue, or break. Putting
cleanup code in a finally block is always a good practice, even when
no exceptions are anticipated. [JAVADOC:1]
268
defer {
print("5")
}
print("2")
throw ErrorColection.errorType0
// Warning:
// Code after 'throw' will never be executed
print("6")
// Warning:
// 'defer' statement at end of scope always executes
// immediately; replace with 'do' statement to silence
// this warning
defer {
print("4")
}
}
defer {
print("9")
}
do {
try doSomethingWithResources()
} catch {
print("7")
throw ErrorColection.errorType0
}
defer {
print("8")
}
1
2
5
3
7
9
Fatal error: Error raised at top level:
[...]
269
270
CHAPTER 9
272
SECTION 1
Protocols
class A class E
class D:B, P1
protocol ProtocolName {
// Protocol definition goes here
}
273
As you can see, every class may have at most one superclass but may
implement many protocols.
274
That was a lot of theory, so now see how it works in the following
example:
protocol ProtocolForClass {
var mustBeSettable: Int { get set }
var mayBeSettable: Int { get }
protocol ProtocolForStructure {
var mustBeSettable: Int { get set }
var mayBeSettable: Int { get }
275
// }
276
var c = ClassImplementingProtocol(someParameter: 2)
print(c.methodName()) // Prints: 6
c.iAmAllowedToModifyThisInstance(withValue: 3)
print(c.methodName()) // Prints: 9
var s = StructureImplementingProtocol(someParameter: 2)
print(s.methodName()) // Prints: 6
s.iAmAllowedToModifyThisInstance(withValue: 3)
print(s.methodName()) // Prints: 9
A matter of initializer
As you know, implementing a protocol initializer on a conforming class,
you must mark the initializer implementation with the required
modifier. You may wonder what to do if a subclass overrides a
designated initializer from a superclass, and also implements a
matching initializer requirement from a protocol? In such a case mark
the initializer implementation with both the required and override
modifiers:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// Initializer implementation goes here
}
}
277
}
}
Protocols as types
Although protocols don’t provide any real functionality themselves, they
are a fully-fledged type you can use in your code. In consequence, you
can use protocols in many places where other types are
allowed:
You can use the is and as operators to check for protocol conformance,
and to cast to a specific protocol. Doing this follows the same, well
known, syntax as for type:
• The as! version of the downcast operator forces the downcast to the
protocol type and triggers a runtime error if the downcast doesn’t
succeed.
278
protocol SimpleProtocol1 {
var property1: Int { get }
}
protocol SimpleProtocol2 {
var property2: Int { get }
}
let o1 = TestClass1()
let o2 = TestClass1_2()
let x: Any = o1
if (x is SimpleProtocol1){
let o = someFunction(object: x as! SimpleProtocol1)
print(o.property2)
// Prints:
// 1
print((o as! TestClass2).property22?.property1 ?? "default")
// Prints:
// 1
}
279
print(arr)
// Prints:
// [test.TestClass1, test.TestClass1_2]
EXAMPLE 1
Consider the following hierarchy of objects:
class A {}
class B: A {}
class C: A {}
class D: B {}
class E {}
class F: E {}
class G: E {}
Now you can create a data structure (array) to store instances of every
class from selected family, for example A:
familyA.append(A())
familyA.append(B())
familyA.append(C())
familyA.append(D())
280
or pass to a function:
func doSomething(on: A) {}
doSomething(on: A())
doSomething(on: B())
doSomething(on: C())
doSomething(on: D())
If you try to use object from different family you will get an errors:
// Error:
// No exact matches in call to instance method 'append'
familyA.append(E())
// Error:
// Cannot convert value of type 'E' to expected argument type
'A'
doSomething(on: E())
protocol P1 {}
protocol P2 {}
class A {}
class B: A {}
class C: A, P2 {}
class D: B, P1 {}
class E {}
class F: E, P1, P2 {}
class G: E, P1 {}
Now you can create a data structure (array) to store instances of every
class from any family – the only requirement is to conform selected
protocol, for example P1:
281
If you try to use object which doesn't conform specified protocol, you
will get an errors:
// Error:
// No exact matches in call to instance method 'append'
friends.append(C())
// Error:
// Argument type 'E' does not conform to expected type 'P2'
doSomething(on: E())
EXAMPLE 2
This example is less abstract and is for you to feel what protocols are for.
class Thing {
class LivingObject {
282
Things and living beings have not too much in common, but every
object of these types can be shortly characterize – you can say, it can
shortly "introduce itself" saying what kind of object it is. This is a
common feature (and very possibly the only one feature) shared among
all objects belonging either to inanimate or to living family. Now you
can introduce a new class, name it Common, and make it a parent for
Thing and LivingObject only to be able to give the ability to
introduce to all classes. Making this you admit that Thing and
LivingObject belongs to one super family, which probably is not
what you want.
protocol Introducable {
var fullName: String { get }
var shortName: String { get }
func introduceYourself() -> String
}
283
set(newValue) {
title = newValue
}
}
set(newValue) {
commonlyUsedTitle = newValue
}
}
284
}
}
introduceObject(object: b)
introduceObject(object: h)
285
protocol P1 {}
protocol P2 {}
class A {}
class B: A {}
class C: A, P1 {}
class D: A, P2 {}
class E: P1, P2 {}
class F: A, P1, P2 {}
// Error:
// Argument type 'B' does not conform to expected type 'P1'
//doSomething(on: B())
286
// Error:
// Argument type 'C' does not conform to expected type 'P2'
//doSomething(on: C())
// Error:
// Argument type 'D' does not conform to expected type 'P1'
//doSomething(on: D())
// Error:
// Cannot convert value of type 'E' to expected argument type
'A'
//doSomething(on: E())
doSomething(on: F())
// Error:
// No exact matches in call to instance method 'append'
//container.append(B())
//container.append(C())
//container.append(D())
//container.append(E())
container.append(F())
You can achieved the same result with intermediate type. The following
code is almost identical with the above except four places marked with
left arrow <-- and bolded font:
protocol P1 {}
protocol P2 {}
class A {}
class B: A {}
class C: A, P1 {}
class D: A, P2 {}
class E: P1, P2 {}
class F: Composition {} // <---
// Error:
// Cannot convert value of type 'B' to expected argument
// type 'Composition'
//doSomething(on: B())
doSomething(on: F())
287
// Error:
// No exact matches in call to instance method 'append'
//container.append(B())
container.append(F())
Protocols inheritance
A protocol can inherit one or more other protocols and can add further
requirements on top of the requirements it inherits. The syntax for
protocol inheritance is similar to the syntax for class inheritance, but
with the option to list multiple inherited protocols, separated by
commas:
protocol ProtocolCommon {
var mustBeSettable: Int { get set }
var mayBeSettable: Int { get }
func iAmAllowedToModifyThisInstance(
withValue value: Int
) {
mustBeSettable = value
mayBeSettable = value * 2
}
288
mustBeSettable = value
mayBeSettable = value * 2
}
}
func iAmAllowedToModifyThisInstance(
withValue value: Int
) {
mustBeSettable = value
mayBeSettable = value * 2
}
var c2 = ClassImplementingProtocol2(someParameter: 2)
print(c2.methodName())
// Prints: 6
c2.iAmAllowedToModifyThisInstance(withValue: 3)
print(c2.methodName())
// Prints: 9
var s2 = StructureImplementingProtocol2(someParameter: 2)
print(s2.methodName())
// Prints: 6
s2.iAmAllowedToModifyThisInstance(withValue: 3)
print(s2.methodName())
// Prints: 9
protocol P1 {}
protocol P2 {}
class A {}
289
class B: A {}
class C: A, P1 {}
class D: A, P2 {}
class E: P1, P2 {}
class F: A, Composition {} // <---
protocol P {
func toString() -> String
}
extension P {
func preetyPrint() -> String {
return "*** \(toString()) ***"
}
}
class A: P {
var v: Int?
var x = A()
290
print(x.preetyPrint())
// Prints:
// *** UNDEFINED ***
x.v = 5
print(x.preetyPrint())
// Prints:
// *** 5 ***
If you look at the above code carefully, you will notice that function
provided in extension doesn't have to use protocol's required method. It
can be any function you want and by implementing it you provide it to
any conforming type. Among others, this can be used to provide a
default implementation to any method or computed property
requirement of that protocol. If a conforming type provides its own
implementation of a required method or property, that implementation
will be used instead of the one provided by the extension.
protocol P {
func important()
}
extension P {
func important() {
print("Default implementation")
}
}
class A: P {}
class B: P {
func important() {
print("Improved implementation")
}
}
var x = A()
x.important()
// Prints:
// Default implementation
291
var y = B()
y.important()
// Prints:
// Improved implementation
292
293
294
SECTION 2
class RightPyramid {
let volume: Double
let height: Double
init(params: [String:AnyObject]) {
height = params["height"] as! Double
let baseArea = params["baseArea"] as! Double
volume = 1.0 / 3.0 * baseArea * height
}
}
295
Now you may ask if there is possibility to implement this hierarchy but
using structures. Answer is positive, although no solution is perfect.
SOLUTION 1
In this approach you use composition of value types.
struct RightPyramid {
let volume: Double
let height: Double
init(params: [String:AnyObject]) {
height = params["height"] as! Double
296
struct RightSquarePyramid {
let rightPyramid: RightPyramid
let edgeB: Double
init(params: [String:AnyObject]) {
edgeB = params["edgeB"] as! Double
let area = edgeB * edgeB
var extendedParams = params
extendedParams["baseArea"] = area as AnyObject
rightPyramid = RightPyramid(params: extendedParams)
}
}
struct RightTrianglePyramid {
let rightPyramid: RightPyramid
let baseB: Double
let heightB: Double
init(params: [String:AnyObject]) {
baseB = params["baseB"] as! Double
heightB = params["heightB"] as! Double
let area = 0.5 * baseB * heightB
var extendedParams = params
extendedParams["baseArea"] = area as AnyObject
rightPyramid = RightPyramid(params: extendedParams)
}
}
297
struct RightSquarePyramid {
let rightPyramid: RightPyramid
// Some code goes here
}
struct RightTrianglePyramid {
let rightPyramid: RightPyramid
// Some code goes here
}
SOLUTION 2
In this approach you use a protocol and one intermediate structure
(RightPyramidData):
protocol RightPyramid {
// Error:
// Protocols cannot require properties to be immutable;
298
init(params: [String:AnyObject]) {
height = params["height"] as! Double
let baseArea = params["baseArea"] as! Double
volume = 1.0 / 3.0 * baseArea * height
}
}
init(params: [String:AnyObject]) {
edgeB = params["edgeB"] as! Double
let area = edgeB * edgeB
var extendedParams = params
extendedParams["baseArea"] = area as AnyObject
let rightPyramidData = RightPyramidData(
params: extendedParams
)
volume = rightPyramidData.volume
height = rightPyramidData.height
}
}
init(params: [String:AnyObject]) {
baseB = params["baseB"] as! Double
heightB = params["heightB"] as! Double
let area = 0.5 * baseB * heightB
var extendedParams = params
extendedParams["baseArea"] = area as AnyObject
299
300
SOLUTION 3
This solution shows how you can get rid of intermediate structure using
protocol extension to provide default data extracting implementation.
protocol RightPyramid {
var volume: Double { get }
var height: Double { get }
}
extension RightPyramid {
static func parseFields(
params: [String:AnyObject]
) -> (Double, Double) {
let height = params["height"] as! Double
let baseArea = params["baseArea"] as! Double
let volume = 1.0 / 3.0 * baseArea * height
init(params: [String:AnyObject]) {
edgeB = params["edgeB"] as! Double
let area = edgeB * edgeB
var extendedParams = params
extendedParams["baseArea"] = area as AnyObject
(volume, height) = RightSquarePyramid.parseFields(
params: extendedParams
)
}
}
init(params: [String:AnyObject]) {
baseB = params["baseB"] as! Double
heightB = params["heightB"] as! Double
301
302
303
304
SECTION 3
Generics
With generics you can write very flexible and reusable code without
duplicating it only because you want it to work with different type(s). In
simple words, generic is an adjustable stamp or template you can use to
generate identical code with some minor type changes. Generics help
you to stop repeating yourself.
Generic functions
Generic functions can work with any type. Any time you have a
functionality common for different types you can write a universal
(generic) code where instead of real type a placeholder type is used. In
Swift a placeholder type, known as a type parameter, specify its name,
and is written immediately after the function’s name, between a pair of
matching angle brackets (such as <T> for T type parameter). The
generic version of the function uses a placeholder type name (T, in this
case) instead of an actual type name (such as Int, or String). The
actual type to use in place of T is determined every time a function is
used.
305
arrayReverse(&arrayString)
for i in 0..<count/2 {
temp = array[i]
array[i] = array[count - 1 - i]
array[count - 1 - i] = temp
}
}
print(arrayString)
// Prints:
// ["five", "four", "three", "two", "one"]
arrayReverse(&arrayInt)
306
for i in 0..<count/2 {
temp = array[i]
array[i] = array[count - 1 - i]
array[count - 1 - i] = temp
}
}
print(arrayInt)
// Prints:
// [5, 4, 3, 2, 1]
protocol P {
func toString() -> String
}
struct A: P {
var v: Int
307
struct B {
var v: Int
}
var x = A(v: 5)
preetyPrint(object: x)
var y = B(v: 5)
// Error:
// Global function 'preetyPrint(object:)' requires that 'B'
conform to 'P'
//preetyPrint(object: y)
Generic types
Generic types are maybe even more often used than generic functions.
The most basic examples are quite natural: basic data structures like
arrays, dictionaries, stacks or queues. As an example, take a look at a
very basic implementation of a priority queue:
struct PriorityQueue<T> {
struct Item<T> {
var item: T
var priority: Int
}
308
var highestPriorityIndex = 0
var highestPriorityValue = items[0].priority
pqS.push("five", withPriority: 5)
pqS.push("two", withPriority: 2)
pqS.push("one", withPriority: 1)
pqS.push("four", withPriority: 4)
pqS.push("three", withPriority: 3)
print(pqS.pop() ?? "undefined")
print(pqS.pop() ?? "undefined")
print(pqS.pop() ?? "undefined")
print(pqS.pop() ?? "undefined")
print(pqS.pop() ?? "undefined")
print(pqS.pop() ?? "undefined")
one
two
three
four
five
undefined
pqI.push(55, withPriority: 5)
309
pqI.push(22, withPriority: 2)
pqI.push(11, withPriority: 1)
pqI.push(44, withPriority: 4)
pqI.push(33, withPriority: 3)
print(pqI.pop() ?? "undefined")
print(pqI.pop() ?? "undefined")
print(pqI.pop() ?? "undefined")
print(pqI.pop() ?? "undefined")
print(pqI.pop() ?? "undefined")
print(pqI.pop() ?? "undefined")
will print:
55
44
33
22
11
undefined
extension PriorityQueue {
var itemWithHighestPriority: T? {
guard items.count > 0 else {
return nil
}
var highestPriorityIndex = 0;
var highestPriorityValue = items[0].priority;
310
}
}
return items[highestPriorityIndex].item
}
}
pq.push("five", withPriority: 5)
pq.push("two", withPriority: 2)
pq.push("one", withPriority: 1)
print(pq.itemWithHighestPriority ?? "undefined")
one
protocol Resetable {
associatedtype ItemType
mutating func reset(
toValue value: ItemType, withPriority priority: Int
)
}
Now you can use the extended protocol either in nongeneric way:
311
var bestPriorityIndex = 0;
var bestPriorityValue = items[0].priority;
print(pqs.pop() ?? "undefined")
// Prints: none
312
print(pqs.pop() ?? "undefined")
// Prints: two
or in generic way:
var highesPriorityIndex = 0;
var highesPriorityValue = items[0].priority;
313
print(pqp.pop() ?? "undefined")
// Prints: none
print(pqp.pop() ?? "undefined")
// Prints: two
314
315
316
CHAPTER 10
Miscellaneous topics
318
SECTION 1
This is the end of this book but I hope not your last adventure with
Swift. Reading and practicing knowledge you have gained so far, you
will have understanding of most basic Swift's "building blocks". Starting
from variables, through extensions and ending on protocols and
generics, now you are ready to try to make a code on your own or try
something else like making app for iOS.
You know a lot but there is still even more to discover. In this chapter I
will show you few concepts which you don't have to know at this
moment but which show how broad Swift is. It is simply to stimulate
your curiosity and encourage you to get to know it on your own
discovering new features and areas of application.
319
320
SECTION 2
case-let pattern
The keyword case used in other than switch statements looks at first
sight strange and awkward. However, once you get used to it, you will
never want to throw it away. The key to accept case-let is to stop
thinking that case is inseparable linked with switch. Rather, see it as
a situation where case is used by switch. If so, why other statements,
like if, guard or for, may not use it as well?
To make this chapter's contents clear you may find helpful quick
refreshment of Section 2: switch - case statement from Chapter 5:
Tuples, switch and extensions, where the following complex pattern
matching example summarizes switch-case syntax:
point2D = (2.5, 2)
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...")
}
// Prints:
// Point (2.5, 2.0) from a 2D subspace
321
As you can see, inside switch-case you can bind values to constants
or variables (with let or var keyword). You can also specify additional
condition with where keyword.
enum Action {
case turnLeftDegree (Double),
turnRightDegree (Double),
makeForwardSteps (Int),
makeBackwardSteps (Int),
saySomething (String)
}
switch currentAction {
case .turnLeftDegree(let degree):
print("Turn left by \(degree) degree")
case let .turnRightDegree(degree):
print("Turn right by \(degree) degree")
case .makeForwardSteps(let steps):
print("Make \(steps) step(s) forward")
case let .makeBackwardSteps(steps):
print("Make \(steps) step(s) backward")
case let .saySomething(text):
print("Say: \(text)")
}
// Prints:
// Make 10 step(s) forward
322
IF-CASE-LET
An alternate form:
IF-CASE-LET-WHERE
323
GUARD-CASE-LET[-WHERE]
// Prints:
// No match or fail at condition check
FOR-CASE-LET[-WHERE]
Combining for and case can also let you iterate on a collection
conditionally. Using for-case-let is semantically similar to using a
for loop and wrapping its whole body in an if-case block: in result it
will only iterate and process the elements that match the pattern.
// Prints:
// Long step ahead: 50 steps
// Long step ahead: 100 steps
FOR-WHERE
324
Note that for without the case pattern matching part but preserving
where part is also a valid Swift syntax:
325
326
Bibliography
328
Bibliography
[ARD] Arduino
329
/
2. Audrey Tam, SwiftUI Property Wrappers, retrieved 2021-05-21,
https://www.raywenderlich.com/21522453-swiftui-property-
wrapper
330
s
t
331