Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

21. Swift Review
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

You have made great progress! You’ve learned the basics of Swift programming and created two applications from scratch, one of them in SwiftUI and the other in UIKit. You are on the threshold of creating your next app.

A good building needs a good foundation. And in order to strengthen the foundations of your Swift knowledge, you first need some additional theory. There is still a lot more to learn about Swift and object-oriented programming!

In the previous chapters, you saw a fair bit of the Swift programming language already, but not quite everything. Previously, it was good enough if you could more-or-less follow along, but now is the time to fill in the gaps in the theory. So, here’s a little refresher on what you’ve learned so far.

In this chapter, you will cover the following:

  • Variables, constants and types: the difference between variables and constants, and what a type is.
  • Methods and functions: what are methods and functions — are they the same thing?
  • Making decisions: an explanation of the various programming constructs that can be used in the decision making process for your programs.
  • Loops: how do you loop through a list of items?
  • Objects: all you ever wanted to know about Objects — what they are, their component parts, how to use them, and how not to abuse them.
  • Protocols: the nitty, gritty details about protocols.

Variables, constants and types

A variable is a temporary container for a specific type of value:

var count: Int
var shouldRemind: Bool
var text: String
var list: [ChecklistItem]

The data type, or just type, of a variable determines what kind of values it can contain. Some variables hold simple values such as Int or Bool, others hold more complex objects such as String or Array.

The basic types you’ve used so far are: Int for whole numbers, Float for numbers with decimals (also known as floating-point numbers), and Bool for boolean values (true or false).

There are a few other fundamental types as well:

  • Double. Similar to a Float but with more precision. You will use Doubles later on for storing latitude and longitude data.
  • Character. Holds a single character. A String is a collection of Characters.
  • UInt. A variation on Int that you may encounter occasionally. The U stands for unsigned, meaning the data type can hold positive values only. It’s called unsigned because it cannot have a negative sign (-) in front of the number. UInt can store numbers between 0 and 18 quintillion, but no negative numbers.
  • Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64. These are all variations on Int. The difference is in how many bytes they have available to store their values. The more bytes, the bigger the values they can store. In practice, you almost always use Int, which uses 8 bytes for storage on a 64-bit platform (a fact that you may immediately forget) and can fit positive and negative numbers up to about 19 digits. Those are big numbers!
  • CGFloat. This isn’t really a Swift type but a type defined by the iOS SDK. It’s a decimal point number like Float and Double. For historical reasons, this is used throughout UIKit for floating-point values. (The “CG” prefix stands for the Core Graphics framework.)

Swift is very strict about types, more so than many other languages. If the type of a variable is Int, you cannot put a Float value into it. The other way around also won’t work: an Int won’t go into a Float.

Even though both types represent numbers of some sort, Swift won’t automatically convert between different number types. You always need to convert the values explicitly.

For example:

var i = 10
var f: Float
f = i         // error
f = Float(i)  // OK

You don’t always need to specify the type when you create a new variable. If you give the variable an initial value, Swift uses type inference to determine the type:

var i = 10              // Int
var d = 3.14            // Double
var b = true            // Bool
var s = "Hello, world"  // String

The integer value 10, the floating-point value 3.14, the boolean true and the string "Hello, world" are named literal constants or just literals.

Note that using the value 3.14 in the example above leads Swift to conclude that you want to use a Double here. If you intended to use a Float instead, you’d have to write:

var f: Float = 3.14

The : Float bit is called a type annotation. You use it to override the guess made by Swift’s type inference mechanism, since it doesn’t always get things right.

Likewise, if you wanted the variable i to be a Double instead of an Int, you’d write:

var i: Double = 10

Or a little shorter, by giving the value 10 a decimal point:

var i = 10.0

These simple literals such as 10, 3.14, or "Hello world", are useful only for creating variables of the basic types — Int, Double, String, and so on. To use more complex types, you’ll need to instantiate an object first.

