DEV Community

Fernando Martín Ortiz
Fernando Martín Ortiz

Posted on

Introduction to Swift - Part 1

Note: This article is part of the course Introduction to iOS using UIKit I've given many times in the past. The course was originally in Spanish, and I decided to release it in English so more people can read it and hopefully it will help them.

Swift overview

Swift is a programming language developed by Apple to be used in application development for Apple platforms, as a replacement for (or in combination to) Objective-C, its previous recommended language. This way, and using Xcode, we can develop apps for iOS, iPad OS, MacOS, WatchOS and TVOS. Since 2015, Swift is also Open Source, being maintained not just by Apple, but by a open development community.

Swift is multi-paradigm, which means we can use it to develop object oriented code (OOP), functional programming, scripting, etc. Swift adapts to our preferred programming style, although the community has agreed on some best practices and a way to develop code following mainly an object oriented approach.

Swift is modern, which means it includes features from other modern languages, as we'll see during this course. What's more, it continues being added more features during the years.

Swift is simple to learn, but it can be as complex and go as low level as we need it to go.

During this course we'll learn the Swift syntax and usage examples. But not in depth. We'll learn the necessary syntax in order to start developing iOS application. However, it's important to note that the language can be much more complex than what we'll learn here. This course isn't intended to be comprehensive, but to bring the needed tools to start.


Variables are present in most (if not all) programming languages, and are names (also called identifiers), which contain a value.
In Swift, variables are declared using the keyword var, followed by a = and the value it contains. For instance:

var name = "Fernando"
var street = "Rivadavia Av."
var catLegsCount = 4
Enter fullscreen mode Exit fullscreen mode

Something that could be curious in these declarations is that we aren't specifying their type. That's because Swift includes a feature called type inference. Type inference let us declare variables without having to be explicit about their types, because Swift is "intelligent" enough to figure out that a 4 is an Int, and that "Fernando" is a String.

We can be explicit

Of course, there are moments where it could be useful to be explicit about the data type of a variable. For instance, if we're talking about a price.

var price1 = 12
Enter fullscreen mode Exit fullscreen mode

We could want to be explicit about the type because the price is a decimal value, and it's not an Int. In Swift, we can specify the data type using : <DataType>. For example:

var price2: Double = 2.50
Enter fullscreen mode Exit fullscreen mode


Let's suppose we have a variable with a value that we know it won't change. For example, a cat has four legs. If we want to define the number of legs a cat has, we can use a constant. In order to so, we'll replace var by let, transforming that variable into a constant. A constant is a variable to which we can only associate a value once. If we want to update its value afterwards, we'll get an error.

var leavesInATree = 10
leavesInATree = 22
leavesInATree = 25 // We won't get any errors if we modify this value.
let catLegsCount = 4

// The next line won't compile.
// catLegsCount = 5 
Enter fullscreen mode Exit fullscreen mode

While naming a variable, it's important to be very explicit on its meaning. This means: do not abbreviate

lbl isn't a good variable name. label is.
chCnt isn't a good variable name. charactersCount is.
i isn't a good variable name. index is.

Every programming language has it's own "correct" way to write code with. We know it as best practices. In Swift, the best practice is to declare variables as constants, using let, and use var only when it's strictly needed. This will save us from potential headaches, because we know that the variable we're using that was declared using let, hasn't been modified. For complex functions, this can be critical.

Data Types

As we've seen in the last section, although it isn't necessary to be explicit about a variable's data type (because the compiler infers the data type), we can choose to be explicit and specify it anyway. In this section, we'll do it that way.
Swift comes with a set of predefined data types, and let us define our custom types, as we'll see in the following sections.

Numeric Data Types

Swift defines the following main numeric data types:

  • Int: Integer number.
  • Double: A decimal number.
  • Float: A decimal number, but with less capacity than a Double.
let legs: Int = 4
let price: Double = 30.50
let temperature: Float = 20.2
Enter fullscreen mode Exit fullscreen mode


Bool is a boolean value. It can either be true or false. Those are only two possibilities.

let enabled: Bool = true
let isReady: Bool = false
Enter fullscreen mode Exit fullscreen mode


String is a list of characters. It's used to define texts and words. For instance, a person name or a street name could be defined as String.

