Swift Learning (2) - The Basics (Fully Ver.)
Swift
includes all the basic data types from C
and Objective-C
. Int
represents integer values; Double
and Float
represent floating-point values; Bool
is for boolean values; String
is for text data. Swift
also provides three basic collection types: Array
, Set
, and Dictionary
.
Just like in C
, Swift
uses variables to store values and associates them with variable names. In Swift
, constants—variables whose values cannot be changed—are widely used, and they are even more powerful than constants in C
. If the value you are working with does not need to change, using a constant makes your code safer and more clearly expresses your intent.
In addition to familiar types, Swift
introduces higher-level data types not found in Objective-C
, such as tuples. Tuples allow you to create or pass around groups of values, for example, returning multiple values from a function.
Swift
also introduces an Optional type to handle cases where a value might be missing. An Optional is either “there is a value, and it equals x” or “there is no value at all.” Optionals are similar to using nil
in Objective-C
, but they can be used with any type, not just classes. Optionals are safer and more expressive than nil
pointers in Objective-C
, and they are an important part of many of Swift’s powerful features.
Swift
is a type-safe language, which means it helps you be clear about the types of values your code can work with. If your code needs a String
parameter, type safety prevents you from accidentally passing an Int
. Similarly, if your code needs a String
, type safety prevents you from passing an optional String
by mistake. Type safety helps developers catch and fix errors early during development.
Constants and Variables
Constants and variables associate a name (like testVal) with a value of a specified type (like “Jensen”). The value of a constant cannot be changed once set, while the value of a variable can be changed freely.
Declaring Constants and Variables
Constants and variables must be declared before use. Use let
to declare a constant and var
to declare a variable.
let maximumNumberOfLoginAttempts = 10 // Constant for the total number of login attempts allowed
var currentLoginAttempt = 0 // Variable for the current number of login attempts
The above two lines can be understood as: “Declare a new constant named maximumNumberOfLoginAttempts and give it a value of 10. Then, declare a variable named currentLoginAttempt and initialize it to 0.”
You can declare multiple constants or variables in a single line, separated by commas.
var x = 0.0, y = 0.0, z = 0.0
Note
If a value in your code does not need to change, declare it as a constant using
let
. Only declare values as variables if they need to change.
Type Annotations
When declaring constants or variables, you can add a type annotation to specify the type of value to be stored. To add a type annotation, write a colon and a space after the name, followed by the type name.
var welcomeMessage: String // Adds a type annotation to welcomeMessage, indicating it can store a String value
// The colon in the declaration means “is of type ...”, so welcomeMessage can now be set to any string.
welcomeMessage = "Hello!"
You can also define multiple variables of the same type in one line, separated by commas, and add a type annotation after the last variable name.
var red, green, yellow: Double
Naming Constants and Variables
Constant and variable names can include almost any character, including Unicode
characters.
let π = 3.141592654
let 你好 = "Hello, world!"
let 🐮🍺 = "666"
There are exceptions: names cannot contain mathematical symbols, arrows, reserved (or illegal) Unicode code points, line and tab characters. They also cannot begin with a number, but numbers can appear elsewhere in the name.
Once a constant or variable has a type annotation, you cannot redeclare it with the same name or change its type. Also, Swift
does not allow converting between variables and constants.
Note
If you need to use a reserved Swift keyword as a constant or variable name, you can enclose it in backticks (`). However, developers should avoid using reserved keywords as names unless absolutely necessary.
You can change the value of an existing variable to another value of the same type.
var friendlyWelcome = "Hello!"
friendlyWelcome = "Benjour!" // friendlyWelcome is now “Bonjour!”
friendlyWelcome = "你好!" // friendlyWelcome is now “Welcome!”
// Unlike variables, the value of a constant cannot be changed once set. Attempting to do so will result in a compile-time error.
let languageName = "Swift"
//languageName = "C++" // Uncommenting this line will cause a compile error: Cannot assign to value: 'languageName' is a 'let' constant
Printing Constants and Variables
print(friendlyWelcome) // Output: 你好!
---
output: 你好!
The separator
and terminator
parameters have default values, so you can omit them when calling this function.
By default, the print function ends the current line with a newline character. If you do not want a newline, pass an empty string to the terminator
parameter.
print("Line1", terminator: "")
print("Line2")
---
output: Line1Line2
Swift
uses string interpolation to insert constant or variable names as placeholders into longer strings. Swift replaces these placeholders with the current value of the constant or variable. Place the name inside parentheses and prefix it with a backslash.
print("The current login attempt is \(currentLoginAttempt), you have \(maximumNumberOfLoginAttempts) login attempts in total.")
---
output: The current login attempt is 0, you have 10 login attempts in total.
Comments
Comments in Swift
are very similar to those in C
. Single-line comments start with double slashes //
.
You can also write multi-line comments, which start with /*
and end with */
.
/* This is a comment
that spans multiple lines */
Unlike C
, Swift’s multi-line comments can be nested within other multi-line comments.
/* This is the first line of the outer comment
/* This is the inner comment */
This is the last line of the outer comment */
By using nested multi-line comments, you can quickly comment out large blocks of code, even if they already contain multi-line comment blocks.
Semicolons
Unlike most other programming languages, Swift
does not require you to end every statement with a semicolon (;
). Of course, you can add semicolons if you prefer, but there is one case where you must use them: when writing multiple independent statements on a single line.
let pig = "🐷"; print(pig)
---
output: 🐷
Integers
Integers are numbers without a fractional part, such as 43 and -23. Swift
provides 8-, 16-, 32-, and 64-bit signed and unsigned integer types. These types are named similarly to those in C
, such as UInt8
for an 8-bit unsigned integer and Int32
for a 32-bit signed integer. Like other Swift types, integer types use capitalized names.
Integer Ranges
You can access the min
and max
properties of different integer types to get their minimum and maximum values.
let minValue = UInt8.min
let maxValue = UInt8.max
print("The minimum value of UInt8 is: \(minValue)\nThe maximum value of UInt8 is: \(maxValue)")
---
output: The minimum value of UInt8 is: 0
The maximum value of UInt8 is: 255
The types of the values returned by min
and max
are the same as the integer type they belong to. For example, UInt8.min
returns a value of type UInt8
.
Int
Generally, you do not need to specify the length of an integer. Swift
provides a special integer type, Int
, whose length matches the native word size of the current platform:
On 32-bit platforms, Int is the same as Int32
On 64-bit platforms, Int is the same as Int64
UInt
Similarly, Swift
provides a special unsigned type, UInt
, whose length matches the native word size of the current platform:
On 32-bit platforms, UInt is the same as UInt32
On 64-bit platforms, UInt is the same as UInt64
Note
Avoid using UInt unless you specifically need to store an unsigned integer with the platform’s native word size. Otherwise, prefer Int for better code reusability, to avoid conversions between different numeric types, and to match type inference.
Floating-Point Numbers
Floating-point numbers are numbers with a fractional part, such as 3.141592654, 0.1, and -98.09.
Floating-point types can represent a much wider range of values than integer types and can store numbers larger or smaller than those that fit in an Int. Swift
provides two signed floating-point types:
Double represents a 64-bit floating-point number. Use this type when you need to store very large or high-precision floating-point values.
Float represents a 32-bit floating-point number. Use this type when precision is not as important.
Note
Double has at least 15 decimal digits of precision, while Float has only 6. Which type you choose depends on the range of values your code needs to handle. When both types are suitable, Swift always chooses Double.
Type Safety and Type Inference
Swift
is a type-safe language. Type safety means the language helps you be clear about the types of values your code is working with.
Because Swift
is type-safe, it performs type checks when compiling code and marks mismatched types as errors. This helps developers catch errors early.
When working with different types of values, type checking helps prevent mistakes. However, this does not mean you must always specify the type when declaring constants and variables. If you do not specify a type, Swift
uses type inference to choose an appropriate type. Type inference works by examining the value you assign.
Type inference is especially useful when you assign an initial value as you declare a constant or variable. Assigning a literal value (such as 0.1 or 3.141592654) triggers type inference.
let meaningOfLife = 42 // meaningOfLife is inferred to be of type Int
let pi = 3.141592654 // pi is inferred to be of type Double; Swift always chooses Double for floating-point literals
If an expression contains both integers and floating-point numbers, it is always inferred as Double
.
let anotherPi = 3 + 0.141592654 // The literal 3 has no explicit type, but the presence of a floating-point literal causes the expression to be inferred as Double
Numeric Literals
Integer literals can be written as:
A decimal number, with no prefix
A binary number, with a
0b
prefix; an octal number, with a0o
prefix; a hexadecimal number, with a0x
prefix
All of the following integer literals have a decimal value of 17:
let decimalInteger = 17
let binaryInteger = 0b10001 // Binary 17
let octalInteger = 0o21 // Octal 17
let hexadecimalInteger = 0x11 // Hexadecimal 17
There must be at least one decimal or hexadecimal digit on either side of the decimal point. Decimal floating-point numbers can have an optional exponent, specified by an uppercase or lowercase e
; hexadecimal floating-point numbers must have an exponent, specified by an uppercase or lowercase p
.
Decimal: 1.25e2 means 1.2510^2, which equals 125.0; Hexadecimal: 0xFp-2 means 152^-2, which equals 3.75
The following floating-point literals all equal 12.1875 in decimal:
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
Numeric literals can include extra formatting to improve readability. Both integers and floating-point numbers can include extra zeros and underscores, which do not affect the literal’s value.
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
Numeric Type Conversion
Generally, even if you know that integer constants and variables are non-negative, use Int. Only use other integer types when necessary, such as when working with externally defined data of a specific length or for performance/memory optimization. Using explicitly sized types helps catch overflow and signals that you are working with special data.
Integer Conversion
Different integer types can store different ranges of numbers. An Int8
constant or variable can store values from -128 to 127; a UInt8
constant or variable can store values from 0 to 255. If a value exceeds the range, the compiler will report an error.
// let cannotBeNegative: UInt8 = -1 // Uncommenting this line will cause an error: Negative integer '-1' overflows when stored into unsigned type 'UInt8'
// let tooBig: Int8 = Int8.max + 1 // Uncommenting this line will cause an error: Arithmetic operation '127 + 1' (on type 'Int8') results in an overflow
Developers need to choose numeric type conversions as appropriate. This selective use helps prevent implicit conversion errors and makes conversion intent clear in code.
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one) // Here, UInt16(one) creates a new UInt16 value initialized with one, allowing the two UInt16 values to be added
print("UInt8 converted to UInt16: ", twoThousandAndOne)
---
output: UInt8 converted to UInt16: 2001
SomeType(ofInitialValue)
is the default way to call a Swift initializer with an initial value. Internally, UInt16
has an initializer that accepts a UInt8
value, so you can use an existing UInt8
to create a new UInt16
.
Integer and Floating-Point Conversion
Conversion between integers and floating-point numbers must be explicit.
let three = 3
let pointOneFourOneFiveNine = 0.14159
let simplePi = Double(three) + pointOneFourOneFiveNine // simplePi is 3.14159, inferred as Double
print("Integer and floating-point conversion: ", simplePi)
---
output: Integer and floating-point conversion: 3.14159
In the example above, the value of three
is used to create a Double
value; otherwise, the types on both sides of the plus sign would not match.
The reverse conversion from floating-point to integer works the same way; integer types can be initialized with Double
or Float
values.
let roundPi = Int(simplePi) // roundPi is 3, inferred as Int
print("Floating-point converted to integer: ", roundPi)
---
output: Floating-point converted to integer: 3
When initializing a new integer value this way, the floating-point value is truncated (e.g., 4.75 becomes 4, -3.9 becomes -3).
Note
Combining numeric constants and variables is different from combining numeric literals. The literal 3 can be added directly to the literal 0.14159 because numeric literals have no explicit type until the compiler needs to evaluate them.
Type Aliases
A type alias gives an existing type another name. Use the typealias
keyword to define a type alias.
Type aliases are useful when you want to give an existing type a more meaningful name.
Suppose you are working with data from an external resource of a specific length. After defining a type alias, you can use the alias anywhere you would use the original name.
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min // maxAmplitudeFound is now 0
print("The current value of maxAmplitudeFound is: ", maxAmplitudeFound)
---
output: The current value of maxAmplitudeFound is: 0
In the example above, AudioSample
is defined as an alias for UInt16
. Since it is an alias, AudioSample.min
is actually UInt16.min
.
Boolean Values
Swift
has a basic Boolean type called Bool
. Boolean values represent logical values and can only be true or false. Swift
has two Boolean constants: true
and false
.
let orangesAreOrange = true
let turnipsAreDelicious = false
The types of orangesAreOrange
and turnipsAreDelicious
are inferred as Bool
because their initial values are Boolean literals. As with Int
and Double
, if you assign true
or false
when creating a constant or variable, you do not need to declare it as Bool
.
Boolean values are especially useful in conditional statements, such as if
.
if turnipsAreDelicious {
print("Haha, turnips are delicious!")
} else {
print("Oh shit! Turnips are horrible!")
}
---
output: Oh shit! Turnips are horrible!
If you use a non-Boolean value where a Bool
is required, Swift’s safety mechanism will report an error.
let test = 1
//if test {} // Uncommenting this line will cause a compile error: Type 'Int' cannot be used as a boolean; test for '!= 0' instead
The following example is valid.
let i = 1
if i == 1 {} // i == 1 results in a Bool, so it passes Swift’s type check
Tuples
Tuples group multiple values into a single compound value. The values inside a tuple can be of any type and do not need to be the same type.
For example, (404, “Not Found”) is a tuple describing an HTTP status code.
let http404Error = (404, "Not Found") // http404Error is of type (Int, String)
The tuple (404, “Not Found”) combines an Int
and a String
to represent the two parts of an HTTP status code. This tuple can be described as “a tuple of type (Int, String).”
You can combine any types in any order in a tuple. You can also decompose a tuple into individual constants or variables and use them as normal.
let (statusCode, statusMessage) = http404Error
print("The http status code is \(statusCode).")
print("The http status message is \(statusMessage).")
---
output: The http status code is 404.
The http status message is Not Found.
If you only need part of a tuple, you can use an underscore (_) to ignore the rest, similar to Python.
let (httpStatusCode, _) = http404Error
print("The temp http status code is \(httpStatusCode).")
---
output: The temp http status code is 404.
You can also access individual elements of a tuple by index, starting from zero.
print("Accessing the first element of the tuple by index: ", http404Error.0)
print("Accessing the second element of the tuple by index: \(http404Error.1)")
---
output: Accessing the first element of the tuple by index: 404
Accessing the second element of the tuple by index: Not Found
You can name the elements of a tuple when defining it, and then access those elements by name.
let http500Status = (statusCode: 500, description: "Server Error")
print("Accessing the first element by tuple element name: \(http500Status.statusCode)")
print("Accessing the second element by tuple element name: \(http500Status.description)")
---
output: Accessing the first element by tuple element name: 500
Accessing the second element by tuple element name: Server Error
Tuples are useful when a function needs to return multiple values.
Optionals
Use Optionals to handle cases where a value might be missing. An Optional represents two possibilities:
Either there is a value, which you can access, or there is no value at all.
For example, Swift’s Int
type has an initializer that converts a String
to an Int
. However, not all strings can be converted to integers. The string “123” can be converted to 123, but “Hello, world” cannot.
let possibleNumber = "123"
var convertedNumber = Int(possibleNumber)
print("convertedNumber is inferred as type 'Int?' or 'optional Int':", convertedNumber!)
let possibleName = "Jensen"
convertedNumber = Int(possibleName)
print("There is a problem converting string \(possibleName) to a number:", convertedNumber)
---
output: convertedNumber is inferred as type 'Int?' or 'optional Int': 123
There is a problem converting string Jensen to a number: nil
---
warning: Expression implicitly coerced from 'Int?' to 'Any'
Because this initializer might fail, Int(xxx)
returns an Optional Int
, not a plain Int
. An Optional Int
is written as Int?
. The question mark indicates that the value is optional, but it cannot contain any other value, such as a Bool
or String
—only an Int
or nothing at all.
nil
You can assign nil
to an Optional variable to indicate that it has no value.
var serverResponseCode: Int? = 404 // serverResponseCode contains an optional Int value 404
serverResponseCode = nil // serverResponseCode now contains no value
print("Optional value serverResponseCode:", serverResponseCode)
---
output: Optional value serverResponseCode: nil
---
warning: Expression implicitly coerced from 'Int?' to 'Any'
Note that nil
cannot be used with non-optional constants and variables. If you need to handle missing values, declare them as Optionals. If you declare an Optional constant or variable without assigning a value, it is automatically set to nil
.
var surveyAnswer: String?
print("The value of an uninitialized optional constant/variable is:", surveyAnswer)
---
output: The value of an uninitialized optional constant/variable is: nil
---
warning: Expression implicitly coerced from 'String?' to 'Any'
if Statements and Forced Unwrapping
You can use an if
statement to compare an Optional to nil
to check whether it contains a value, using ==
or !=
.
If an Optional has a value, it is not equal to nil
.
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
} else {
print("convertedNumber is nil.")
}
---
output: convertedNumber is nil.
Once you are sure an Optional contains a value, you can add an exclamation mark (!
) after its name to access the value. This is called forced unwrapping.
let optionalInteger: Int? = 10086
if optionalInteger != nil {
print("The optional integer value is \(optionalInteger!)") // Accessing a non-existent optional value will cause a runtime error. Always make sure the optional contains a non-nil value before using !
} else {
print("The optional value is Nil.")
}
---
output: The optional integer value is 10086
Optional Binding
Optional binding lets you check whether an Optional contains a value, and if so, assign the value to a temporary constant or variable. Optional binding can be used in if
and while
statements.
if let actualNumber = Int(possibleNumber) {
print("'\(possibleNumber)' has an integer value of \(actualNumber).")
} else {
print("'\(possibleNumber)' could not be converted to an Integer.")
}
---
output: '123' has an integer value of 123.
The above code can be understood as: “If Int(possibleNumber) returns an Optional Int containing a value, create a new constant called actualNumber and assign the value to it. Since actualNumber has been initialized with the value, you do not need to use ! to access it.”
You can include multiple optional bindings or boolean conditions in a single if
statement, separated by commas. If any optional binding is nil or any boolean condition is false, the entire if
condition is false.
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) > \(secondNumber) < 100")
}
// Equivalent to the following code
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) > \(secondNumber) < 100")
}
}
}
---
output: 4 > 42 < 100
4 > 42 < 100
Note
When you use constants and variables to create an optional binding in an if statement, the value is only available inside the body of the if statement. In contrast, when you use constants and variables to create an optional binding in a guard statement, the value is available outside and after the guard statement.
Implicitly Unwrapped Optionals
Sometimes, in your program’s architecture, you can be certain that an Optional will always have a value after it is first set. In this case, checking and unwrapping the Optional every time is inefficient. This kind of Optional is defined as an implicitly unwrapped Optional. To declare one, replace the question mark (String?
) with an exclamation mark (String!
). Instead of putting the exclamation mark after the name when using it, you put it after the type when declaring it.
Implicitly unwrapped Optionals are useful when an Optional is guaranteed to have a value after being set for the first time, such as during class initialization in Swift.
An implicitly unwrapped Optional is just a normal Optional, but it can be used as a non-Optional without unwrapping.
let possibleString: String? = "An optional String."
let forcedString = possibleString!
print("Explicitly unwrapped optional:", forcedString)
let assumedString: String! = "An implicitly unwrapped optional String."
let implicitString: String = assumedString
print("Implicitly unwrapped optional:", implicitString)
---
output: Explicitly unwrapped optional: An optional String.
Implicitly unwrapped optional: An implicitly unwrapped optional String.
You can think of an implicitly unwrapped Optional as an Optional that is automatically unwrapped. When you use an implicitly unwrapped Optional, Swift first treats it as a normal Optional. If it cannot be used as an Optional, Swift forcibly unwraps it. In the code above, the value of assumedString
is forcibly unwrapped before being assigned to implicitString
, because implicitString
is a non-Optional String
.
In the following code, optionalString
has no explicit type, so it is inferred as a normal Optional.
let optionalString = assumedString
If you try to access the value of an implicitly unwrapped Optional when it is nil, you will trigger an error, just as you would by adding an exclamation mark to a normal Optional with no value.
You can check whether an implicitly unwrapped Optional contains a value just like a normal Optional.
if assumedString != nil {
print("Value of implicitly unwrapped optional:", assumedString!)
}
---
output: Value of implicitly unwrapped optional: An implicitly unwrapped optional String.
You can also use implicitly unwrapped Optionals in optional binding to check and unwrap their values.
if let definiteString = assumedString {
print("Value of implicitly unwrapped optional:", definiteString)
}
---
output: Value of implicitly unwrapped optional: An implicitly unwrapped optional String.
Note
If a variable might become nil later, do not use an implicitly unwrapped Optional. If you need to check for nil during the variable’s lifetime, use a normal Optional.
Error Handling
You can use error handling to respond to error conditions that may occur during program execution.
Compared to using Optionals to indicate success or failure, error handling can infer the reason for failure and propagate it to other parts of your program.
When a function encounters an error condition, it can throw an error. The place where the function is called can catch the error and handle it appropriately.
func canThrowAnError() throws {
// This function may throw an error
}
A function can throw errors by adding the throws
keyword to its declaration. When calling a function that can throw errors, you should prefix the expression with try
.
do {
try canThrowAnError()
// If no error is thrown, the following code is executed
} catch {
// If an error is thrown, the following code is executed
}
A do
statement creates a new scope, allowing errors to be propagated to one or more catch
clauses. The following pseudocode shows how error handling can be used to respond to different error conditions.
func makeASandwich() throws {
// function body
}
enum SandwichError: Error {
case outOfCleanDishes
case missingIngredients(ingredients: String)
}
do {
try makeASandwich()
// eatASandwich()
} catch SandwichError.outOfCleanDishes {
// washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
// If a missingIngredients error is thrown, buyGroceries(_:) is called with the associated value [ingredients] as a parameter.
// buyGroceries(ingredients)
}
---
warning: Immutable value 'ingredients' was never used; consider replacing with '_' or removing it
In the example above, the makeASandwich()
function throws an error if there are no clean dishes or if an ingredient is missing. Because makeASandwich()
can throw errors, the function call is wrapped in a try
expression. By wrapping the function call in a do
statement, any thrown errors are propagated to the provided catch
clauses.
Assertions and Preconditions
Assertions and preconditions are runtime checks. You can use them to check whether a necessary condition is met before executing subsequent code. If the Boolean condition in an assertion or precondition evaluates to true
, code execution continues as normal. If it evaluates to false
, the program is in an invalid state, execution stops, and the application terminates.
Assertions help developers find errors and incorrect assumptions during development, while preconditions help detect problems in production. Unlike error handling, assertions and preconditions are not for handling recoverable or expected errors. A failed assertion indicates that the program is in an invalid state and cannot recover.
Note
Using assertions and preconditions is not a way to prevent invalid program states. However, if an invalid state does occur, assertions and preconditions can force a check of data and program state, causing the program to terminate predictably (rather than being passively terminated by the system), and making the problem easier to debug. Once an invalid state is detected, execution stops, preventing further damage to the system.
The difference between assertions and preconditions is when they are checked: assertions are only checked in debug builds, while preconditions are checked in both debug and production builds. In production, assertion conditions are not evaluated. This means you can use many assertions during development, but they will have no effect in production.
Debugging with Assertions
You can call Swift’s standard library function assert(_:_:file:line:)
to write an assertion. Pass in an expression that evaluates to true
or false
and a message to display if the expression is false
.
let age = -3
assert(age >= 0, "A person's age cannot be less than zero.") // Because age < 0, the assertion will be triggered
In the code above, if age
is negative, as it is here, age >= 0
is false
, the assertion is triggered, and the application terminates.
If you do not need an assertion message, you can omit it as shown below.
assert(age >= 0)
If your code has already checked the condition, you can use the assertionFailure(_:file:line:)
function to indicate that the assertion has failed.
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
print("You can ride the ferris wheel.")
} else {
assertionFailure("A person's age can't be less than zero.")
}
Enforcing Preconditions
When a condition might be false, but code execution requires it to be true, use a precondition. For example, use a precondition to check for out-of-bounds indices or to ensure a correct parameter is passed to a function.
You can use the global precondition(_:_:file:line:)
function to write a precondition. Pass in an expression that evaluates to true
or false
and a message to display if the expression is false
.
var index = 0
precondition(index > 0, "Index must be greater than zero.") // Index must start from 1
You can call preconditionFailure(_:file:line:)
to indicate an error, for example, if a switch
statement falls through to the default
case when all valid values should be handled by other cases.
Note
If you compile your code in unchecked mode, preconditions are not checked. The compiler assumes all preconditions are always true and optimizes your code accordingly. However, the
fatalError(_:file:line:)
function always terminates execution, regardless of optimization settings.You can use
fatalError(_:file:line:)
during prototyping and early development, when you have method declarations but no implementations. You can writefatalError("Unimplemented")
as the implementation. BecausefatalError
is never optimized away, you can be sure that if your code reaches an unimplemented method, the program will terminate.