When you write the following,

var item: ChecklistItem

it only tells Swift you want to store a ChecklistItem object into the item variable, but it does not create that ChecklistItem object itself. For that you need to write:

item = ChecklistItem()

This first reserves memory to hold the object’s data, followed by a call to init() to properly set up the object for use. Reserving memory is also called allocation; filling up the object with its initial value(s) is initialization.

The whole process is known as instantiating the object — you’re making an object instance. The instance is the block of memory that holds the values of the object’s variables (that’s why they are called “instance variables,” get it?).

Of course, you can combine the above into a single line:

var item = ChecklistItem()

Here you left out the : ChecklistItem type annotation because Swift is smart enough to realize that the type of item should be ChecklistItem.

However, you can’t leave out the () parentheses — this is how Swift knows that you want to make a new ChecklistItem instance.

Some objects allow you to pass parameters to their init method. For example:

var item = ChecklistItem(text: "Charge my iPhone", checked: false)

This calls the corresponding init(text:checked:) method to prepare the newly allocated ChecklistItem object for usage.

You’ve seen two types of variables: local variables, whose existence is limited to the method they are declared in, and instance variables (also known as “ivars,” or properties) that belong to the object and therefore can be used from within any method in the object.

The lifetime of a variable is called its scope. The scope of a local variable is smaller than that of an instance variable. Once the method ends, any local variables are destroyed.

class MyObject {
  var count = 0      // an instance variable

  func myMethod() {
    var temp: Int    // a local variable
    temp = count     // OK to use the instance variable here
  }

  // the local variable “temp” doesn’t exist outside the method
}

If you have a local variable with the same name as an instance variable, then it is said to shadow (or hide) the instance variable. You should avoid these situations as they can lead to subtle bugs where you may not be using the variable that you think you are:

class MyObject {
  var count = 7      // an instance variable

  func myMethod() {
    var count = 42   // local variable “hides” instance variable
    print(count)     // prints 42
  }
}

Some developers place an underscore in front of their instance variable names to avoid this problem: _count instead of count. An alternative is to use the keyword self whenever you want to access an instance variable:

  func myMethod() {
    var count = 42
    print(self.count)   // prints 7
  }

Constants

Variables are not the only code elements that can hold values. A variable is a container for a value that is allowed to change over the course of the app being run.

let pi = 3.141592
let difference = abs(targetValue - currentValue)
let message = "You scored \(points) points"
let image = UIImage(named: "SayCheese")

Value types vs. reference types

When working with basic values such as integers and strings — which are value types — a constant created with let cannot be changed once it has been given a value:

let pi = 3.141592
pi = 3                // not allowed
let item = ChecklistItem()
item.text = "Do the laundry"
item.checked = false
item.dueDate = yesterday
let anotherItem = ChecklistItem()
item = anotherItem   // cannot change the reference

Collections

A variable stores only a single value. To keep track of multiple objects, you can use a collection object. Naturally, I’m talking about arrays (Array) and dictionaries (Dictionary), both of which you’ve seen previously.

// An array of ChecklistItem objects:
var items: Array<ChecklistItem>

// Or, using shorthand notation:
var items: [ChecklistItem]

// Making an instance of the array:
items = [ChecklistItem]()

// Accessing an object from the array:
let item = items[3]
// A dictionary that stores (String, Int) pairs, for example a
// list of people’s names and their ages:
var ages: Dictionary<String, Int>

// Or, using shorthand notation:
var ages: [String: Int]

// Making an instance of the dictionary:
ages = [String: Int]()

// Accessing an object from the dictionary:
var age = dict["Jony Ive"]

Generics

Array and Dictionary are known as generics, meaning that they are independent of the type of thing you want to store inside these collections.

var items: Array  // error: should be Array<TypeName>
var items: []     // error: should be [TypeName]

Optionals

Sometimes, it’s useful to have a variable that can have no value, in which case you need to declare it as an optional:

