Swift Language Reference
Day 120 part 2
Let’s now delve into the Language Reference. It should not be too long before I can start building apps again!
Lexical Structure
The lexical structure of Swift describes what sequence of characters form valid tokens of the language. These valid tokens form the lowest-level building blocks of the language and are used to describe the rest of the language in subsequent chapters. A token consists of an identifier, keyword, punctuation, literal, or operator.
Whitespace and Comments
Whitespace has two uses: to separate tokens in the source file and to help determine whether an operator is a prefix or postfix, but is otherwise ignored.
Comments are treated as whitespace by the compiler. Single line comments begin with //
and continue until a line feed (U+000A) or carriage return (U+000D). Multiline comments begin with /*
and end with */
. Nesting multiline comments is allowed, but the comment markers must be balanced.
Identifiers
Identifiers begin with an uppercase or lowercase letter A through Z, an underscore (_
), a noncombining alphanumeric Unicode character in the Basic Multilingual Plane, or a character outside the Basic Multilingual Plane that isn’t in a Private Use Area. After the first character, digits and combining Unicode characters are also allowed.
Keywords and Punctuation
-Keywords used in declarations: associatedtype
, class
, deinit
, enum
, extension
, fileprivate
, func
, import
, init
, inout
, internal
, let
, open
, operator
, private
, protocol
, public
, static
, struct
, subscript
, typealias
, and var
.
- Keywords used in statements:
break
,case
,continue
,default
,defer
,do
,else
,fallthrough
,for
,guard
,if
,in
,repeat
,return
,switch
,where
, andwhile
. - Keywords used in expressions and types:
as
,Any
,catch
,false
,is
,nil
,rethrows
,super
,self
,Self
,throw
,throws
,true
, andtry
. - Keywords used in patterns:
_
. - Keywords that begin with a number sign (
#`): `#available
,#colorLiteral
,#column
,#else
,#elseif
,#endif
,#error
,#file
,#fileLiteral
,#function
,#if
,#imageLiteral
,#line
,#selector
,#sourceLocation
, and#warning
. - Keywords reserved in particular contexts:
associativity
,convenience
,dynamic
,didSet
,final
,get
,infix
,indirect
,lazy
,left
,mutating
,none
,nonmutating
,optional
,override
,postfix
,precedence
,prefix
,Protocol
,required
,right
,set
,Type
,unowned
,weak
, andwillSet
. Outside the context in which they appear in the grammar, they can be used as identifiers.
Literals
A literal is the source code representation of a value of a type, such as a number or string. A literal doesn’t have a type on its own. Instead, a literal is parsed as having infinite precision and Swift’s type inference attempts to infer a type for the literal.
When specifying the type annotation for a literal value, the annotation’s type must be a type that can be instantiated from that literal value.
- Integer Literals: integer literals represent integer values of unspecified precision. By default, integer literals are expressed in decimal; you can specify an alternate base using a prefix. Binary literals begin with
0b
, octal literals begin with0o
, and hexadecimal literals begin with0x
. - Floating-Point Literals: floating-point literals represent floating-point values of unspecified precision. By default, floating-point literals are expressed in decimal (with no prefix), but they can also be expressed in hexadecimal (with a
0x
prefix). Unless otherwise specified, the default inferred type of a floating-point literal is the Swift standard library typeDouble
, which represents a 64-bit floating-point number. The Swift standard library also defines aFloat
type, which represents a 32-bit floating-point number. - String Literals: a string literal is a sequence of characters surrounded by quotation marks. A single-line string literal is surrounded by double quotation marks. String literals can’t contain an unescaped double quotation mark (
"
), an unescaped backslash (\
), a carriage return, or a line feed. A multiline string literal is surrounded by three double quotation marks. Unlike a single-line string literal, a multiline string literal can contain unescaped double quotation marks ("
), carriage returns, and line feeds. It can’t contain three unescaped double quotation marks next to each other.- The line break after the
"""
that begins the multiline string literal is not part of the string. The line break before the"""
that ends the literal is also not part of the string. To make a multiline string literal that begins or ends with a line feed, write a blank line as its first or last line. - A multiline string literal can be indented using any combination of spaces and tabs; this indentation is not included in the string. The
"""
that ends the literal determines the indentation: Every nonblank line in the literal must begin with exactly the same indentation that appears before the closing"""
; there’s no conversion between tabs and spaces. You can include additional spaces and tabs after that indentation; those spaces and tabs appear in the string. - In a multiline string literal, writing a backslash (
\
) at the end of a line omits that line break from the string. Any whitespace between the backslash and the line break is also omitted. - The value of an expression can be inserted into a string literal by placing the expression in parentheses after a backslash (
\
). The interpolated expression can contain a string literal, but can’t contain an unescaped backslash, a carriage return, or a line feed. - A string delimited by extended delimiters is a sequence of characters surrounded by quotation marks and a balanced set of one or more number signs (
#
). Special characters in a string delimited by extended delimiters appear in the resulting string as normal characters rather than as special characters. - String literals that are concatenated by the + operator are concatenated at compile time.
- The line break after the
Operators
The whitespace around an operator is used to determine whether an operator is used as a prefix operator, a postfix operator, or a binary operator. This behaviour is summarised in the following rules:
- If an operator has whitespace around both sides or around neither side, it’s treated as a binary operator. As an example, the
+++
operator ina+++b
anda +++ b
is treated as a binary operator. - If an operator has whitespace on the left side only, it’s treated as a prefix unary operator. As an example, the
+++
operator ina +++b
is treated as a prefix unary operator. - If an operator has whitespace on the right side only, it’s treated as a postfix unary operator. As an example, the
+++
operator ina+++ b
is treated as a postfix unary operator. - If an operator has no whitespace on the left but is followed immediately by a dot (
.
), it’s treated as a postfix unary operator. As an example, the+++
operator ina+++.b
is treated as a postfix unary operator(a+++ .b
rather thana +++ .b)
.
Types
In Swift, there are two kinds of types: named types and compound types. A named type is a type that can be given a particular name when it’s defined. Named types include classes, structures, enumerations, and protocols. A compound type is a type without a name, defined in the Swift language itself. There are two compound types: function types and tuple types. A compound type may contain named types and other compound types.
Type Annotation
A type annotation explicitly specifies the type of a variable or expression. Type annotations begin with a colon (:
) and end with a type, as the following examples show:
let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }
Type annotations can contain an optional list of type attributes before the type.
Type Identifier
A type identifier refers to either a named type or a type alias of a named or compound type.
There are two cases in which a type identifier doesn’t refer to a type with the same name. In the first case, a type identifier refers to a type alias of a named or compound type. For instance, in the example below, the use of Point
in the type annotation refers to the tuple type (Int, Int)
.
typealias Point = (Int, Int)
let origin: Point = (0, 0)
In the second case, a type identifier uses dot (.
) syntax to refer to named types declared in other modules or nested within other types.
Tuple Type
A tuple type is a comma-separated list of types, enclosed in parentheses.
We can use a tuple type as the return type of a function to enable the function to return a single tuple containing multiple values. We can also name the elements of a tuple type and use those names to refer to the values of the individual elements. An element name consists of an identifier followed immediately by a colon (:
).
All tuple types contain two or more types, except for Void
which is a type alias for the empty tuple type, ()
.
Function Type
A function type represents the type of a function, method, or closure and consists of a parameter and return type separated by an arrow (->
). The parameter type is comma-separated list of types. Because the return type can be a tuple type, function types support functions and methods that return multiple values.
A parameter of the function type () -> T
(where T
is any type) can apply the autoclosure
attribute to implicitly create a closure at its call sites.
A function type can have a variadic parameter in its parameter type. Syntactically, a variadic parameter consists of a base type name followed immediately by three dots (...
), as in Int...
. A variadic parameter is treated as an array that contains elements of the base type name.
To specify an in-out parameter, we must prefix the parameter type with the inout
keyword. We can’t mark a variadic parameter or a return type with the inout
keyword.
Function types that can throw an error must be marked with the throws
keyword, and function types that can rethrow an error must be marked with the rethrows
keyword. The throws keyword is part of a function’s type, and nonthrowing functions are subtypes of throwing functions.
- Restrictions for Nonescaping Closures: a parameter that’s a nonescaping function can’t be stored in a property, variable, or constant of type
Any
, because that might allow the value to escape. A parameter that’s a nonescaping function can’t be passed as an argument to another nonescaping function parameter. This restriction helps Swift perform more of its checks for conflicting access to memory at compile time instead of at runtime.
Day 121
Array Type
The Swift language provides the following syntactic sugar for the Swift standard library Array<Element>
type:
[type]
The elements of an array can be accessed through subscripting by specifying a valid index value in square brackets: someArray[0]
refers to the element at index 0.
We can create multidimensional arrays by nesting pairs of square brackets, where the name of the base type of the elements is contained in the innermost pair of square brackets. For example, we can create a three-dimensional array of integers using three sets of square brackets:
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
When accessing the elements in a multidimensional array, the left-most subscript index refers to the element at that index in the outermost array. The next subscript index to the right refers to the element at that index in the array that’s nested one level in. And so on. This means that in the example above, array3D[0]
refers to [[1, 2], [3, 4]]
, array3D[0][1]
refers to [3, 4]
, and array3D[0][1][1]
refers to the value 4
.
Dictionary Type
The Swift language provides the following syntactic sugar for the Swift standard library Dictionary<Key, Value>
type:
[key type: value type]
In other words, the following two declarations are equivalent:
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
The values of a dictionary can be accessed through subscripting by specifying the corresponding key in square brackets: someDictionary["Alex"]
refers to the value associated with the key "Alex"
. The subscript returns an optional value of the dictionary’s value type. If the specified key isn’t contained in the dictionary, the subscript returns nil
.
The key type of a dictionary must conform to the Swift standard library Hashable
protocol.
Optional Type
The Swift language defines the postfix ?
as syntactic sugar for the named type Optional<Wrapped>
, which is defined in the Swift standard library. In other words, the following two declarations are equivalent:
var optionalInteger: Int?
var optionalInteger: Optional<Int>
The type Optional<Wrapped>
is an enumeration with two cases, none
and some(Wrapped)
, which are used to represent values that may or may not be present. Any type can be explicitly declared to be (or implicitly converted to) an optional type. If you don’t provide an initial value when we declare an optional variable or property, its value automatically defaults to nil
.
If an instance of an optional type contains a value, we can access that value using the postfix operator !
. Using the !
operator to unwrap an optional that has a value of nil
results in a runtime error.
We can also use optional chaining and optional binding to conditionally perform an operation on an optional expression. If the value is nil
, no operation is performed and therefore no runtime error is produced.
Implicitly Unwrapped Optional Type
The Swift language defines the postfix !
as syntactic sugar for the named type Optional<Wrapped>
, which is defined in the Swift standard library, with the additional behaviour that it’s automatically unwrapped when it’s accessed. If we try to use an implicitly unwrapped optional that has a value of nil
, we will get a runtime error.
Because implicit unwrapping changes the meaning of the declaration that contains that type, optional types that are nested inside a tuple type or a generic type—such as the element types of a dictionary or array—can’t be marked as implicitly unwrapped.
Protocol Composition Type
A protocol composition type defines a type that conforms to each protocol in a list of specified protocols, or a type that is a subclass of a given class and conforms to each protocol in a list of specified protocols. Protocol composition types may be used only when specifying a type in type annotations, in generic parameter clauses, and in generic where clauses.
Protocol composition types have the following form:
Protocol1 & Protocol2
Opaque Type
An opaque type defines a type that conforms to a protocol or protocol composition, without specifying the underlying concrete type.
Opaque types appear as the return type of a function or subscript, or the type of a property. Opaque types can’t appear as part of a tuple type or a generic type, such as the element type of an array or the wrapped type of an optional.
Opaque types have the following form:
some constraint
The constraint is a class type, protocol type, protocol composition type, or Any
. A value can be used as an instance of the opaque type only if it’s an instance of a type that conforms to the listed protocol or protocol composition, or inherits from the listed class. Code that interacts with an opaque value can use the value only in ways that are part of the interface defined by the constraint.
Protocol declarations can’t include opaque types. Classes can’t use an opaque type as the return type of a nonfinal method.
Metatype Type
A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.
The metatype of a class, structure, or enumeration type is the name of that type followed by .Type
. The metatype of a protocol type is the name of that protocol followed by .Protocol
.
We can use the postfix self
expression to access a type as a value. For example, SomeClass.self
returns SomeClass
itself, not an instance of SomeClass
. And SomeProtocol.self
returns SomeProtocol
itself, not an instance of a type that conforms to SomeProtocol
at runtime.
We can call the type(of:)
function with an instance of a type to access that instance’s dynamic, runtime type as a value.
Self Type
The Self
type isn’t a specific type, but rather allows us to conveniently refer to the current type without repeating or knowing that type’s name.
Inside a nested type declaration, the Self
type refers to the type introduced by the innermost type declaration.
Type Inheritance Clause
A type inheritance clause is used to specify which class a named type inherits from and which protocols a named type conforms to. A type inheritance clause begins with a colon (:
), followed by a list of type identifiers.
Class types can inherit from a single superclass and conform to any number of protocols. When defining a class, the name of the superclass must appear first in the list of type identifiers, followed by any number of protocols the class must conform to.
A type inheritance clause in an enumeration definition can be either a list of protocols, or in the case of an enumeration that assigns raw values to its cases, a single, named type that specifies the type of those raw values.
Type Inference
Swift uses type inference extensively, allowing you to omit the type or part of the type of many variables and expressions in your code.
Expressions
In Swift, there are four kinds of expressions: prefix expressions, binary expressions, primary expressions, and postfix expressions. Evaluating an expression returns a value, causes a side effect, or both.
Prefix Expressions
Prefix expressions combine an optional prefix operator with an expression. Prefix operators take one argument, the expression that follows them.
In addition to the standard library operators, we can use &
immediately before the name of a variable that’s being passed as an in-out argument to a function call expression.
- Try Operator: a try expression consists of the
try
operator followed by an expression that can throw an error. An optional-try expression consists of thetry?
operator followed by an expression that can throw an error. If the expression does not throw an error, the value of the optional-try expression is an optional containing the value of the expression. Otherwise, the value of the optional-try expression isnil
. A forced-try expression consists of thetry!
operator followed by an expression that can throw an error. If the expression throws an error, a runtime error is produced.- A try expression can’t appear on the right-hand side of a binary operator, unless the binary operator is the assignment operator or the try expression is enclosed in parentheses.
Binary Expressions
Binary expressions combine an infix binary operator with the expression that it takes as its left-hand and right-hand arguments.
- Assignment Operator: the assignment operator
=
sets a new value for a given expression. The value of the expression is set to the value obtained by evaluating the value. If the expression is a tuple, the value must be a tuple with the same number of elements (nested tuples are allowed). Assignment is performed from each part of the value to the corresponding part of the expression. - Ternary Conditional Operator: the ternary conditional operator evaluates to one of two given values based on the value of a condition. It has the following form:
If the condition evaluates to true, the conditional operator evaluates the first expression and returns its value. Otherwise, it evaluates the second expression and returns its value. The unused expression is not evaluated. - Type-Casting Operators: there are four type-casting operators: the
is
operator, theas
operator, theas?
operator, and theas!
operator.- The
is
operator checks at runtime whether the expression can be cast to the specified type. It returnstrue
if the expression can be cast to the specified type; otherwise, it returnsfalse
. - The
as
operator performs a cast when it is known at compile time that the cast always succeeds, such as upcasting or bridging. Upcasting lets us use an expression as an instance of its type’s supertype, without using an intermediate variable. Bridging lets us use an expression of a Swift standard library type such asString
as its correspondingFoundation
type such asNSString
without needing to create a new instance. - The
as?
operator performs a conditional cast of the expression to the specified type. Theas?
operator returns an optional of the specified type. At runtime, if the cast succeeds, the value of expression is wrapped in an optional and returned; otherwise, the value returned isnil
. If casting to the specified type is guaranteed to fail or is guaranteed to succeed, a compile-time error is raised (that is, a warning ⚠️ ). - The
as!
operator performs a forced cast of the expression to the specified type. Theas!
operator returns a value of the specified type, not an optional type. If the cast fails, a runtime error is raised.
- The
Primary Expressions
Primary expressions are the most basic kind of expression. They can be used as expressions on their own, and they can be combined with other tokens to make prefix expressions, binary expressions, and postfix expressions.
- Literal Expression: a literal expression consists of either an ordinary literal (such as a string or a number), an array or dictionary literal, a playground literal, or one of the following special literals:
- An array literal is an ordered collection of values.
- A dictionary literal is an unordered collection of key-value pairs.
- A playground literal is used by Xcode to create an interactive representation of a color, file, or image within the program editor. Playground literals in plain text outside of Xcode are represented using a special literal syntax.
- Self Expression: the
self
expression is an explicit reference to the current type or instance of the type in which it occurs. In an initialiser, subscript, or instance method,self
refers to the current instance of the type in which it occurs. In a type method,self
refers to the current type in which it occurs. In a mutating method of a value type, we can assign a new instance of that value type toself
. - Superclass Expression: a superclass expression lets a class interact with its superclass.
- Closure Expression: a closure expression creates a closure, also known as a lambda or an anonymous function in other programming languages. Like a function declaration, a closure contains statements, and it captures constants and variables from its enclosing scope. It has the following form:
There are several special forms that allow closures to be written more concisely:- A closure can omit the types of its parameters, its return type, or both. If you omit the parameter names and both types, omit the
in
keyword before the statements. If the omitted types can’t be inferred, a compile-time error is raised. - A closure may omit names for its parameters. Its parameters are then implicitly named
$
followed by their position:$0
,$1
,$2
, and so on. - A closure that consists of only a single expression is understood to return the value of that expression. The contents of this expression are also considered when performing type inference on the surrounding expression.
escaping
ornonescaping
depends on the surrounding context of the expression. A closure expression isnonescaping
if it is called immediately or passed as anonescaping
function argument. Otherwise, the closure expression isescaping
. - A closure can omit the types of its parameters, its return type, or both. If you omit the parameter names and both types, omit the
- Capture Lists: by default, a closure expression captures constants and variables from its surrounding scope with strong references to those values. We can use a capture list to explicitly control how values are captured in a closure. A capture list is written as a comma-separated list of expressions surrounded by square brackets, before the list of parameters. If we use a capture list, we must also use the
in
keyword, even if we omit the parameter names, parameter types, and return type. The entries in the capture list are initialised when the closure is created.- If the type of the expression’s value is a class, we can mark the expression in a capture list with
weak
orunowned
to capture a weak or unowned reference to the expression’s value.
- If the type of the expression’s value is a class, we can mark the expression in a capture list with
- Implicit Member Expression: an implicit member expression is an abbreviated way to access a member of a type, such as an enumeration case or a type method, in a context where type inference can determine the implied type. It has the following form:
- Parenthesised Expression: a parenthesized expression consists of an expression surrounded by parentheses.
- Tuple Expression: a tuple expression consists of a comma-separated list of expressions surrounded by parentheses. Each expression can have an optional identifier before it, separated by a colon (
:
). A tuple expression can contain zero expressions, or it can contain two or more expressions. A single expression inside parentheses is a parenthesised expression. - Wildcard Expression: a wildcard expression (
_
) is used to explicitly ignore a value during an assignment. - Key-Path Expression: a key-path expression refers to a property or subscript of a type. We use key-path expressions in dynamic programming tasks, such as key-value observing. They have the following form:
ThetypeName
is the name of a concrete type, while thepath
consists of property names, subscripts, optional-chaining expressions, and forced unwrapping expressions. Each of these key-path components can be repeated as many times as needed, in any order.- At compile time, a key-path expression is replaced by an instance of the
KeyPath
class. Here’s an example: - The
typeName
can be omitted in contexts where type inference can determine the implied type. - The
path
can refer toself
to create the identity key path (\.self
). The identity key path refers to a whole instance, so you can use it to access and change all of the data stored in a variable in a single step. - The
path
can contain multiple property names, separated by periods, to refer to a property of a property’s value. - The
path
can include subscripts using brackets, as long as the subscript’s parameter type conforms to theHashable
protocol. - The value used in a subscript can be a named value or a literal. Values are captured in key paths using value semantics.
- The
path
can use optional chaining and forced unwrapping.
- At compile time, a key-path expression is replaced by an instance of the
- Selector Expression: a selector expression lets us access the selector used to refer to a method or to a property’s getter or setter in Objective-C. When creating a selector for a property’s getter, the property name can be a reference to a variable or constant property. In contrast, when creating a selector for a property’s setter, the property name must be a reference to a variable property only.
- Key-Path String Expression: a key-path string expression lets us access the string used to refer to a property in Objective-C, for use in key-value coding and key-value observing APIs. It has the following form:
At compile time, the key-path string expression is replaced by a string literal. Because the key path string is created at compile time, not at runtime, the compiler can check that the property exists and that the property is exposed to the Objective-C runtime.
Postfix Expressions
Postfix expressions are formed by applying a postfix operator or other postfix syntax to an expression. Syntactically, every primary expression is also a postfix expression.
- Function Call Expression: a function call expression consists of a function name followed by a comma-separated list of the function’s arguments in parentheses. If the function definition includes names for its parameters, the function call must include names before its argument values separated by a colon (
:
)- A function call expression can include a trailing closure in the form of a closure expression immediately after the closing parenthesis. The trailing closure is understood as an argument to the function, added after the last parenthesised argument.
- Initialiser Expression: an initialiser expression provides access to a type’s initialiser:
expression.init(initializer arguments)
. - Explicit Member Expression: an explicit member expression allows access to the members of a named type, a tuple, or a module. It consists of a period (
.
) between the item and the identifier of its member:expression.memberName
.- The members of a named type are named as part of the type’s declaration or extension.
- The members of a tuple are implicitly named using integers in the order they appear, starting from zero.
- If a period appears at the beginning of a line, it is understood as part of an explicit member expression, not as an implicit member expression.
- Postfix Self Expression: a postfix
self
expression consists of an expression or the name of a type, immediately followed by.self
. - Subscript Expression: a subscript expression provides subscript access using the getter and setter of the corresponding subscript declaration. To evaluate the value of a subscript expression, the subscript getter for the expression’s type is called with the index expressions passed as the subscript parameters. To set its value, the subscript setter is called in the same way.
- Forced-Value Expression: a forced-value expression unwraps an optional value that we are certain is not
nil
. - Optional-Chaining Expression: an optional-chaining expression provides a simplified syntax for using optional values in postfix expressions. The postfix
?
operator makes an optional-chaining expression from an expression without changing the expression’s value. Optional-chaining expressions must appear within a postfix expression, and they cause the postfix expression to be evaluated in a special way. If a postfix expression that contains an optional-chaining expression is nested inside other postfix expressions, only the outermost expression returns an optional type.
Day 122
Statements
In Swift, there are three kinds of statements: simple statements, compiler control statements, and control flow statements. Simple statements are the most common and consist of either an expression or a declaration. Compiler control statements allow the program to change aspects of the compiler’s behaviour and include a conditional compilation block and a line control statement.
Control flow statements are used to control the flow of execution in a program. There are several types of control flow statements in Swift, including loop statements, branch statements, and control transfer statements. Loop statements allow a block of code to be executed repeatedly, branch statements allow a certain block of code to be executed only when certain conditions are met, and control transfer statements provide a way to alter the order in which code is executed. In addition, Swift provides a do
statement to introduce scope, and catch
to handle errors, and a defer
statement for running cleanup actions just before the current scope exits.
Loop Statements
Loop statements allow a block of code to be executed repeatedly, depending on the conditions specified in the loop. Swift has three loop statements: a for-in
statement, a while
statement, and a repeat-while
statement.
- For-In Statement: a
for-in
statement allows a block of code to be executed once for each item in a collection (or any type) that conforms to theSequence
protocol.
ThemakeIterator()
method is called on the collection expression to obtain a value of an iterator type—that is, a type that conforms to theIteratorProtocol
protocol. The program begins executing a loop by calling thenext()
method on the iterator. If the value returned is notnil
, it is assigned to the item pattern, the program executes the statements, and then continues execution at the beginning of the loop. Otherwise, the program does not perform assignment or execute the statements, and it is finished executing the for-in statement. - While Statement: a
while
statement allows a block of code to be executed repeatedly, as long as a condition remains true.
A while statement is executed as follows:- The condition is evaluated. If
true
, execution continues to step 2. Iffalse
, the program is finished executing thewhile
statement. - The program executes the statements, and execution returns to step 1.
- The condition is evaluated. If
- Repeat-While Statement: a
repeat-while
statement allows a block of code to be executed one or more times, as long as a condition remains true.
A repeat-while statement is executed as follows:- The program executes the statements, and execution continues to step 2.
- The condition is evaluated. If
true
, execution returns to step 1. Iffalse
, the program is finished executing the repeat-while
statement.
Branch Statements
Branch statements allow the program to execute certain parts of code depending on the value of one or more conditions. Swift has three branch statements: an if
statement, a guard
statement, and a switch
statement.
- If Statement: an
if
statement is used for executing code based on the evaluation of one or more conditions. There are two basic forms of anif
statement, the first form allows code to be executed only when a condition is true (if condition { statements }
) while the second form of anif
statement provides an additionalelse
clause and is used for executing one part of code when the condition is true and another part of code when the same condition is false (if condition { statements to execute if condition is true } else { statements to execute if condition is false }
. Theelse
clause of anif
statement can contain anotherif
statement to test more than one condition. - Guard Statement: a
guard
statement is used to transfer program control out of a scope if one or more conditions aren’t met.
Theelse
clause of aguard
statement is required, and must either call a function with theNever
return type or transfer program control outside theguard
statement’s enclosing scope using one of the following statements:return
,break
,continue
,throw
. - Switch Statement: a
switch
statement allows certain blocks of code to be executed depending on the value of a control expression.
The control expression of theswitch
statement is evaluated and then compared with the patterns specified in each case. If a match is found, the program executes the statements listed within the scope of that case. The scope of each case can’t be empty. As a result, we must include at least one statement following the colon (:
) of each case label. Use a singlebreak
statement if you don’t intend to execute any code in the body of a matched case.- A
switch
case can optionally contain awhere
clause after each pattern. Awhere
clause is introduced by thewhere
keyword followed by an expression, and is used to provide an additional condition before a pattern in a case is considered matched to the control expression. - Patterns in a case can also bind constants using the
let
keyword (they can also bind variables using thevar
keyword). These constants (or variables) can then be referenced in a correspondingwhere
clause and throughout the rest of the code within the scope of the case. - A
switch
statement can also include a default case, introduced by thedefault
keyword. The code within a default case is executed only if no other cases match the control expression. A switch statement can include only one default case, which must appear at the end of the switch statement. - Switch Statements Must Be Exhaustive
- Switching Over Future Enumeration Cases: a nonfrozen enumeration is a special kind of enumeration that may gain new enumeration cases in the future. We can apply the
@unknown
attribute to the default case, which indicates that the default case should match only enumeration cases that are added in the future. - Execution Does Not Fall Through Cases Implicitly: after the code within a matched case has finished executing, the program exits from the switch statement. Program execution does not continue or “fall through” to the next case or default case.
- A
Labeled Statement
We can prefix a loop statement, an if
statement, a switch
statement, or a do
statement with a statement label, which consists of the name of the label followed immediately by a colon (:
). We can use statement labels with break
and continue
statements to be explicit about how we want to change control flow in a loop statement or a switch statement.
Control Transfer Statements
Control transfer statements can change the order in which code in our program is executed by unconditionally transferring program control from one piece of code to another. Swift has five control transfer statements: a break
statement, a continue
statement, a fallthrough
statement, a return
statement, and a throw
statement.
- Break Statement: a
break
statement ends program execution of a loop, anif
statement, or aswitch
statement. Abreak
statement can consist of only thebreak
keyword, or it can consist of thebreak
keyword followed by the name of a statement label. - Continue Statement: a
continue
statement ends program execution of the current iteration of a loop statement but does not stop execution of the loop statement. Acontinue
statement can consist of only thecontinue
keyword, or it can consist of thecontinue
keyword followed by the name of a statement label. - Fallthrough Statement: a
fallthrough
statement consists of thefallthrough
keyword and occurs only in acase
block of aswitch
statement. Afallthrough
statement causes program execution to continue from one case in a switch statement to the next case. Program execution continues to the next case even if the patterns of the case label do not match the value of the switch statement’s control expression. Afallthrough
statement can appear anywhere inside a switch statement, not just as the last statement of a case block, but it can’t be used in the final case block. It also cannot transfer control into a case block whose pattern contains value binding patterns. - Return Statement: a
return
statement occurs in the body of a function or method definition and causes program execution to return to the calling function or method. Program execution continues at the point immediately following the function or method call. Areturn
statement can consist of only thereturn
keyword, or it can consist of thereturn
keyword followed by an expression. - Throw Statement: a
throw
statement occurs in the body of a throwing function or method, or in the body of a closure expression whose type is marked with thethrows
keyword. Athrow
statement causes a program to end execution of the current scope and begin error propagation to its enclosing scope. The error that’s thrown continues to propagate until it’s handled by acatch
clause of ado
statement. The value of the expression must have a type that conforms to theError
protocol.
Defer Statement
A defer
statement is used for executing code just before transferring program control outside of the scope that the defer
statement appears in.
The statements within the defer
statement are executed no matter how program control is transferred.
If multiple defer
statements appear in the same scope, the order they appear is the reverse of the order they are executed. Executing the last defer statement in a given scope first means that statements inside that last defer statement can refer to resources that will be cleaned up by other defer statements.
The statements in the defer
statement can’t transfer program control outside of the defer statement.
Do Statement
The do
statement is used to introduce a new scope and can optionally contain one or more catch
clauses, which contain patterns that match against defined error conditions. Variables and constants declared in the scope of a do statement can be accessed only within that scope.
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
Like a switch
statement, the compiler attempts to infer whether catch
clauses are exhaustive. If such a determination can be made, the error is considered handled. Otherwise, the error can propagate out of the containing scope, which means the error must be handled by an enclosing catch
clause or the containing function must be declared with throws
.
Compiler Control Statements
Compiler control statements allow the program to change aspects of the compiler’s behaviour. Swift has three compiler control statements: a conditional compilation block, a line control statement, and a compile-time diagnostic statement.
- Conditional Compilation Block: a conditional compilation block allows code to be conditionally compiled depending on the value of one or more compilation conditions. Every conditional compilation block begins with the
#if
compilation directive and ends with the#endif
compilation directive. - Line Control Statement: a line control statement is used to specify a line number and filename that can be different from the line number and filename of the source code being compiled. Use a line control statement to change the source code location used by Swift for diagnostic and debugging purposes.
- Compile-Time Diagnostic Statement: A compile-time diagnostic statement causes the compiler to emit an error or a warning during compilation. A compile-time diagnostic statement has the following forms:
Availability Condition
An availability condition is used as a condition of an if
, while
, and guard
statement to query the availability of APIs at runtime, based on specified platforms arguments.
if #available(platform name version, ..., *) {
statements to execute if the APIs are available
} else {
fallback statements to execute if the APIs are unavailable
}
We use an availability condition to execute a block of code, depending on whether the APIs we want to use are available at runtime. The compiler uses the information from the availability condition when it verifies that the APIs in that block of code are available. The *
argument is required and specifies that on any other platform, the body of the code block guarded by the availability condition executes on the minimum deployment target specified by your target.
Declarations
A declaration introduces a new name or construct into our program.
Top-Level Code
The top-level code in a Swift source file consists of zero or more statements, declarations, and expressions. By default, variables, constants, and other named declarations that are declared at the top-level of a source file are accessible to code in every source file that is part of the same module.
Code Blocks
A code block is used by a variety of declarations and control structures to group statements together.
Import Declaration
An import declaration grants us access to symbols that are declared outside the current file. The basic form imports the entire module; it consists of the import
keyword followed by a module name.
Constant Declaration
A constant declaration introduces a constant named value into your program. Constant declarations are declared using the let keyword and have the following form:
let constantName: type = expression
A constant declaration defines an immutable binding between the constant name and the value of the initialiser expression; after the value of a constant is set, it cannot be changed. That said, if a constant is initialised with a class object, the object itself can change, but the binding between the constant name and the object it refers to can’t.
When a constant is declared at global scope, it must be initialised with a value. When a constant declaration occurs in the context of a function or method, it can be initialised later, as long as it is guaranteed to have a value set before the first time its value is read. When a constant declaration occurs in the context of a class or structure declaration, it is considered a constant property. Constant declarations are not computed properties and therefore do not have getters or setters.
To declare a constant type property, mark the declaration with the static
declaration modifier. A constant type property of a class is always implicitly final; you can’t mark it with the class
or final
declaration modifier to allow or disallow overriding by subclasses.
Variable Declaration
A variable declaration introduces a variable named value into our program and is declared using the var keyword.
- Stored Variables and Stored Variable Properties: the following form declares a stored variable or stored variable property:
We can define this form of a variable declaration at a global scope (1), in the local scope of a function (2), or in the context of a class or structure declaration (3). When a variable declaration of this form is declared at global scope or the local scope of a function (1,2), it is referred to as a stored variable. When it is declared in the context of a class or structure declaration (3), it is referred to as a stored variable property. - Computed Variables and Computed Properties:
When a variable declaration of this form is declared at global scope or the local scope of a function, it is referred to as a computed variable. When it is declared in the context of a class, structure, or extension declaration, it is referred to as a computed property.- The getter is used to read the value, and the setter is used to write the value. The setter clause is optional but if we provide a setter clause, we must also provide a getter clause.
- The setter name and enclosing parentheses is optional. If we provide a setter name, it is used as the name of the parameter to the setter. If we do not provide a setter name, the default parameter name to the setter is
newValue
.
- Stored Variable Observers and Property Observers: we can also declare a stored variable or property with
willSet
anddidSet
observers.
When a variable declaration of this form is declared at global scope or the local scope of a function, the observers are referred to as stored variable observers. When it is declared in the context of a class or structure declaration, the observers are referred to as property observers.- The initialiser expression is optional in the context of a class or structure declaration, but required elsewhere.
- The
willSet
anddidSet
observers provide a way to observe (and to respond appropriately) when the value of a variable or property is being set. The observers are not called when the variable or property is first initialised. Instead, they are called only when the value is set outside of an initialisation context. - A
willSet
observer is called just before the value of the variable or property is set. The new value is passed to thewillSet
observer as a constant, and therefore it can’t be changed in the implementation of thewillSet
clause. - The
didSet
observer is called immediately after the new value is set. In contrast to thewillSet
observer, the old value of the variable or property is passed to thedidSet
observer in case you still need access to it.
- Type Variable Properties: to declare a type variable property, we need to mark the declaration with the
static
declaration modifier. Classes can mark type computed properties with theclass
declaration modifier instead to allow subclasses to override the superclass’s implementation.
Type Alias Declaration
A type alias declaration introduces a named alias of an existing type into our program.
typealias name = existing type
Function Declaration
A function declaration introduces a function or method into your program. A function declared in the context of class, structure, enumeration, or protocol is referred to as a method.
func function name(parameters) -> return type {
statements
}
The type of each parameter must be included—it can’t be inferred. If we write inout
in front of a parameter’s type, the parameter can be modified inside the scope of the function.
A function definition can appear inside another function declaration. This kind of function is known as a nested function.
A nested function is nonescaping if it captures a value that is guaranteed to never escape—such as an in-out parameter—or passed as a nonescaping function argument. Otherwise, the nested function is an escaping function.
- Parameter Names: function parameters are a comma-separated list where each parameter has one of several forms. The order of arguments in a function call must match the order of parameters in the function’s declaration.
- A parameter has a name, which is used within the function body, as well as an argument label, which is used when calling the function or method. By default, parameter names are also used as argument labels.
- We can override the default behaviour for argument labels with one of the following forms:
- In-Out Parameters: In-out parameters are passed as follows:
- When the function is called, the value of the argument is copied.
- In the body of the function, the copy is modified.
- When the function returns, the copy’s value is assigned to the original argument.
If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).
If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).
You can also support me by buying Paul Hudson’s books from this Affiliate Link.
Anyways, thank you so much for reading!
Till the next one!