let name: String = "Fernando"
Enter fullscreen mode Exit fullscreen mode

A curious fact about String values in Swift is that emojis are valid String values!
Why? Why not? 🤷

let fancyHelloWorld: String = "👋 🌎!"
Enter fullscreen mode Exit fullscreen mode


Sometimes we need to print a value in the console. In Swift, we can do that using the print() function. For example, a "Hello, World!" program can be written like this:

print("Hello, World!")
Enter fullscreen mode Exit fullscreen mode

If you execute this program using the button in the left bar, a message will appear in the console that is at the bottom of the screen.


Comments can be single-line or multi-line.

Single-liner comments

They begin with a // and only occupy one line.

let mensaje = "Hello, world!" // This is a message
Enter fullscreen mode Exit fullscreen mode

Multi-line comments

They begin with a /* and end with */.

/* This line
 prints the
 Hello, World! message */
Enter fullscreen mode Exit fullscreen mode


How do we create a String from other variables? An easy way to do it is by concatenating the parts.

let firstName = "Fernando"
let lastName = "Ortiz"
let fullName = firstName + " " + lastName // Fernando Ortiz
Enter fullscreen mode Exit fullscreen mode

The alternative, and the most common way to do it in Swift, is by using interpolation. A String interpolation consists in "embedding" variables inside a String. This is done by entering \(<variable>) inside the String. For instance:

let middleName = "Martín"
let experience = 7
let description = "My name is \(firstName) \(middleName) \(lastName) and I have \(experience) years of experience as an iOS developer."

print(description) // "My name is Fernando Martín Ortiz and I have 7 years of experience as an iOS developer."
Enter fullscreen mode Exit fullscreen mode

Note that we're also introducing an Int in the middle of the String and we aren't getting problems. That's another advantage of String interpolation.


Let's collections in Swift to the data types that contain other data types on them and that we can iterate over them on some way.


Also known as lists or vectors, they are data types that store values in a sequential way. What's interesting about Arrays in Swift is that you can't mix values of different types in an Array. The simplest way of defining an array is this:

let students = ["Federico", "Micaela", "Aldana", "Oscar"]
Enter fullscreen mode Exit fullscreen mode

Note that we are just using String values. An Array with the values ["Fernando", 28, true] won't be valid, unless the Array is of type [Any]. The Any type is a type that can be of "any" type of value, but there are certain disadvantages on using it. My advice here is that, for now at least, AVOID USING IT.

If we want to be explicit about the type, we can also do it:

let numbers1: [Int] = [1, 21, 129]
let numbers2: Array<Int> = [3, 122]
Enter fullscreen mode Exit fullscreen mode

There are many ways of declaring an empty Array:

let numbersEmpty1: [Int] = [] // Preferred
let numbersEmpty2: Array<Int> = []
let numbersEmpty3 = [Int]()
let numbersEmpty4 = Array<Int>()
Enter fullscreen mode Exit fullscreen mode

Useful methods in Array

If we want to know the number of elements in an Array, we use the property count.

let numbers3 = [12, 31, 231, 23]
print("\(numbers3) has \(number3.count) elements")
Enter fullscreen mode Exit fullscreen mode

Curious fact, String in Swift are actually sequences of characters, so they also have the count property defined on them. Actually, most of the things we explain in this section apply also for String values.

let name = "Fernando"
let nameLength = name.count
print("\(name) has \(nameLength) characters.")
Enter fullscreen mode Exit fullscreen mode

If we want to get the first element in an Array, we'll use .first!, why we're using ! will be explained later. For now, let's say we're using ! to affirm that we know with full confidence that the first element in the Array will be there, because the Array is not empty. Note that if we do .first! and the Array is in fact empty, the program will stop executing and crash.

let names = ["Tamara", "Nicolás", "Francisco"]
let firstName = nombres.first! // "Tamara"
Enter fullscreen mode Exit fullscreen mode

If we want to know if an Array is empty, we'll use the isEmpty property, that's of type Bool.