var checklistToEdit: Checklist?
if let checklist = checklistToEdit {
  // “checklist” now contains the real object
} else {
  // the optional was nil
}
if let age = dict["Jony Ive"] {
  // use the value of age
}
var age = dict["Jony Ive"]!
navigationController!.delegate = self
navigationController?.delegate = self
if navigationController != nil {
  navigationController!.delegate = self
}
var dataModel: DataModel!

Methods and functions

You’ve learned that objects, the basic building blocks of all apps, have both data and functionality. Instance variables and constants provide the data, methods provide the functionality.

let result = performUselessCalculation(314)
print(result)

. . .

func performUselessCalculation(_ a: Int) -> Int {
  var b = Int(arc4random_uniform(100))
  var c = a / 2
  return (a + b) * c
}
// Method with no parameters, no return a value.
override func viewDidLoad()

// Method with one parameter, slider. No return a value.
// The keyword @IBAction means that this method can be connected
// to a control in Interface Builder.
@IBAction func sliderMoved(_ slider: UISlider)

// Method with no parameters, returns an Int value.
func countUncheckedItems() -> Int

// Method with two parameters, cell and item, no return value.
// Note that the first parameter has an extra label, for, 
// and the second parameter has an extra label, with.
func configureCheckmarkFor(for cell: UITableViewCell, 
                          with item: ChecklistItem)

// Method with two parameters, tableView and section. 
// Returns an Int. The _ means the first parameter does not 
// have an external label.
override func tableView(_ tableView: UITableView, 
      numberOfRowsInSection section: Int) -> Int

// Method with two parameters, tableView and indexPath.
// The question mark means it returns an optional IndexPath 
// object (may also return nil).
override func tableView(_ tableView: UITableView, 
          willSelectRowAt indexPath: IndexPath) -> IndexPath?
// Calling a method on the lists object:
lists.append(checklist)

// Calling a method with more than one parameter:
tableView.insertRows(at: indexPaths, with: .fade)
class DataModel {
  func loadChecklists() {
    . . .	
    sortChecklists()  // this method also lives in DataModel
  }

  func sortChecklists() { 
    . . . 
  }
}
  func loadChecklists() {
    . . .	
    self.sortChecklists()
  }
@IBAction func cancel() {
  delegate?.itemDetailViewControllerDidCancel(self)
}

Parameters

Often methods have one or more parameters, so they can work with multiple data items. A method that is limited to a fixed set of data is not very useful or reusable. Consider sumValuesFromArray(), a method that has no parameters:

class MyObject {
  var numbers = [Int]()

  func sumValuesFromArray() -> Int {
    var total = 0
    for number in numbers {
      total += number
    }
    return total
  }
}
func sumValues(from array: [Int]) -> Int {
  var total = 0
  for number in array {
    total += number
  }
  return total
}
func downloadImage(for searchResult: SearchResult,  
                withTimeout timeout: TimeInterval, 
                  andPlaceOn button: UIButton) {
  . . .
}
downloadImage(for: result, withTimeout: 10, 
                            andPlaceOn: imageButton)
override func tableView(_ tableView: UITableView, 
      numberOfRowsInSection section: Int) -> Int

Making decisions

The if statement looks like this:

if count == 0 {
  text = "No Items"
} else if count == 1 {
  text = "1 Item"
} else {
  text = "\(count) Items"
}

Comparison Operators

You use comparison operators to perform comparisons between two values:

let a = "Hello, world"
let b = "Hello," + " world"
print(a == b)             // prints true

Logical Operators

You can use logical operators to combine two expressions:

if ((this && that) || (such && so)) && !other {
  // statements
}
if ((this and that) or (such and so)) and not other {
  // statements
}
if ( 
      (this and that)
            or
      (such and so)
   )
   and
      (not other)

switch statement

Swift has another very powerful construct in the language for making decisions, the switch statement:

