Concrete values are the opposite of optional values in that they always represent a value (i.e. they are never
nil
).
For example, Int
is a concrete type whilst Int?
is an optional type. To use Int?
as a concrete type, it can be unwrapped using if let
or guard let
.
An enumeration (or enum
) is a type that represents a range of possible options called cases. When a value is created based on an enumeration, it represents a single case. This is is a useful tool for representing a choice without needing to resort to magical strings or numbers.
Example: an enum
with cases representing a range of colours. One colour is selected and stored in a constant.
enum Colour {
case red
case yellow
case green
case blue
}
let myFavouriteColour = Colour.blue
Cases can have associated values; these are additional pieces of information that can be stored with the selected case. There can be none, one, or more associated values which can be of any type.
Example: an enum
of distance measurements.
enum Measurement {
case none
case kilometre(km: Double)
case mile(mi: Double, nautical: Bool)
case fuzzy(description: String)
}
let wgtnToNapier = Measurement.mile(mi: 168, nautical: false)
Associated values can be accessed using switch
statements or if case
/guard case
.
switch wgtnToNapier {
case .none: print("No distance specified.")
case .kilometre(let km): print("\(km) km — or \(km * 0.62137119) mi")
case .mile(let mi, let nautical):
let km = mi * (nautical ? 1.852 : 1.609)
let unitName = nautical ? "nautical " : ""
print("\(mi) \(unitName)mi — or \(km) km")
case .fuzzy(let description): print("Around \(fuzzy) away")
}
if case .none = wgtnToNapier {
print("No distance specified")
}
guard case .kilometre(let km) = wgtnToNapier else { break }
print("\(km) km — or \(km * 0.62137119) mi")
Generics allow you to write flexible and reusable code by defining functions, types, and protocols that can work with any data type while maintaining type safety. Instead of writing multiple versions of a function for different types, generics let you write one function or type definition that adapts to different types. You specify a placeholder type, typically represented by a single uppercase letter like T
, which gets replaced with an actual type when the code is used.
Example: a generic function for swapping two values. (This uses the inout
keyword which means that the parameters are mutable)
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var bill = "Bill"
var hank = "Hank"
swap(bill, hank)
print(bill) // Hank
Here, T
acts as a type placeholder, allowing the function to work with any type that supports assignment.
Generics are especially useful in collections, such as arrays and dictionaries which internally use generics to store elements of any type. For example, [Int]
and [String]
are both instances of the generic [Element]
type; when you create an array, Element
is swapped out for the appropriate concrete type. This ensures type safety because you can only store elements of a single specified type in the collection. Generics also play a key role in protocols with associated types, such as Comparable
, which allows sorting different data types while ensuring that elements are of a consistent type.
Another powerful feature of Swift generics is generic constraints, which allow you to limit the types that can be used with a generic function or type. For example, if you want a function to work only with types that conform to the Equatable
protocol, you can use T: Equatable
.
Example: a (silly) function that only accepts structs that conform to Equatable
.
func areEqual
This ensures that the function can use ==
to compare values of type T
. Similarly, generic classes and structs can be constrained to inherit from specific base classes or conform to protocols.
The term "magic numbers" refers to values whose use is seemingly unjustified except by a comment. That is to say, if a value requires a comment to explain what it means rather than being self-evident, it should probably be removed to improve the flexibility and robustness of the program.
For example, a number used in a calculation or condition might be considered magical if the number appears to make no sense. A 2
used when checking if a collection of numbers are even makes sense. On the other hand, some seemingly random number such as 324
used as a boundary may not make sense unless there is a comment to explain it.
In such a case, the number should be assigned to a constant with a meaningful name.
// Magical number.
if userInput >= 0 && userInput <= 324 { ... } // 324 looks like magic.
// Flexible and robust version.
let maximumValue = 324
if userInput >= 0 && userInput <= maximumValue { ... }
Although it adds a line of code, it makes the code much clearer. Additionally, the maximum value in the range can be changed by adjusting that single constant rather than having to scan through any of the rest of the code to replace that value.
Equivalent to null
in most languages or None
in Python, this represents the absence of a value. If an optional value contains no value, it is equivalent to nil
.
Values that either contain a concrete type or
nil
. When specifying types, optional values end with a question mark. For example, an optional String
is denoted as String?
.
Functions return optional values where it cannot be guaranteed that the function can return a valid value. For example, the Int()
function (technically an initializer method for the Int
type) can accept a String
as an argument; therefore, Int()
returns Int?
just in case the string does not represent a valid integer.
Furthermore, struct
s and class
es might define some attributes as optional in case they are not required for the rest of the object to function correctly.
To use optional values as concrete values, they need to be unwrapped using if let
or guard let
.
if let int = Int(string) {
print(int + 5)
}
guard let double = Double(string) else {
print("Invalid number.")
return
}
print(double / 2)
Internally, this is represented as an
enum
containing either of two possible values: Optional.some(T)
or Optional.none
; however, Swift has special syntactic sugar to make handling Optional.none
exactly the same as handling nil
directly.
Safe unwrapping is used when you need to use optional values as concrete values. In Swift, the keywords are if let
and guard let
.
Use if let
to create a new variable containing the value (if one exists) from an optional type. The constant can only be used inside that if
statement's code block. If the optional value was empty (i.e. contained
nil
), you can use an else
statement to handle the absence of the value gracefully.
Example: converting a string to a number using if let
. A message gets printed if the number is invalid.
if let number = Int(userInput) {
print("You typed in \(userInput).")
} else {
print("You did not enter a value.")
}
One limitation of if let
is that the concrete value can only be used inside the if
statement's code block. To be able to use the concrete value in the rest of the program at the current scope, use guard let
instead.
Unlike if let
, guard let
requires you to handle the case when the optional value is nil
. You must provide an else
block straight away and the block must exit the current scope. To do so:
exit(1)
to quit the program. (Remember to import Foundation)for
or while
loop, use break
or continue
:
break
completely ends the loop, regardless of whether the condition has been metcontinue
stops the current iteration of the loop but continues with the next; this is useful for allowing for
loops to completely finish.Note: these also apply to .forEach
return
to prematurely exit the functionnil
Example: converting a string to a number using guard let
. The code is contained inside a function that returns an optional value; in case the provided string is not usable, it returns nil
. Since readLine()
returns a String?
, this is also part of the guard let
statement.
func intInput(_ prompt: String) -> Int? {
print(prompt, terminator: "")
guard let userInput = readLine(), let number = Int(userInput) else {
print("Invalid value provided.")
return nil
}
print("User entered \(number).")
return number
}
Syntactic sugar is when a programming language provides syntax that make it easier to deal with. For example, the question mark used at the end of optional types (i.e. Int?
) instead of typing the full type (Optional<Int>
) is an example of syntactic sugar, shortening the amount of code you need to write.
Syntax is the set of rules that define the structure and arrangement of symbols, keywords, and punctuation in a programming language. It dictates how code must be written and formatted to be correctly interpreted and executed by a compiler or interpreter.
In Swift, all code in a project must have correct syntax or else the program will not run; on the other hand, Python only requires syntax be correct for lines of code that are actually run. This can mean bugs related to incorrect syntax might occur in Python code if and when certain sections of code are run; Swift eliminates this class of error.
A version of the
map
method whose closure returns an optional value rather than only a concrete value; this allows failable transformations to be skipped so that no erroneous value is added to the new collection.
The resulting collection's type is a collection of a concrete type with all nil
values skipped.
let strings = ["1", "2.0", "banana", "4"]
let numbers = strings.compactMap { string in
return Double(string) // This returns Double? — the third array item is skipped
}
print(numbers) // [1.0, 2.0, 4.0]
A method that filters items from one collection into a new collection based on a condition. The condition is evaluated inside a closure which must return Bool
.
Example: filtering to include only numbers divisible by 2.
let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { number in
return number.isMultiple(of: 2)
}
print(evenNumbers) // [2, 4, 6]
A method that transforms all items from one collection into new values stored in a new collection. The transformation (that is, how the original values should be modified) is performed using a closure.
let numbers = [1, 5, 10, 15]
let doubled = numbers.map { number in
return number * 2
}
print(doubled) // [2, 10, 20, 30]
Functions are reusable blocks of code that perform a specific task and can be called with inputs (parameters) to return an output. They help organize code, reduce repetition, and improve readability and maintainability in programming.
In Swift, a function can have a signature or can be a closure. Either way, functions are also treated as a type, passed to functions or stored as an attribute in a
struct
.
Functions can accept parameters. In Swift, parameters have names in addition to the name of the variable used within the function itself. The parameter names are considered part of the signature.
A function that is defined as part of an object is called a method.
Closures are blocks of code usually passed as parameters to other function calls. For example, the transformation in
map
,
filter
, and
reduce
is achieved using closures.
An initializer (note the American spelling) is a special method in struct
s and class
es
A function when it is defined as a behaviour within an object. Methods are called by adding a full stop after an object constant/variable or literal then calling the method like any other function.
Example: calling the uppercased
method on a String
constant (inside a call to print
):
let name = "Anahera Madeupname"
print(name.uppercased())
Example: calling the isMultipleOf
method on an Int
literal (inside a call to print
):
print(245.isMultipleOf(7))
Example: calling the forEach
method, passing in a closure on a collection of numbers.
let numbers = [1, 2, 3, 4, 5]
numbers.forEach { number in
print(number * 2)
}
See also: static methods.
A named function is the opposite of an anonymous function in that it is assigned a name either through a signature or being assigned to a constant/variable.
Example: both of the following functions are named input
. The first uses a signature, the second is a closure assigned to a constant.
// Signature version.
func input(_ prompt: String) -> String? {
print(prompt, terminator: "")
return readLine()
}
let userAge = input("Enter your age: ")
// Closure version.
let input: (String) -> (String?) = { prompt in
print(prompt, terminator: "")
return readLine()
}
let userAge = input("Enter your age: ")
A function/method signature is the first line of a named function, defining:
Example: a function called input
accepting a String
parameter and returning an optional String
:
func input(_ prompt: String) -> String?
Example: a throwing asynchronous function that returns an Int
.
func intInput(_ prompt: String) async throws -> Int
In Swift, the parameter names are considered part of the signature. This helps to distinguish between signatures that share the same name. When condensing signatures down (i.e. for documentation or sharing information with others), the parameter names are included, separated by colons.
Example: a signature written as func myFunction(_ a: Int, b: Double, forC c: String)
can be condensed to myFunction(_:b:forC)
.
-
-
-
-
-