if numbersEmpty.isEmpty {
    print("It's empty!")
Enter fullscreen mode Exit fullscreen mode

If we'd like to add an element to an Array, we'll use the functionappend`.

var namesToFill: [String] = []
print("namesToFill has \(namesToFill.count) elements") // namesToFill has 2 elements

If we want to access an exact position in an Array, we'll use what's known as a subscript, and that's basically a number (the index) between square brackets. Indexes in a Swift Array start from 0. Note that if we want to get an element in a position that is undefined, the program will stop executing and crash.

let names2 = ["Aldana", "Iván", "Marcos", "Cristian"]
let thirdName = names2[2] // "Marcos"
// This will fail when executed.
// let quintoNombre = nombres2[4]


A Swift Dictionary is a container that associate identifiers to values. They are key-value structures where the key is generally a String and the value can be anything. In contrast to Array, where the Any type was generally discouraged, creating Dictionary objects where the key is String and the value is Any is perfectly normal and widely used.
The Dictionary type is similar to what a Map is in other programming languages.

let person: [String: Any] = [ // Note that when we define a dictionary with
Any` as its value type, it can be anything.
"firstName": "Richard",
"lastName": "Brown",
"age": 27,
"position": "Project Manager"

let family: [String: String] = [ // If we define the dictionary as [String:String], values can only be String.
"mother": "Marge",
"father": "Homer",
"son": "Bart",
"daughter": "Lisa",
"baby": "Maggie",
"dog": "Santa's Little Helper"

let emptyDictionary: [String: Any] = [:] // The : sign must be there, so the compiler won't confuse this with an empty Array.

The advantage of using key-value objects is that we can now access to each value by its key. In the Family example:

let mom = family["mother"]! // "Marge"

The ! sign is necessary because we need to tell the compiler that there is an actual value associated to that key.


The for loop is a very well known structure in general programming languages. In Swift it's a bit different. The for loop let us execute a code fragment a predefined number of times. In practice, the most common use case for it, is to iterate over a collection, such as an Array or a Dictionary.

// We'll use these values for the examples.
let names = ["Ayelén", "Lautaro", "Natalia", "Sergio", "Gerardo"]
let person: [String: Any] = [
"firstName": "Richard",
"lastName": "Brown",
"age": 27,
"position": "Project Manager"

for-in - Array

To iterate over an Array, we can use a for-in loop. In this case, this will print:

Name: Ayelén
Name: Lautaro
Name: Natalia
Name: Sergio
Name: Gerardo

for name in names {
print("Name: \(name)")

for-in - Dictionary

We can also iterate over dictionaries using the for-in loop. In this case, the syntax will be a bit different, because for each element in a Dictionary wi'll get a pair (tuple actually, as we'll see in a later section), with the corresponding key and value. Let's see an example. this will print:

lastName : Gomez
position : Project Manager
firstName : Ricardo
age : 27

Also note that a Dictionary doesn't preserve the fields order. In this example, lastName was printed third, regardless I defined it first.

for (key, value) in person {
print("\(key) : \(value)")


An alternative way of using for loops is by define a Range. A Range defines a value interval.

  • (0 ...< 10) for instance, defines a range from 0 to 10, not including 10.
  • (0 ... 10) defines a range from 0 to 10, 10 included.

Will print:

Number: 0
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Number: 6
Number: 7
Number: 8
Number: 9
Number: 10

for number in 0 ... 10 {
print("Number: \(number)")

We can use the count property in Array, to iterate over it in a more "traditional" way. Note that for index in 0 ..< names.count is very similar to something like for var index = 0 ; index < names.count ; index++, which is a syntax that isn't valid in Swift. Furthermore, index++ isn't valid Swift code. An alternative to that is index += 1, but anyway, the most traditional way of using for isn't valid in Swift.

for index in 0 ..< names.count {
print("Name: \(names[index])")


The while loop is widely used in programming languages. However, if I have to be honest, in my almost 7 years as iOS developer, I don't remember having to ever use it. Most of the times, some sort of for is enough, and it's better also, because every time we use whie, we are in the risk of accidentally entering into an infinite loop.
In this section, I'll show you how to use while. But let me repeat, it's almost unnecessary to use it.
while is a keyword what let us define a condition and a block of code. The condition will be evaluated. It it's true, then the block of code will be executed. This process will be repeated indefinitely, until the condition is evaluated as false`.

This example will print:

 Name with while: Ayelén
 Name with while: Lautaro
 Name with while: Natalia
 Name with while: Sergio
 Name with while: Gerardo
Enter fullscreen mode Exit fullscreen mode
var index = 0
while index < names.count {
    print("Name with while: \(names[index])")
    index += 1
Enter fullscreen mode Exit fullscreen mode


Functions are blocks of code that can receive a set of parameters, the input, and can return a value as a result, named output.
In Swift, functions are declared with the keyword func. The structure to declare a function is the following:

func <functionName> (<input1>: <InputType1>, <input2>: <InputType2>, ..., <inputN>: <InputTypeN>) -> <OutputType> { <body> }

Functions may or may not have an output, as previously said. However, if we declare that the function will return a value, we need to return it using the return keyword.

All of this can be a bit confusing but it's actually very intuitive. Let's see some examples:

func sum(x: Int, y: Int) -> Int {
    return x + y

func helloWorld() {
    print("Hello, World!")

func greet(name: String) {
    print("Hi, \(name)!")
Enter fullscreen mode Exit fullscreen mode

The sum function receives two parameters, x and y, and returns the sum of both of them (it has an Output).
The helloWorld function doesn't have an input, neither an output. It only prints "Hello, World!" in the console.
The greet function receives an input name and doesn't return anything.

Calling a function

Functions can be called (or "invoqued") by their name, and the names of their arguments explicitly. This is very important and it's different to other languages.

let sumResult = sum(x: 10, y: 14) // 24
greet(name: "Fernando")
Enter fullscreen mode Exit fullscreen mode


Now, if you're detail-oriented people, you probably noticed that greet(name: "Fernando") doesn't read so natural, to be honest. We can fix that. Don't you think it would be easier to read greet(to: "Fernando")? However, that would make things worse, because inside the function we'd have a variable to, with the value "Fernando".
Actually, we aren't forced to choose between one way or the other. Swift differentiates between internal names and external names for function parameters. Let's do a modification here, with a new function sayHello.

func sayHello(to name: String) {
    print("Hello, \(name)!")

sayHello(to: "Fernando")
Enter fullscreen mode Exit fullscreen mode

Much better! This new function sayHello receives a single argument with an internal name name, visible only inside the function body, and an external name to, visible from the outside of the function (when we call it). If we don't specify different names for the internal and external ones, both will be the same, such as in the first couple of examples.

Let's fix another thing. The function sum looks a bit weird. Instead of sum(x: 10, y: 14), it would be more natural to say sum(10, 14) or sum(10, to: 14), or something similar. Swift let us define empty external names using the keyword _. The low dash let us specify that we don't want an external name for that parameter.
Let's try to define a divide function that would look like divide(8, by: 2).

func divide(_ x: Double, by y: Double) -> Double {
    return x / y
divide(8, by: 2) // 4
Enter fullscreen mode Exit fullscreen mode


A precondition is a condition that needs to be true so that a function is executed. If the precondition is false, then the function will return a different value or throws an error. In Swift, we express the preconditions using the keyword guard. We can read it as "Ensure this is true. Otherwise, do this instead".

func describeDivision(of x: Double, by y: Double) -> String {
    guard y != 0 else {
        return "Dividing by zero is not possible."
    let result = x / y
    return "The result of dividing \(x) by \(y) is \(result)."

let description1 = describeDivision(of: 20, by: 5)
print(description1) // The result of dividing 20 by 5 is 4.

let description2 = describeDivision(of: 10, by: 0)
print(description2) // Dividing by zero is not possible.
Enter fullscreen mode Exit fullscreen mode

Best practices

Until here, the theory of functions syntax. The following best practices section is entirely optional. Feel free to skip it or read it at the end of this course. However, I will give you a piece of advice that I think is important.
Why do we write functions? There are many reasons, but it's important to understand that a function is an important tool that we have to build abstractions in our code. An abstraction is in some way, to adapt a concept to natural terms, so we can reason on it in a more intuitive way. Considering we write code to define a solution to a problem in a way that not only the computer will understand (which is the simplest part), but also other people will be able to read it, understand it, and feel confident modifying it and extending it, then abstractions will allow us to improve the readability of our code.
A good function needs to be understandable, clear, readable and modifiable. I'll list, next, a couple of best practices when writing functions.

Short functions

A function shouldn't be longer than 10 or 15 lines of code, in general. If a function is longer than that, it's convenient to split it in smaller functions, and then compose those smaller functions into a bigger function. A single-line function isn't necessarily bad, if it bring clarity to the code.

Readable functions

This is try not only to the function body, but also to the function invocation part. Code inside a function needs to be a well defined sequence of steps. The invocation of that function needs to be read naturally.

Cohesive functions

A function must do one thing, and do it right. This is even more important than writing small functions. If a function is longer than 25 lines or code, but every line of code contribute to the same goal without adding cognitive load to the code reader, then there is no reason to modify it.


A tuple is the first custom data type we'll see during this course. Tuples are sets of data with a name and without any additional functionality (unlike classes, for instance). A tuple can be defined using the keyword typealias, or when it's needed.

typealias FullName = (firstName: String, lastName: String)
let name: FullName = (firstName: "Fernando", lastName: "Ortiz")
print("The first name is \(name.firstName) and the last name is \(name.lastName)") // The first name is Fernando and the last name is Ortiz
Enter fullscreen mode Exit fullscreen mode

The most common use case for tuples is to let a function return more than a single value.

func divide(_ dividend: Int, by divisor: Int) -> (quotient: Int, remainder: Int) {
    // We assume we're not dividing by zero
    return (
        quotient: Int(dividend / divisor), // Integer value without the decimal values
        remainder: dividend % divisor // Modulo operator returns the remainder of a division

let result = divide(13, by: 4)
print("Quotient: \(result.quotient) ;; Remainder: \(result.remainder)") // Quotient: 3 ;; Remainder: 1
Enter fullscreen mode Exit fullscreen mode


Destructuring is a way of split a tuple into its components. It's done by assigning a tuple to a set of variables in parenthesis, and separated by comma:

let (myQuotient, myRemainder) = divide(17, by: 3)
print("Quotient: \(miQuotient) ;; Remainder: \(miRemainder)") // Quotient: 5 ;; Remainder: 2

// We can also ignore any of those values with a low dash.
let (quotient, _) = divide(50, by: 4) // We are ignoring the remainder here
print("The result of dividing 50 by 4 is \(quotient)") // The result of dividing 50 by 4 is 12
Enter fullscreen mode Exit fullscreen mode

Anonymous components

A tuple can also have their components are anonymous values. Even when it's not recommendable, it's possible. In this case, we can access to its components using their index. For example: tuple.0 for the first element, tuple.1 for the second one, etc. The alternative is to use destructuring let (first, second) = tuple, which is preferrable.
Let's see a last example, generating intervals using a function that will return the lower and the upper extremes in a tuple:

func generateInterval(value: Int, amplitude: Int) -> (Int, Int) {
    let lower = valor - amplitud
    let upper = valor + amplitud
    return (lower, upper)

// Using destructuring - Preferrable.
let (lower, upper) = generateInterval(value: 10, amplitude: 2)
print("[\(lower);\(upper)]") // [8;12]

// Accessing by order to the anonymous components
let interval = generateInterval(value: 10, amplitude: 2)
print("[\(interval.0);\(interval.1)]") // [8;12]
Enter fullscreen mode Exit fullscreen mode

Optional data types

Let's suppose we want to describe a person. What attributes does a person have? We could say firstName, lastName, identifier, age, etc. All of them exist. A person always have a firstName. A person always have lastName. All of them are of type String. String is a non-optional type.
However, this isn't true for every attribute in a person. What about middleName? A person may not have middleName, it's OPTIONAL.
Optional data types in Swift let us design exactly that, values that may not be there, they may be nil. You might have already noticed that in all examples so far, we have used non-optional values, and only in specific places we have used ! to note that we are sure that the value is not nil, like in first!, for Array.
Every data type have its optional counterpart, that is defined with the ? suffix. So, for instance:

  • String? is a String that might have a String value or nil.
  • Int? is an Int that might be nil.
  • Bool? is a Bool that might be nil.
  • [String]? is an Array of String that might be nil.
  • (quotient: Int, remainder: Int)? is a type composed by two Int values. The tuple might be nil. Its components, on the other hand, can't be nil.
  • (firstName: String, lastName: String?) is a tuple composed by two String values. The first one (firstName) can't be nil. The second one might be nil. The tuple isn't optional.
typealias PersonData = (firstName: String, middleName: String?, lastName: String, age: Int)
let fernando: PersonData = (
    firstName: "Fernando",
    middleName: "Martín",
    lastName: "Ortiz",
    age: 29
let nicolas: PersonData = (
    firstName: "Nicolás",
    middleName: nil, // it's nil, it doesn't exist
    lastName: "Duarte",
    age: 29
Enter fullscreen mode Exit fullscreen mode

Force unwrap optionals

If we're sure that an optional value isn't nil, we can force it to convert it into its non-optional counterpart. To do that, we can add a ! at the end of the variable name, and it will become non-optional.
Let's see some examples. I have to say, however, that this is a VERY BAD PRACTICE. If we add a ! to get the non-optional counterpart of a value that is actually nil, the app will crash because of a fatal error.

print("Fernando's middle name is \(fernando.middleName)")
// Fernando's middle name is Optional("Martín")
// We have to get the non optional value of Fernando's middle name.

print("Fernando's middle name is \(fernando.middleName!)")
// Fernando's middle name is Martín
Enter fullscreen mode Exit fullscreen mode


This is one of the "correct" ways of handling optional values. Inside an if, we can include an assignment from an optional that will be executed just in the case that the value isn't nil. For example:

if let middleName = fernando.middleName {
    // Inside here, middleName isn't optional
    print("Fernando's middle name is \(middleName)") // Fernando's middle name is Martín

if let middleName = nicolas.middleName {
    // This won't be executed
    print("Nicolas' middle name is \(middleName)")
} else {
    print("Nicolas doesn't have a middle name")
Enter fullscreen mode Exit fullscreen mode


guard is the opposite of if. It will execute its body only in the case that the condition isn't met and it's used to express a precondition. guard can also be used to unwrap optionals in a safe way, such as if-let.

func getMiddleName(of person: PersonData) -> String {
    guard let middleName = person.middleName else {
        return "\(person.firstName) doesn't have middle name"
    return "\(person.firstName)'s middle name is \(middleName)"

print(getMiddleName(for: fernando)) // Fernando's middle name is Martín
print(getMiddleName(of: nicolas)) // Nicolás doesn't have middle name
Enter fullscreen mode Exit fullscreen mode

Implicitly unwrapped optionals

An implicitly unwrapped optional is an optional data type, that we can assign nil to, obviously, but that when we need to use it, it will be considered non optional for most of the cases.
Of course, the app will crash in case the value is actually nil and we try to use it as a non-nil value. And, of course, this is a bad practice. However, this is used a lot in iOS, especially in frameworks that are written in Objective-C (Swift's predecessor, in Apple's platforms).

let nonNilMiddleName: String! = "Marcos"
let nilMiddleName: String! = nil

func printMiddleName(_ middleName: String) {

printMiddleName(nonNilMiddleName) // Marcos
// printMiddleName(nilMiddleName) // CRASH!
Enter fullscreen mode Exit fullscreen mode

Optional chaining

Let's suppose we have an optional tuple.

var optionalPerson: PersonData?
optionalPerson = (
    firstName: "Nicolás",
    middleName: nil,
    lastName: "Duarte",
    age: 29
Enter fullscreen mode Exit fullscreen mode

If in this case, that optionalPerson is optional, we need to access any of its components, being them attributes or methods (such as in classes, as we'll see later), we can use a feature called Optional chaining.

Optional chaining lets us access members of the optional object using ?. instead of ..

let firstName = optionalPerson?.firstName // type: String?, because, although `firstName` in `PersonData` is `String`, we don't know if `optionalPerson` exists, or if it's `nil`.
Enter fullscreen mode Exit fullscreen mode


  1. Define a tuple that describes an address, with fields like city, state, zipCode, country, etc. Feel free to experiment and use dictionaries, optional values, and everything we've learned in this lesson.
  2. Inside the address, define some optional components, like floorNumber and apartment.
  3. Create three addresses as constants.
  4. Write a function that gets an address and prints it as a well formatted String. Use interpolation.
  5. Write a function that gets an Array of addresses and returns an String with "floor: \(floor) ; apt: \(apartment)" ONLY for those addresses with non-nil floor and apartment.

Discussion (0)