switch condition {
  case value1:
    // statements

  case value2:
    // statements

  case value3:
    // statements

  default:
    // statements
}
if condition == value1 {
  // statements
} else if condition == value2 {
  // statements
} else if condition == value3 {
  // statements
} else {
  // statements
}
switch difference {
  case 0:
    title = "Perfect!"
  case 1..<5:
    title = "You almost had it!"
  case 5..<10:
    title = "Pretty good!"
  default:
    title = "Not even close..."
}

return statement

Note that if and return can be used to return early from a method:

func divide(_ a: Int, by b: Int) -> Int {
  if b == 0 {
    print("You really shouldn’t divide by zero")
    return 0
  }
  return a / b
}
func performDifficultCalculation(list: [Double]) {
  if list.count < 2 {
    print("Too few items in list")
    return
  }

  // perform the very difficult calculation here
}
func performDifficultCalculation(list: [Double]) {
  if list.count < 2 {
    print("Too few items in list")
  } else {
    // perform the very difficult calculation here
  }
}
func someMethod() {
  if condition1 {
    if condition2 {
      if condition3 {
        // statements
      } else {
        // statements
      }
    } else {
      // statements
    }
  } else {
    // statements
  }
}
func someMethod() {
  if !condition1 {
    // statements
    return
  }

  if !condition2 {
    // statements
    return
  }

  if !condition3 {
    // statements
    return
  }

  // statements
}
func someMethod() {
  guard condition1 else {
    // statements
    return
  }
  guard condition2 else {
    // statements
    return
  }
  . . .

Loops

You’ve seen the for...in statement for looping through an array:

for item in items {
  if !item.checked {
    count += 1
  }
}
for item in items where !item.checked {
  count += 1
}

Looping through number ranges

Some languages, including Swift 2, have a for statement that looks like this:

for var i = 0; i < 5; ++i {
  print(i)
}
0
1
2
3
4
for i in 0...4 {   // or 0..<5
  print(i)
}
for i in stride(from: 0, to: 5, by: 1) {
  print(i)
}

while statement

The for statement is not the only way to perform loops. Another very useful looping construct is the while statement:

while something is true {
  // statements
}
repeat {
  // statements
} while something is true
var count = 0
var i = 0
while i < items.count {
  let item = items[i]
  if !item.checked {
    count += 1
  }
  i += 1
}
var found = false
for item in array {
  if item == searchText {
    found = true
    break
  }
}
var uncheckedItems = items.filter { item in !item.checked }

Objects

Objects are what it’s all about. They combine data with functionality into coherent, reusable units — that is, if you write them properly!

class MyObject {
  var text: String
  var count = 0
  let maximum = 100

  init() {
    text = "Hello world"
  }

  func doSomething() {
    // statements
  }
}

Properties

There are two types of properties:

var indexOfSelectedChecklist: Int {
  get {
    return UserDefaults.standard.integer(
                forKey: "ChecklistIndex")
  }
  set {
    UserDefaults.standard.set(newValue, 
                                forKey: "ChecklistIndex")
  }
}

Methods

There are three kinds of methods:

let myInstance = MyObject()   // create the object instance
. . .
myInstance.doSomething()      // call the method
class MyObject {
  . . .

  class func makeObject(text: String) -> MyObject {
    let m = MyObject()
    m.text = text
    return m
  }
}

let myInstance = MyObject.makeObject(text: "Hello world")
class MyObject {
  . . .

  init(text: String) {
    self.text = text
  }
}

let myInstance = MyObject(text: "Hello world")

Protocols

Besides objects, you can also define protocols. A protocol is simply a list of method names (and possibly, properties):

protocol MyProtocol {
  func someMethod(value: Int)
  func anotherMethod() -> String
}
class MyObject: MyProtocol {
  . . .
}
var m1: MyObject = MyObject()
var m2: MyProtocol = MyObject()
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now