Swift Essentials: Loops, Closures, and More (Part 2)
In the first part of "100DaysOfSwiftUI," we explored the foundational building blocks of Swift — variables, data types, and more. Now, let's dive deeper into the control flow, loops, functions, and closures. These are essential concepts for building dynamic and interactive apps in SwiftUI, and they'll help us write more powerful and flexible code. Get ready to unleash the true potential of Swift and unlock new possibilities for your iOS development journey!
Control Flow: Making Decisions and Repeating Actions
In the world of programming, we need to create code that can respond to different situations and repeat actions efficiently. This is where control flow comes in. It allows us to make decisions based on conditions and execute code blocks repeatedly based on specific criteria.
Let’s explore some key elements of control flow in Swift:
If Statements and Comparisons
if
statements are used to execute code only if a certain condition is true. We use comparison operators to compare values and determine if a condition is met. Common comparison operators include:
-
>
(greater than) -
<
(less than) -
>=
(greater than or equal to) -
<=
(less than or equal to) -
==
(equal to) -
!=
(not equal to)
Here’s a simple example:
let age = 25
if age >= 18 {
print("You are an adult.")
}
This code checks if the variable age
is greater than or equal to 18. If it is, the message “You are an adult.” is printed.
Else and Else If
The else
keyword allows us to execute a different block of code if the if
condition is false. The else if
keyword can be used to create additional conditions to check.
Another simple example:
let temperature = 20
if temperature > 25 {
print("It's hot!")
} else if temperature < 15 {
print("It's cold!")
} else {
print("The temperature is pleasant.")
}
Logical Operators (&&, ||): Combining Conditions
Logical operators allow us to combine multiple conditions within an if statement.
-
&&
(AND): The&&
operator checks if both conditions are true. -
||
(OR): The||
operator checks if at least one condition is true.
Here’s an AND
example:
let isLoggedIn = true
let isAdmin = true
if isLoggedIn && isAdmin {
print("You have administrative privileges.")
}
Here’s an OR
example:
let isAdmin = false
let isSimpleUserWithGrantAccess = true
if isAdmin || isSimpleUserWithGrantAccess {
print("You can execute the action.")
}
Switch Statements: Evaluating Multiple Cases
switch
statements provide a more concise and readable way to handle multiple conditions, especially when dealing with a limited number of cases.
let trafficLight = "Red"
switch trafficLight {
case "Red":
print("Stop!")
case "Yellow":
print("Slow down.")
case "Green":
print("Go!")
default:
print("Invalid traffic light color.")
}
This code checks the value of trafficLight
. It matches the value against different cases and executes the corresponding code block. The default
case handles any values not explicitly matched.
I think this way to create switch
and case
is the strangest thing in Swift, because they’re on the same indentation. But this is the pattern, that’s okay.
Ternary Conditional Operator
The ternary operator is a shorthand way to write simple conditional expressions. It takes the form:
condition ? valueIfTrue : valueIfFalse
Here’s an example:
let age = 25
let message = age >= 18 ? "You are an adult." : "You are not an adult."
print(message)
This code checks the value of age
. If it’s greater than or equal to 18, the variable message is assigned “You are an adult.” Otherwise, it’s assigned “You are not an adult.”
Control flow statements are fundamental building blocks of any programming language. They allow us to create code that can make decisions and repeat actions, making our programs more interactive and efficient. In the next section, we’ll explore another important aspect of Swift programming: loops.
Loops: Repeating Actions
Loops are powerful tools in Swift that allow us to repeat blocks of code multiple times. They’re essential for automating tasks, iterating over collections, and handling repetitive operations. Let’s explore two common types of loops: for
loops and while loops.
For Loops
for
loops are used to iterate over a sequence of values, such as a range of numbers or elements in an array.
Here’s an example iterating over an array:
let fruits = ["Apple", "Banana", "Orange"]
for fruit in fruits {
print(fruit)
}
// Output:
// Apple
// Banana
// Orange
Here’s an example iterating over a range:
for number in 1...5 {
print(number)
}
// Output:
// 1
// 2
// 3
// 4
// 5
Swift provides a good way to create ranges. Very easy to use.
While Loops
while
loops are used to repeat a block of code as long as a specific condition is true.
var counter = 0
while counter < 5 {
print(counter)
counter += 1
}
// Output:
// 0
// 1
// 2
// 3
// 4
This loop continues to execute as long as the value of counter
is less than 5. Inside the loop, the value of counter
is printed, and then incremented by 1.
When we’re using while
we need to be careful to always control the variable who is deterministic to go out of the while. If we don’t increment the counter
in this example, the code won’t stop.
Break and Continue
Sometimes, we need more control over how loops execute. break
and continue
statements provide us with this control.
Break
The break
statement immediately exits a loop, regardless of the loop’s condition.
for number in 1...5 {
if number == 3 {
break
}
print(number)
}
// Output:
// 1
// 2
Continue
The continue
statement skips the current iteration of a loop and jumps to the next iteration.
for number in 1...5 {
if number == 3 {
continue
}
print(number)
}
// Output:
// 1
// 2
// 4
// 5
Loops are powerful tools for repeating code efficiently, and break
and continue
provide additional control over loop execution. Understanding how to use loops effectively is essential for building complex and efficient algorithms in Swift. Now, let’s move on to another important aspect of Swift programming: functions.
Functions: Reusable Code Blocks
Functions are like mini-programs within your Swift code. They allow you to encapsulate a block of code that performs a specific task, making your code more organized, reusable, and easier to maintain.
Let’s break down the fundamentals of functions in Swift.
Basic Function Definition
To define a function, we use the func
keyword followed by the function’s name and parentheses ()
. You can optionally add parameters within the parentheses. The code block that executes when the function is called is enclosed in curly braces {}
.
func greet(name: String) {
print("Hello, \(name)!")
}
greet(name: "Laura")
// Output: Hello, Laura!
In this example, we define a function named greet
that takes a single parameter name
of type String
. The function prints a greeting message to the console. We call the function by using its name followed by parentheses and passing the argument “Laura” for the name
parameter.
Returning Values
Functions can also return values. We use the return
keyword to specify the value to be returned. The return type is specified after the parentheses in the function definition.
func getGreet(name: String) -> String {
return "Hello, \(name)!"
}
let greet = getGreet(name: "Laura")
print(greet) // Output: Hello, Laura!
If you have a function with a single-line, you can suppress the return
keyword.
func getGreet(name: String) -> String {
"Hello, \(name)!"
}
let greet = getGreet(name: "Laura")
print(greet) // Output: Hello, Laura!
Default Values for Parameters
We can provide default values for function parameters, making our functions more flexible. If a value is not provided when calling the function, the default value will be used.
func getGreet(name: String, greeting: String = "Hello") -> String {
return "\(greeting), \(name)!"
}
let greetLaura = getGreet(name: "Laura")
print(greetLaura) // Output: Hello, Laura!
let greetAdrian = getGreet(name: "Adrian", greeting: "Hi")
print(greetAdrian) // Output: Hi, Adrian!
Functions That Can Throw Errors
In Swift, we can define functions that can throw errors using the throws
keyword. This indicates that the function might not complete successfully and could throw an error.
enum DivisionByZeroError: Error {
case zeroDivision
}
func divide(number1: Int, number2: Int) throws -> Int {
if number2 == 0 {
throw DivisionByZeroError.zeroDivision
}
return number1 / number2
}
To use functions with throws
keyword you need to use the following syntax to ensure you’ll capture the exception error:
do {
let result = try divide(number1: 10, number2: 0)
print(result)
} catch DivisionByZeroError.zeroDivision {
print("Error: Division by zero.")
} catch {
print("An error occurred.")
}
Here, the divide
function throws a DivisionByZeroError
if the number2
is 0. We use a do-catch
block to handle the error: the try
keyword indicates that the code inside the block might throw an error, and the catch
block handles the thrown error.
Functions are fundamental building blocks of Swift code, making our programs more modular, reusable, and efficient. Next, we’ll explore another powerful feature: closures.
Closures: Passing Code as Values
Closures are blocks of code that can be passed around like variables. They’re incredibly versatile, allowing you to encapsulate behavior and reuse it in different parts of your code. Imagine closures as self-contained mini-programs that can be executed on demand. In SwiftUI is very important to understand about this concept. Let’s dive into it.
Basic Closures
The simplest way to create a closure is to use curly braces {}
. A closure without parameters can be defined like this:
let greet = {
print("Hello, world!")
}
greet() // Output: Hello, world!
We assign this closure to a constant named greet
. To execute the closure, we call it like a function. If you think about the type of this closure, it’s () -> Void
, because it’s a function without parameter, and without return. It can be represented this way:
let greet: () -> Void = {
print("Hello, world!")
}
greet() // Output: Hello, world!
It’s the same.
Closures with Parameters and the “in” Keyword
Closures can also take parameters and have return values. We use the “in” keyword to separate the parameters and return type from the closure’s code block.
let greet = { (name: String) in
print("Hello, \(name)!")
}
greet("Laura")
// Output: Hello, Laura!
This closure has a parameter of type String
without return. The type can be represented as (String) -> Void
, and you can write this closure this way too:
let greet: (String) -> Void = { name in
print("Hello, \(name)!")
}
greet("Laura")
// Output: Hello, Laura!
Also, your closure can return a value.
let greet = { (name: String) -> String in
return "Hello, \(name)!"
}
let result = greet("Laura")
print(result) // Output: Hello, Laura!
Now the closure type is (String) -> String
. You can write the same closure above this way:
let greet: (String) -> String = { name in
return "Hello, \(name)!"
}
let result = greet("Laura")
print(result) // Output: Hello, Laura!
But where can we use it? In Swift is very common you see a function with a “completion handler”. Usualy, it is a closure that is called when some operations finish, like an animation or a data load. Let’s se an example:
func registerNewUser(name: String, completionHandler: (String) -> Void ) {
// Make a long operation like call an API or change a database
// After it, call the completion handler
completionHandler(name)
}
let greet = { (name: String) in
print("Hello, \(name)!")
}
registerNewUser(name: "Laura", completionHandler: greet)
// Output after the long operation: Hello, Laura!
We create a function called registerNewUser
with two parameters. The first one is the name
and the second one is a closure
that expect a parameter of type String
and without return represented with Void
keyword.
Shorthand Syntax for Trailing Closures
Swift provides a shorthand syntax for trailing closures when the closure is the last argument and it has only a single expression. We can just omit the closure parameter declaration and embed it at the trailing. I’ll consider the same registerNewUser
function showed previously, and I’ll embed the greet
closure using the shorthand syntax.
registerNewUser(name: "Laura") { name in
print("Hello, \(name)!")
}
Very easy, and clear. You’ll see it a lot in SwiftUI.
Conclusion
We’ve covered a lot of exciting ground in this second part of “100DaysOfSwiftUI”! We’ve explored control flow, loops, functions, and closures — powerful features that allow us to create more dynamic and sophisticated Swift code (closures specially).
Mastering these concepts is like unlocking a new level of proficiency in Swift programming. These tools provide us with the ability to make decisions, repeat actions efficiently, and encapsulate reusable code blocks, which are crucial for building complex and interactive applications in SwiftUI.
As we continue our journey through the “100 Days of SwiftUI” challenge, remember that practice is key. Experiment with these concepts, try out different examples, and don’t hesitate to ask questions. The more you practice, the more comfortable you’ll become with these essential tools.
I encourage you to explore further, and remember, every line of code you write brings you closer to mastering Swift!
Stay tuned for the next part of the “100DaysOfSwiftUI” series, where we’ll delve into structs and access control, expanding our knowledge of Swift programming even more.