0% found this document useful (0 votes)
2K views110 pages

Preparing For A Technical iOS Job Interview

Uploaded by

Tilek Koszhanov
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2K views110 pages

Preparing For A Technical iOS Job Interview

Uploaded by

Tilek Koszhanov
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 110

Table of Contents

1. Introduction
About this book

Acknowledgements

Copyright

2. Swift
Swift - Questions Overview

2.1 Swift Language Features

2.2 Variables & Properties

2.3 Types & Optionals

2.4 Functions & Closures

2.5 Extensions, Protocols & Generics

2.6 Error Handling

2.7 Syntactic Sugar & Readability

2.8 Memory Management

3. Objective-C
Objective-C - Questions Overview

3.1 Classes & Objects

3.2 Categories & Extensions

3.3 Protocols

3.4 Value Types

3.5 Blocks

3.6 Interoperability with Swift

4. Xcode
Xcode - Questions Overview

4.1 Build System

4.2 Working with Xcode

4.3 Debugging with Xcode

5. SwiftUI
SwiftUI - Questions Overview

1
5.1 App Structure

5.2 Views

5.3 Property Wrappers

5.4 SwiftUI & UIKit

6. UIKit
UIKit - Questions Overview

6.1 App Structure

6.2 Views

6.3 User Interactions

7. Combine
Combine - Questions Overview

7.1 Publishers & Subjects

7.2 Subscribers

7.3 Operators

8. Server Communication
Server Communication - Questions Overview
8.1 HTTP Protocol

8.2 Working with JSON


8.3 Sending Requests in iOS

9. Concurrency
Concurrency - Questions Overview
9.1 Swift Concurrency with async/await

9.2 Grand Central Dispatch & Operations

10. Persisting Data


Persisting Data - Questions Overview
10.1 UserDefaults

10.2 File System


10.3 Core Data

11. Security
Security - Questions Overview

2
11.1 Cryptography

11.2 Authentication

12. Automated Testing


Automated Testing - Questions Overview

12.1 Unit Tests


12.2 Performance Tests

12.3 UI Tests

What's next?
Thank you for reading

3
Introduction

About this book


Having been a freelance iOS developer for over 10 years, I participated in quite a few hiring interviews for various
projects and have also been in a position of leading the technical interviews myself.

With this book, I'd like to give you a guide for technical iOS questions you might be asked in such an interview for
an iOS hiring position. It can also be used to get an overview on different iOS development topics. It can be an
inspiration for deepening and expanding your knowledge on your iOS journey.

Each chapter starts with a questions overview that allows you to check your knowledge without seeing the answers.
All questions are numerated so you can quickly find the answer if needed. The following topics are covered:

Swift
Being the main programming language for the iOS platform, a deep knowledge in Swift is expected from an iOS
developer. This chapter provides questions and answers on Swift and general programming language concepts.

Objective-C
Objective-C is becoming more and more obsolete after Swift was released in 2014. However, with existing apps
written in Objective-C still out there, knowledge on Objective-C may be required for an iOS position. This chapter
provides questions and answers on Objective-C basics and the interoperability between Swift and Objective-C.

Xcode
Being the main iOS development tool, a confident usage of Xcode is expected from an iOS developer. This chapter
provides questions and answers on working and debugging with Xcode.

SwiftUI
Since 2019, SwiftUI is Apple's main framework for building user interfaces for iOS. This chapter provides questions
and answers on the main aspects of SwiftUI.

UIKit
With SwiftUI slowly pushing UIKit into the background, UIKit is still part of many existing iOS applications. A deep
knowledge of UIKit may be required for an iOS position. This chapter provides questions and answers around key
aspects of UIKit.

Combine
With the release of Combine at WWDC 2019, Apple introduced a native way to write functional reactive code. This
chapter provides questions and answers on Combine and on functional reactive programming concepts in general.

Server Communication
Server communication is part of almost every iOS application. This chapter provides questions and answers on
general computer networking concepts and on specific iOS networking topics.

4
Concurrency
Apple introduced Swift's async/await syntax during WWDC21, allowing us to write asynchronous code in a shorter
and safer way. This chapter focuses on this concurrency model with async/await. It also provides questions and
answers on more low-level technologies like Grand Central Dispatch and Operations. Since they were used in iOS
applications before async/await, knowledge around these technologies may be required for an iOS position.

Persisting Data
Persisting pieces of data is required in many applications. For different scenarios, Apple offers different ways to
store data. This chapter contains questions and answers on the iOS File System, UserDefaults, Keychain and the
Core Data framework.

Security
Many applications need to manage some kind of sensitive user data. To ensure a high level of security, Apple
provides different technologies to be used by developers. This chapter provides questions and answers on specific
security-related iOS technologies such as keychain or shared web credentials, as well as general cryptography
concepts.

Automated Testing
Testing is an important part in every software development process. Apple offers tools to write unit, UI and
performance tests for iOS applications. This chapter provides questions and answers on general testing concepts
and specific iOS testing tools.

I hope you'll have some fun and gain many new insights with this book!

5
Acknowledgements
A special thanks goes to my partner Marcel Gladbach who except of being my husband and supporting me every
step of the way, also helped me editing and publishing this book.

Another big thanks goes to the iOS developers community which was supportive throughout my writing journey.
Many developers reached out saying how helpful my articles were to them. These messages have greatly
encouraged me to continue creating content and writing this book. Thank you!

6
Copyright
Copyright @2023 Natascha Fadeeva, All rights reserved.

First Edition, released in January 2023. Self-published.

Contact: [email protected]

Website: tanaschita.com

Author: Natascha Fadeeva

While a great effort was made to ensure that the contents of this work are accurate, the author and publisher
disclaim all responsibility for errors, omissions or damages resulting from the use of this work.

This book is copyright protected and only for personal use. It is illegal to reproduce, duplicate, distribute or sell any
part or the content within this book without the consent of the author.

7
2. Swift
Being the main programming language for the iOS platform, a deep knowledge in Swift is expected from an iOS
developer. This chapter provides questions and answers on Swift and general programming language concepts.

Questions Overview
2.1 Swift Language Features

2.1.1 Swift is a type-safe language. What does type safety mean?

2.1.2 Swift supports type inference. What does it mean?

2.1.3 What are optionals in Swift and what are their benefits?

2.1.4 Swift supports extensions. Which benefits do extensions have?

2.1.5 What is the difference between value and reference types in Swift?

2.1.6 Which additional capabilities do classes provide compared to structs in Swift?

2.1.7 What is protocol oriented programming? Which features do protocols have in Swift?

2.1.8 Which benefits do generics have? How can we define a generic function in Swift?

2.2 Swift Variables & Properties

2.2.1 What is the difference between let and var in Swift?

2.2.2 What is a lazy stored property in Swift?

2.2.3 What are computed properties in Swift?

2.2.4 What are property wrappers in Swift?

2.2.5 What are property observers in Swift?

2.3 Swift Types & Optionals

2.3.1 What is the difference between an Array ,a Set and a Dictionary ?

2.3.2 When working with Swift's String type, what is the outcome of the following print statement?

var fruit1 = "apple"

var fruit2 = "orange"

fruit1 = fruit2

fruit2 = "banana"

print(fruit1)

2.3.3 What are tuples? In which cases are they useful?

2.3.4 What is the difference between raw values and associated values when working with enumerations in Swift?

2.3.5 What type does the following variable have: let value = 0b1011 ?

8
2.3.6 How can we unwrap an optional type in a safe way in Swift?

2.3.7 What happens when forced unwrapping is used to unwrap an optional type in Swift?

2.3.8 What is optional chaining in Swift?

2.4 Swift Functions & Closures

2.4.1 What type does the following variable have?

let action: (Int) -> Void

2.4.2 In Swift, is it possible to return a function as a result of a function?

2.4.3 What are closure expressions in Swift?

2.4.4 What does the @escaping keyword mean in Swift closures?

2.4.5 What is a higher order function? What are some examples of higher order functions in Swift?

2.4.6 What are in-out parameters in Swift?

2.4.7 What are variadic parameters in Swift?

2.4.8 What is operator overloading in Swift?

2.5 Swift Extensions, Protocols & Generics

2.5.1 Is it possible to make a type conform to a protocol if we don't have access to the source code for this type?

2.5.2 When extending a type in Swift, is the following code valid?

extension String {

var name: String?

2.5.3 What is a protocol associated type in Swift?

2.5.4 What is protocol composition?

2.5.5 In addition to generic functions, Swift enables us to define generic types. What are generic types?

2.5.6 What are extensions with a generic where clause in Swift?

2.5.7 What does the some keyword do in the following Swift code?

func cleanup(store: some Store) {

2.6 Swift Error Handling

2.6.1 How are errors defined and thrown in Swift?

2.6.2 What does the throws keyword in a function declaration mean?

2.6.3 How can we call a throwing function inside another throwing function?

2.6.4 How can we call a throwing function inside a nonthrowing function?

2.7 Syntactic Sugar & Readability in Swift

9
2.7.1 What is a type alias in Swift?

2.7.2 What is a subscript in Swift and how can we create one for a custom type?

2.7.3 What is String interpolation in Swift?

2.7.4 What is the ternary conditional operator in Swift?

2.7.5 What is the nil coalescing operator?

2.7.6 How can we shorten the following closure expression?

let sorted = numbers.sorted(by: { (number1: Int, number2: Int) -> Bool in

return number1 > number2

})

2.8 Memory Management in Swift

2.8.1 What is Automatic Reference Counting in Swift?

2.8.2 What is the difference between a weak and a strong reference?

2.8.3 What the difference between a weak and an unowned reference?

2.8.4 What is a memory leak? How can memory leaks be avoided?

2.8.5 How can we detect memory leaks?

10
2.1 Swift Language Features
2.1.1 Swift is a type-safe language. What does type safety mean?
Type safety means that the compiler prevents type errors. For example when declaring a variable of type Int , we
cannot assign a String value to it. The compiler performs type checks and informs us about mismatched types
with compiler errors. This enables us to catch and fix type mismatches as early as possible in the development
process.

var age: Int = 10

age = "ten" // Compiler error

2.1.2 Swift supports type inference. What does it mean?


Type inference is an automatic detection of types. This means that in a lot of cases, we don't need to explicitly
specify the type.

var age = 10

In the example above, we didn't specify the type of the variable. The Swift compiler uses type inference to
determine the type based on the value we assigned to it. In our example, age has the type Int .

2.1.3 What are optionals in Swift and what are their benefits?
Optionals are a fundamental concept in Swift that complements its type-safety rules. An optional type is used to
handle the abscence of a value.

Declaring a type as optional means it can become nil . In return, a variable or property that is not declared as an
optional is guaranteed never to be nil . This approach makes the language more expressive and safe.

2.1.4 Swift supports extensions. Which benefits do extensions have?


Swift extensions provide a way to add new functionality to an existing type. By using an extension, we can add
methods, initializers, computed properties or subscripts to a type even if we don't have access to its original source
code. We can also extend a type to conform to a protocol.

extension String {

var localized: String {

NSLocalizedString(self, comment: "")

This is a great way to extend behaviour of types provided by libraries and frameworks.

2.1.5 What is the difference between value and reference types in Swift?
When assigning a value type to a variable or passing it into a function, the created variable will be a copy of its data.
A reference type variable will just point to the original data in memory.

11
In Swift, struct and enum types are value types and class types are reference types.

2.1.6 Which additional capabilities do classes provide compared to structs


in Swift?
Classes support inheritance. This enables a class to subclass another one and inherit certain behavior.
Classes support type casting which enables us to check the type of a class instance at runtime.
Classes support deinitializers so we can free up any assigned resources.
Since classes are reference types there can be more than one reference to a class.

2.1.7 What is protocol oriented programming? Which features do protocols


have in Swift?
A protocol defines a blueprint of methods, properties and other requirements providing the possibility to add
abstraction to our code. In Swift, class , struct and enum types can conform to multiple protocols.

Protocol oriented programming is a programming approach where protocols play an important role in building a
modular, decoupled and testable code base.

Swift provides protocol features such as protocol extensions, protocol inheritence and protocol composition.

2.1.8 Which benefits do generics have? How can we define a generic


function in Swift?
Generic code enables us to avoid code duplication by writing flexible and reusable components.

To define a generic function, we put a placeholder type name inside angle brackets right after the function's name.
Additionally, we can specify a protocol to which the generic type must conform.

func log<T: Loggable>(element: T) {

file.write(element.logDescription)

In the example above, the function takes a parameter of generic type T which has to conform to the Loggable

protocol.

12
2.2 Swift Variables & Properties
2.2.1 What is the difference between let and var in Swift?
With the let keyword, we declare costants in Swift. A value of a constant can’t be changed once it is set. With
var , we declare variables which can be reassigned.

let age = 10

age = 12 // Compiler error

2.2.2 What is a lazy stored property in Swift?


A lazy stored property is a property whose initial value isn't calculated until the first time it is accessed.

lazy var database: Database = {

let database = Database()

return database

}()

Lazy stored properties allow us to avoid doing expensive work unless it's really needed, for example when creating
complex objects.

2.2.3 What are computed properties in Swift?


Computed properties can be accessed just like stored properties. They provide a getter and an optional setter. But
computed properties themselves do not store any values. Instead, they use stored properties to set or to calculate
the return value.

var fullName: String {

return "\(firstName) \(lastName)"

2.2.4 What are property wrappers in Swift?


A property wrapper separates the definion of a property from the code managing how the property is stored. It
allows us to encapsulate and reuse storage related code by applying it to multiple properties.

@propertyWrapper

struct UserTextInput {

private var input = ""

var wrappedValue: String {

get { return input }

set { input = String(newValue.prefix(100)) }

The example above creates a property wrapper named UserTextInput which stores a maximum of 100 characters.

13
To create a property with that behaviour, we apply the wrapper as follows:

@UserTextInput var message: String

2.2.5 What are property observers in Swift?


We define property observers to monitor and respond to changes of a property's value. There are two types of
property observers:

willSet is called just before the value is set


didSet is called immediately after the new value is set

var name: String = "" {

willSet {

print(name)

print(newValue)

didSet {

print(oldValue)

print(name)

As shown in the example above, the didSet observer additionally gets passed in a constant parameter named
oldValue containing the old property value. The willSet observer gets passed in the new property value as a
constant parameter named newValue .

A property observer is called every time a value is set, even if the new value is the same as the old one.

14
2.3 Swift Types & Optionals
2.3.1 What is the difference between an Array , a Set and a
Dictionary ?
Arrays are ordered collections of values that we access by using indices.

var berries = ["gooseberry", "blueberry", "raspberry"]

let berry = berries[1]

berries[0] = "blackberry"

Sets are unordered collections of unique values. We use a set instead of an array when the order of items isn’t
important, or when we need to ensure that an item only exists once. A type must be hashable in order to be stored
in a set.

var numbers: Set<Int> = [1, 3, 5]

numbers.insert(7)

On sets, we can perform set operations like intersection(_:) , union(_:) etc.

numbers.intersection([1, 2, 3]) // result is [1, 3]

Dictionaries are unordered collections of key-value pairs. The key is unique and acts as the identifier for a value in
the dictionary.

let inventory = ["apple": 2, "raspberry": 5]

let applesCount = inventory["apple"]

2.3.2 When working with Swift's String type, what is the outcome of the
following print statement?

var fruit1 = "apple"

var fruit2 = "orange"

fruit1 = fruit2

fruit2 = "banana"

print(fruit1)

Since String is a value type in Swift, the third line fruit1 = fruit2 does not change any pointers, but simply
copies the value of fruit2 into fruit1 . This results in orange as print output.

2.3.3 What are tuples? In which cases are they useful?


Tuples group multiple values into a single compound value.

15
let result = (15, "apples")

The example shows a tuple of type (Int, String) . The individual tuple values can be named.

let result = (count: 15, fruit: "apples")

Tuples are useful for simple grouping, for example to return multiple related values of a function without having to
create a struct.

2.3.4 What is the difference between raw values and associated values
when working with enumerations in Swift?
When using raw values, we associate each enumaration case with a value of the same type.

enum Week: Int {

case mo = 0

case tue = 1

// ...

When using associated values, each case can be associated with any given type or no type at all.

enum ReturnReason {

case notSpecified

case damagedItem(UIImage)

case other(String)

When switching over an enum with associated values, the associated values can be extracted as constants as part
of the switch statement:

switch returnReason {

case .notSpecified:

break

case .damagedItem(let image):

showImage(image)

case .other(let description):

showDescription(description)

2.3.5 What type does the following variable have: let value =
0b1011 ?
The value variable is of type Int . It is defined by using the numeric literal for binary numbers. 0b1011 is the
binary notation for the number 11.

let value = 11 // same as let value = 0b1011

16
2.3.6 How can we unwrap an optional type in a safe way in Swift?
We can use an approach called optional binding by using if or guard statements to unwrap the optional and get
the value safely.

func validate(userInput: String?) {

guard let userInput = userInput else { return }

2.3.7 What happens when forced unwrapping is used to unwrap an optional


type in Swift?
Forced unwrapping provides direct access to the value by using the exclamation mark.

let result = response!.result

In the example above, forced unwrapping is used to unwrap the optional variable response . In case the optional
has no value, i.e. is nil , forced unwrapping leads to a runtime error.

2.3.8 What is optional chaining in Swift?


Optional chaining is a safe alternative to forced unwrapping when trying to call a method or to get a property value
of an optional.

let taskDescription = tasks?.first?.description

In the above line of code, the tasks array and its first item are both optionals. To avoid a runtime error when one of
the values is nil , optional chaining is used by placing a question mark after every optional value.

17
2.4 Swift Functions & Closures
2.4.1 What type does the following variable have?

let action: (Int) -> Void

The variable named action in the example above has the type of a function that takes an Int and returns
nothing.

2.4.2 In Swift, is it possible to return a function as a result of a function?


Yes. Function types can be stored in a variable, be passed in as parameters and also be returned from another
function.

func validateFunction(for inputType: InputType) -> (String) -> Bool {

switch inputType {

case .numberInput:

return validateNumberInput

func validateNumberInput(_ input: String) -> Bool {

Int(input) != nil

The validateFunction method returns a function that we can store in a variable and then use it to validate some
input.

let validate = validateFunction(for: .numberInput)

print(validate("5"))

2.4.3 What are closure expressions in Swift?


Closure expressions are unnamed self-contained blocks of functionality written in a lightweight syntax. For example,
if a function takes another function as a parameter, we can use a closure expression to do that.

let values = [1, 2, 3]

let descriptions = values.map { value in

return "number \(value)"

2.4.4 What does the @escaping keyword mean in Swift closures?


When writing a function that takes a closure as a parameter, we can declare it as @escaping to indicate that the
closure is allowed to escape.

18
func load(completionHandler: @escaping () -> Void) {

self.loadCompletionHandler = completionHandler

...

A common use case are functions that start an asynchronious operation and take a closure as a completion
handler. The function returns after starting the operation but the closure isn't called until the operation is completed.

2.4.5 What is a higher order function? What are some examples of higher
order functions in Swift?
A higher order function is a function that takes one or more functions as arguments or returns a function as its
result. Examples are map , filter , reduce etc.

let doubled = [1, 2, 3].map { $0 * 2 }

print(doubled) // prints [2, 4, 6]

As shown above, the map function iterates through an array and changes each element of that array based on the
closure passed into the method.

2.4.6 What are in-out parameters in Swift?


Parameters passed into a Swift function are constants by default, so we can’t modify them. We can define them as
inout parameters to be able to modify them and to reflect the changes in the original value outside the function.

func modify(_ number: inout Int) {

number = 8

var number = 10

modify(number)

print(number) // prints 8

2.4.7 What are variadic parameters in Swift?


A variadic parameter accepts zero or more values of a specified type. They are defined by combining any type with
three period characters ... .

func sum(_ numbers: Int...) -> Int {

var result: Int = 0

for number in numbers {

result += number

return result

sum(1, 4, 7)

19
When calling the function above, we can pass in as many Int values as we want, separated by comma. Within
the function, the parameter is made available as an array.

2.4.8 What is operator overloading in Swift?


Classes and structures can provide their own implementations of existing operators. This is known as operator
overloading.

For example, to overload the + operator for a custom struct, we create a static method named + that takes two
arguments since the addition operator is a binary operator.

extension Ingredient {

static func + (left: Ingredient, right: Ingredient) -> Ingredient {

let ingredient = // create new ingredient

return ingredient

20
2.5 Swift Extensions, Protocols & Generics
2.5.1 Is it possible to make a type conform to a protocol if we don't have
access to the source code for this type?
Yes, by using extensions.

protocol Purchasable {

var formattedPrice: String { get }

extension Chocolate: Purchasable {

var formattedPrice: String {

return ...

In the example above, we extend Chocolate to conform to the Purchasable protocol.

2.5.2 When extending a type in Swift, is the following code valid?

extension String {

var name: String?

The above code produces a compiler error, since extensions are not allowed to have stored properties.

2.5.3 What is a protocol associated type in Swift?


Associated types provide a way to make protocols generic. An associated type is a type placeholder that is used as
part of the protocol.

protocol Shop {

associatedtype Item

var items: [Item] { get }

The concrete type for the associated type is specified when the protocol is adopted.

class ChocolateShop: Shop {

var items: [Chocolate] = []

In the example above, the associated type Chocolate is automatically inferred by Swift.

We can declare one or more associated types as part of the protocol's definition.

2.5.4 What is protocol composition?

21
Protocol compositions can be created by combining two or more protocols with a & sign. This is useful when we
want a type to conform to multiple protocols at the same time.

func methodName(parameterName: ProtocolOne & ProtocolTwo)

2.5.5 In addition to generic functions, Swift enables us to define generic


types. What are generic types?
Generic types are classes, structures or enumerations that can work with any type.

An example of a generic type is Swift's Array type which is defined as follows:

struct Array<Element> {

The generic Element type can be then used inside the defined struct, for example as a method parameter:

mutating func append(_ newElement: Element)

To create a new Array instance, we specify the actual type to be stored in the array within angle brackets:

let titles: Array<String>

2.5.6 What are extensions with a generic where clause in Swift?


By using a generic where clause we can add a new requirement to the extension.

For example, when extending Swift's generic Array<Element> type, we can specify that its elements need to
conform to the Equatable protocol as follows:

extension Array where Element: Equatable {

func someMethod(_ item: Element) -> Bool {

// ...

2.5.7 What does the some keyword do in the following Swift code?

func cleanup(store: some Store) {

Swift allows us to use the some keyword when referencing protocols with associated types as parameters.

The opaque declaration is basically syntactic sugar for the following generic code:

func cleanup<T: Store>(store: T) {

22
2.6 Swift Error Handling
2.6.1 How are errors defined and thrown in Swift?
In Swift, an error is represented by a type conforming to the Error protocol. For example, an enum type can be
used to represent a group of related error cases.

enum LoginError: Error {

case wrongPassword

// other error cases

We use the throw statement to throw an error.

throw LoginError.wrongPassword

2.6.2 What does the throws keyword in a function declaration mean?


The throws keyword means that the function can throw an error if it can't produce the requested result because
some condition is not given.

func validate(address: String) throws {

// this function may throw an error

A function marked with throws is called a throwing function.

2.6.3 How can we call a throwing function inside another throwing


function?
To call a throwing function inside another throwing function, we can use the try keyword.

func validate(userInput: UserInput) throws {

let address = try validate(address: userInput.address)

// ...

This approach is called propagating the error, i.e. the error that the validate(address: String) function might throw
is not handled directly but propagated to the caller of the validate(userInput: UserInput) function.

2.6.4 How can we call a throwing function inside a nonthrowing function?


In this case, we cannot propagate the error with try . Instead, we can use a do catch statement to handle the
error.

23
do {

let result = try validate(address: userInputAddress)

// no error was thrown

} catch let error {

// an error was thrown

If we are not interested in the error, we can use the try? expression instead of the do catch statement which
returns an optional result:

let result = try? someThrowingFunction()

As a third possibility, we could use try! which will either return a non-optional value or create a runtime error in
case an error is thrown.

24
2.7 Syntactic Sugar & Readability in Swift
2.7.1 What is a type alias in Swift?
Type aliases define an alternative name for an existing type. It gives us the possibility to be more expressive when
writing code.

typealias Height = Int

2.7.2 What is a subscript in Swift and how can we create one for a custom
type?
A subscript is a shortcut for accessing a value. A classic example is accessing an element of an array with an
index.

let firstElement = someArray[0]

We can support subscripts for any custom type by adding a subscript method to that type.

extension Shop {

subscript(id: String) -> Item {

let item = // get item for the given id

return item

By adding the subscript method above, we now are able to use it to get a shop item for a certain id.

let item = shop[id]

2.7.3 What is String interpolation in Swift?


String interpolation provides a way to construct a new String from a mix of constants or variables.

let age = 10

let description = "\(age) years old"

print(description) // prints "10 years old"

Each variable we insert into the string literal is wrapped in a pair of parentheses, prefixed by a backslash.

2.7.4 What is the ternary conditional operator in Swift?


The ternary conditional operator ? : , also called the inline if operator, allows us to shorten certain if-statements
into one line.

Let's look at the following if-statement:

25
let description: String

if number % 2 == 0 {

description = "even number"

} else {

description = "odd number"

By using the ternary conditional operator, the example above can be shortened as follows:

let description = number % 2 == 0 ? "even number" : "odd number"

2.7.5 What is the nil coalescing operator?


By using the nil coalescing operator ?? , we can provide a default value if the optional is nil.

let street = userInput.address?.street ?? ""

2.7.6 How can we shorten the following closure expression?

let sorted = numbers.sorted(by: { (number1: Int, number2: Int) -> Bool in

return number1 > number2

})

We can let Swift infer the parameter types and the return type. Since it is a singe-expression, we can also omit the
return keyword.

let sorted = numbers.sorted(by: { number1, number2 in number1 > number2 })

To shorten it further, we can use the shorthand argument names provided by Swift.

let sorted = numbers.sorted(by: { $0 > $1 })

And there’s actually an even shorter way to write the closure expression above. Since > is a method that takes
two parameters and returns a Bool value, we can simply pass it in and let Swift infer the rest.

let sorted = numbers.sorted(by: >)

26
2.8 Memory Management in Swift
2.8.1 What is Automatic Reference Counting in Swift?
Swift uses Automatic Reference Counting (ARC) to manage the memory usage of the app. Every time a new class
instance is created, ARC allocates memory to store it.

To free up the allocated memory, it needs to determine when an instance is no longer needed. Therefore, ARC
counts the strong references of each instance. It will deallocate the instance when its count of strong references
drops to zero.

2.8.2 What is the difference between a weak and a strong reference?


Creating a strong reference to an instance will increase its reference count. A weak reference only points to an
object and does not increase its reference count.

By default, a variable always creates a strong reference to an object. To use a weak reference instead, Swift
provides the weak keyword. For example:

weak var delegate: SomeDelegate?

2.8.3 What the difference between a weak and an unowned reference?


Just like a weak reference, an unowned reference does not increase the reference count of an instance.

The difference between a weak and an unowned reference is that a weak reference allows the object to become nil.
An unowned reference presumes that it will never become nil during its lifetime.

This can be useful in situations where we know that the object will always be in memory as long as the reference
itself is in memory.

2.8.4 What is a memory leak? How can memory leaks be avoided?


A memory leak is created when memory cannot be deallocated due to retain cycles. For example, when two
instances keep a strong reference to each other, ARC cannot release either of them.

Another example for a strong reference cycle is when we assign a closure to a property of a class instance while
the body of that closure captures the instance.

private var loadCategories: (([Category]) -> Void)?

func update() {

loadCategories { categories in

self.categories = categories

Memory leaks can be avoided by using weak or unowned references correctly. In the example above, capturing
self as a weak instead of a strong reference by using [weak self] fixes the retain cycle.

2.8.5 How can we detect memory leaks?

27
Xcode's Memory Report, which is available from the Debug navigator, shows the app's current memory
consumption. An indicator for memory leaks is a continuously growing memory usage, even if we are closing and
leaving certain parts of the application.

For detecting the root cause of a memory leak, Xcode offers a built-in Debug Memory Graph. The memory graph
shows where the app is using memory and how those uses are related.

An alternative or additional way is the Allocations Instrument which provides a timeline to investigate how the total
amount of memory increases and decreases as we use the app's interface.

28
3. Objective-C
Objective-C is becoming more and more obsolete after Swift was released in 2014. However, with existing apps
written in Objective-C still out there, knowledge on Objective-C may be required for an iOS position. This chapter
provides questions and answers on Objective-C basics and the interoperability between Swift and Objective-C.

Questions Overview
3.1 Classes & Objects

3.1.1 How is a class defined in Objective-C?

3.1.2 What is a header file in Objective-C?

3.1.3 What is an implementation file in Objective-C?

3.1.4 How are properties defined in Objective-C?

3.1.5 How can we define Objective-C properties as private or public?

3.1.6 Accessor methods for Objective-C properties are automatically synthesized by the compiler. What does that
mean?

3.1.7 How many parameters does the following Objective-C method have and what is its return type?

- (NSArray<Tea *> *)search:(NSString *)query inCategory:(NSString *)category;

3.1.8 What is the difference between instance and class methods? How are class methods defined in Objective-C?

3.1.9 How can we initialize an object in Objective-C?

3.1.10 Does Objective-C have an equivalent to optionals in Swift?

3.1.11 Do NSArray objects in Objective-C provide type safety?

3.1.12 How can we append characters to an NSString instance?

3.2 Categories & Extensions in Objective-C

3.2.1 What are categories in Objective-C?

3.2.2 How are categories implemented in Objective-C?

3.2.3 What are extensions in Objective-C?

3.3 Protocols in Objective-C

3.3.1 What is the difference between an Objective-C class interface and a protocol?

3.3.2 Is it possible to define optional methods in an Objective-C protocol?

3.3.3 How can a class conform to a protocol or to multiple protocols in Objective-C?

3.4 Value Types in Objective-C

3.4.1 Objective-C is a superset of C. What does that mean?

3.4.2 How can we define a struct in Objective-C?

29
3.4.3 How can we define an enum in Objective-C?

3.5 Blocks in Objective-C

3.5.1 What are blocks in Objective-C?

3.5.2 What are type definitions and how can we use them to simplify block usage in Objective-C?

3.5.3 When working with blocks in Objective-C, in which cases do we need to capture a weak reference to self and
how can we do that?

3.6 Interoperability of Objective-C & Swift

3.6.1 How can we access Objective-C code from Swift code?

3.6.2 What are nullability declarations and how do they help when using Objective-C code in Swift?

3.6.3 How can we use the NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END macros when working with mixed
Objective-C and Swift code?

3.6.4 How can we use the NS_SWIFT_NAME macro when working with mixed Objective-C and Swift code?

30
3.1 Classes & Objects
3.1.1 How is a class defined in Objective-C?
In Objective-C, we need two separate files to define a class - the class header as .h file and the class
implementation as .m file. All classes are derived from the base class NSObject which provides basic methods for
memory allocation and initialization.

3.1.2 What is a header file in Objective-C?


A header file contains the class interface which defines the public interface between instances of the class and the
outside world.

The syntax to declare a class interface looks as follows:

SomeClass.h

@interface SomeClass : NSObject

// define public properties and methods

@end

3.1.3 What is an implementation file in Objective-C?


An implementation file contains the actual implementation for an Objective-C class. It implements all methods
defined in the header file and also contains private properties and methods.

The syntax to declare a class implementation looks as follows:

SomeClass.m

#import "SomeClass.h"

@implementation SomeClass

// implement public methods

// define & implement private properties and methods

@end

3.1.4 How are properties defined in Objective-C?


In Objective-C, properties begin with the @property keyword followed by comma-separated attributes for memory
management and access control.

@property NSString *firstName;

@property (readonly, nonnull) NSString *lastName;

@property (weak) Parent *parent;

3.1.5 How can we define Objective-C properties as private or public?


The visibility of Objective-C properties is not achieved with attributes as in Swift, but rather by where we place them
in our class declarations.

31
Public properties are defined in the class header .h file. Private properties are defined in the class implementation
.m file.

3.1.6 Accessor methods for Objective-C properties are automatically


synthesized by the compiler. What does that mean?
Each property in Objective-C has a getter and a setter method which we use to access the property. For example:

NSString *title = [book title];

[book setTitle:@"Some book title"];

For every property, these access methods are created by the compiler for us automatically with the following
naming conventions:

the getter method has the same name as the property


the setter method starts with the word set and then uses the capitalized property name

3.1.7 How many parameters does the following Objective-C method have
and what is its return type?

- (NSArray<Tea *> *)search:(NSString *)query inCategory:(NSString *)category;

The above method has two parameters ( query and category ) and it returns an array of type NSArray<Tea *> .

Calling this method could look like this:

NSArray<Tea *> *results = [teaShop search:@"camomile" inCategory:@"herbs"];

3.1.8 What is the difference between instance and class methods? How are
class methods defined in Objective-C?
While instance methods need an instance of the class to be called, class methods don't. Class methods can be
used simply by referencing the class. No instance of the class is needed.

In Objective-C, class methods begin with a plus sign:

+ (instancetype)stringWithString:(NSString *)string;

3.1.9 How can we initialize an object in Objective-C?


In Objective-C, we can initialize an object by calling the new method:

SomeObject *object = [SomeObject new];

which is basically the same as a combination of the alloc and init method:

SomeObject *object = [[SomeObject alloc] init];

If an object needs to be initialized with a value, we can use the corresponding initializer:

32
NSNumber *age = [[NSNumber alloc] initWithInt:38];

As an alternative, some classes provide factory methods, for example:

NSNumber *age = [NSNumber numberWithInt:38];

Furthermore, we can use a special @ literal notation for some classes, for example:

NSNumber *age = @38;

NSString *title = @"Hello world!";

3.1.10 Does Objective-C have an equivalent to optionals in Swift?


In Objective-C, we have no possibility to explicitly declare an object as optional or non-optional. Every object in
Objective-C can become or be set to nil .

Declaring an object without initializing it will automatically set it to nil :

SomeObject *someObject;

It is safe to send messages to objects that are nil :

NSString *title = [someObject title];

In the example above, if someObject is nil , title will also become nil .

3.1.11 Do NSArray objects in Objective-C provide type safety?


No. An NSArray can be initialized with different object types. For example, the following array is valid in Objective-
C:

NSArray *fruits = @[@"apple", @"orange", @15];

There is a way though to add some type safety by declaring the array's element type:

NSArray<NSString*> *fruits = @[@"apple", @"orange", @15];

With the declaration above we get at least a compiler warning.

3.1.12 How can we append characters to an NSString instance?


Instances of the NSString class are immutable. Once a string has been initialized using NSString , their content
cannot be changed.

The only way to append text is to create a new NSString object. There are different ways to do that, for example:

NSString *newString = [oldString stringByAppendingString:@"characters to append"];

Alternatively, we can use the mutable class version NSMutableString :

33
NSMutableString *title = [[NSMutableString alloc] initWithString:@"Hello"];

[title appendString: @" world"];

34
3.2 Categories & Extensions in Objective-C
3.2.1 What are categories in Objective-C?
A category in Objective-C is the equivalent to a Swift extension. We can use categories to add new functionality to
an existing class, even if we don't have access to it.

3.2.2 How are categories implemented in Objective-C?


Just like a class, a category consists of two files, a header .h and an implementation .m file. The name of the
category is specified in parentheses behind the type. For example:

NSString+Common.h

@interface NSString (Common)

-(BOOL)contains:(NSString *)string;

@end

NSString+Common.m

#import "NSString+Common.h"

@implementation NSString (Common)

-(BOOL)contains:(NSString *)string {

NSRange range = [self rangeOfString:string];

return (range.location != NSNotFound);

@end

In the example above, we extend the NSString class by adding a method to check if a string contains another
string.

3.2.3 What are extensions in Objective-C?


Extensions in Objective-C are often referred to as anonymous categories.

An extension is similar to a category with the difference that it has no name and the methods it declares must be
implemented in the main @implementation block of the corresponding class.

A common use case for a class extension is to redeclare a public readonly property privately as readwrite , for
example:

ClassName.h

35
@interface ClassName : NSObject

@property (readonly) NSString* title;

@end

ClassName.m

@interface ClassName ()

@property (readwrite) NSString* title;

@end

36
3.3 Protocols in Objective-C
3.3.1 What is the difference between an Objective-C class interface and a
protocol?
While a class interface is used to define methods and properties associated with a specific class, a protocol is used
to declare methods and properties that are independent of any class.

A protocol is defined as follows:

@protocol ProtocolName

// protocol methods

@end

3.3.2 Is it possible to define optional methods in an Objective-C protocol?


Yes, it it possible to define optional methods in a protocol by using the @optional directive.

@protocol ProtocolName

// define required methods

@optional

// define optional methods

@end

3.3.3 How can a class conform to a protocol or to multiple protocols in


Objective-C?
To make a class conform to a protocol, we specify the protocol in angle brackets as follows:

@interface SomeClass : NSObject <SomeProtocol>

@end

To conform to multiple protocols, we separate them with a comma:

@interface SomeClass : NSObject <SomeProtocol, SomeOtherProtocol>

@end

We don't need to redeclare the protocol methods in the class interface.

37
3.4 Value Types in Objective-C
3.4.1 Objective-C is a superset of C. What does that mean?
Objective-C is a superset of C meaning that it is possible to compile any C code with an Objective-C compiler.
Therefore we can use any of the standard C types like int , float etc. in Objective-C code. For example:

int someInteger = 42;

3.4.2 How can we define a struct in Objective-C?


Structs are supported in Objective-C, but only as a C construct which can be used in Objective-C since it's a
superset of C.

struct Position

int x;

int y;

};

3.4.3 How can we define an enum in Objective-C?


Similarly to structs, there are only C enums available in Objective-C. These are much less flexible than Swift
enums.

typedef NS_ENUM(NSInteger, TeaCategory) {

TeaCategoryHerbal,

TeaCategoryGreen,

TeaCategoryFruits

} TeaCategory;

TeaCategory category = TeaCategoryFruits;

38
3.5 Blocks in Objective-C
3.5.1 What are blocks in Objective-C?
Blocks are Objective-C objects which are similar to closures in Swift.

They can take arguments, return values, capture values from the enclosing scope, being saved in variables and
passed as arguments to methods.

The block definition is prefaced with a caret symbol:

int (^add)(int, int) = ^ (int firstInteger, int secondInteger) {

return firstInteger * secondInteger;

};

int result = add(2, 5);

In the example above, we save a block in a variable. The block takes two int parameters and returns an int as
result. We then use the variable to invoke the block.

3.5.2 What are type definitions and how can we use them to simplify block
usage in Objective-C?
Objective-C provides a keyword called typedef , which we can use to give a type a new name.

In cases where we need to reuse a block signature, we can define our own type for that signature.

typedef int (^AdditionBlock)(int, int);

And use it as follows:

AdditionBlock add = ...

3.5.3 When working with blocks in Objective-C, in which cases do we need


to capture a weak reference to self and how can we do that?
Just like with closures in Swift, when capturing self in a block, we need to be careful not to create a retain cycle.

Since blocks maintain strong references to any captured objects, a reference cycle might occur if we are keeping a
reference to that block in a property at the same time.

To avoid a reference cycle, we can keep a weak self instead of a strong self reference as follows:

SomeObject * __weak weakSelf = self;

self.completion = ^{

[weakSelf doSomething];

39
3.6 Interoperability of Objective-C & Swift
3.6.1 How can we access Objective-C code from Swift code?
To use Objective-C code in Swift files, we can use a bridging header to make Objective-C files accessible to Swift.

Xcode offers to create the bridging header when we add a Swift file to an existing Objective-C app or vise versa.
We also can create it ourselves from Xcode's menu.

In the bridging header, we can import every Objective-C header that we want to expose to Swift.

#import "SomeFile.h"

3.6.2 What are nullability declarations and how do they help when using
Objective-C code in Swift?
We can annotate Objective-C code to indicate whether an instance can have a nil value. Swift uses those
annotations to import declarations either as optional or non-optional:

types with a nonnull annotation are imported as non-optionals


types with a nullable annotation are imported as optionals

- (nullable Tea *)search:(nonnull NSString *)name;

The example above is imported to Swift as follows:

func search(name: String) -> Tea?

Without a nullability annotation, Swift would import a type as an implicitly unwrapped optional:

func search(name: String!) -> Tea!

3.6.3 How can we use the NS_ASSUME_NONNULL_BEGIN and


NS_ASSUME_NONNULL_END macros when working with mixed Objective-C
and Swift code?
With the macros NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END , we can annotate regions in Objective-C as
nonnullable .

This can simplify the process of annotating Objective-C code if we'd like to import most of the types as non-
optionals into Swift.

This way, we only need to annotate nullable types:

40
NS_ASSUME_NONNULL_BEGIN

@interface SomeClass : NSObject

- (nullable String *)subtitleForItem:(SomeItem *)item;

@property NSString *title;

@property NSArray<SomeItem *> *items;

@end

NS_ASSUME_NONNULL_END

3.6.4 How can we use the NS_SWIFT_NAME macro when working with
mixed Objective-C and Swift code?
The NS_SWIFT_NAME macro allows us to rename Objective-C APIs for Swift usage. We can apply it to types,
methods and properties. For example:

@property BOOL enabled NS_SWIFT_NAME(isEnabled);

41
Xcode
Being the main iOS development tool, a confident usage of Xcode is expected from an iOS developer. This chapter
provides questions and answers on working and debugging with Xcode.

Questions Overview
4.1 Xcode Build System

4.1.1 What is an Xcode workspace?

4.1.2 What is an Xcode target?

4.1.3 What is an Xcode scheme?

4.1.4 What are build phases in Xcode?

4.1.5 What are build settings in Xcode?

4.1.6 What is a build configuration file?

4.2 Working with Xcode

4.2.1 What are .xcassets files?

4.2.2 What are .stringsdict files used for?

4.2.3 What are some examples of keyboard shortcuts that Xcode provides to work more efficiently?

4.2.4 How can we view the documentation for a certain type in Xcode?

4.2.5 Which refactoring options does Xcode provide?

4.3 Debugging with Xcode

4.3.1 What are breakpoints and which options does Xcode provide to configure them?

4.3.2 What is LLDB?

4.3.3 What options do we have to inspect a variable when debugging an iOS application with Xcode?

4.3.4 What is Instruments and how can it help us when developing iOS applications?

4.3.5 What is the Accessibility Inspector and how can it help us when developing iOS applications?

42
4.1 Xcode Build System
4.1.1 What is an Xcode workspace?
An Xcode workspace contains one or multiple projects. These projects usually relate to one another, for example
we could use a workspace to organize apps and the shared frameworks they link against.

4.1.2 What is an Xcode target?


An Xcode target specifies a product to build, for example an app, a framework, an app extension, unit tests etc. A
project usually contains multiple related targets.

For example, when creating a new App project, Xcode creates an app target, a target for unit tests and a target for
UI tests.

The instructions for building a product are build settings and build phases which we can edit in Xcode's project
editor. A project defines default build settings for all its targets. Each target can override those default settings.

4.1.3 What is an Xcode scheme?


An Xcode build scheme defines configurations for actions like building, running, testing, profiling etc. When we
execute one of those actions, Xcode uses the selected build scheme to determine what to do.

Only one scheme can be active at a time. We can customize existing schemes created by Xcode or create new
ones as needed.

43
For example, we can configure launch arguments to pass to the app, execute scripts before or after any action or
produce different builds like a debug or release build.

4.1.4 What are build phases in Xcode?


We can use build phases to specify files and scripts for a target. When building the target, Xcode combines build
phases with build settings to produce a product.

Xcode configures the initial build phases for each target at creation time, but we can modify or add additional build
phases, for example to specify dependencies or execute custom shell scripts.

4.1.5 What are build settings in Xcode?


Build settings allow us to control every step associated with the build process, including how source files are
compiled, whether debug information is being generated, how code is packaged and a lot more.

44
For example. we can configure different values for debug and release builds by clicking a setting's disclosure
triangle.

4.1.6 What is a build configuration file?


A build configuration file is a text file with an .xcconfig filename extension. We can use build configuration files to
be able to edit build settings outside of Xcode.

Build configuration files make it easier to manage build settings and to change them automatically for different
architectures and platforms.

45
4.2 Working with Xcode
4.2.1 What are .xcassets files?
An .xcassets file is an asset catalog which helps us organize and manage the app's resources. An asset set can
be an image, a color or a data file.

An asset set can contain multiple variants of a resource, for example for different device characteristics, languages
etc. The correct resource to use is automatically detected at runtime.

4.2.2 What are .stringsdict files used for?


A .stringsdict file is a property list that provides additional features to work with different languages.

Unlike a Localizable.strings file, a Localizable.strigsdict file is able to interpret the arguments we pass in and
select the right localized string based on it.

It allows us for example to define language plural rules or change a localized string for different interface widths and
devices.

4.2.3 What are some examples of keyboard shortcuts that Xcode provides
to work more efficiently?
Xcode provides a lot of keyboard shortcuts we can use to work more efficiently.

46
For example, the most basic shortcuts are those for running the application:

⌘ R to build and run the app


⌘ U to run tests
⇧ ⌘ K to clean the build folder
⌘ . to stop running the app

To quickly navigate back and forth when working on different files:

⌃ ⌘ → to go forward
⌃ ⌘ ← to go back
⌃ ⌘ J to reveal a file in the project navigator
⇧ ⌘ O to open a file quickly

To show and hide different Xcode areas quickly:

⌘ 0 to show and hide the project navigator


⌘ ⇧ Y to show and hide the debug console
⌘ ⌥ 0 to show and hide inspectors

4.2.4 How can we view the documentation for a certain type in Xcode?
We can use Xcode's Quick Help Inspector to show the documentation.

Alternatively, we can option-click a type to open a documentation dialog.

4.2.5 Which refactoring options does Xcode provide?


Xcode refactoring engine provides options to transform code locally within a Swift source file and also globally over
multiple files. For example:

Rename for renaming methods, classes, structs etc. over multiple files.
Edit All in Scope to rename code within a single file.
Extract to Method to extract pieces of code into a separate method
Extract to Variable to extract pieces of code into a separate variable
Convert Function to async to add async declarations to a function

47
4.3 Debugging with Xcode
4.3.1 What are breakpoints and which options does Xcode provide to
configure them?
We can use breakpoints to specify where the app pauses when running the debugger to investigate bugs.

We can edit a breakpoint, for example to define additional actions like printing to console or to set conditions.

Xcode also provides the possibility to manage breakpoints across the whole app in the Debug Navigator. Here, we
can also configure general breakpoints, for example to pause on an uncaught Swift error or Objective-C exception.

4.3.2 What is LLDB?


LLDB is the default debugger in Xcode which allows us to debug and explore iOS applications on iOS devices and
simulators. Xcode includes a console pane in the debugging area for direct access to LLDB commands.

4.3.3 What options do we have to inspect a variable when debugging an iOS


application with Xcode?
When the iOS application pauses at a breakpoint, we can either use Xcode's variable viewer or LLDB commands to
inspect variables in the console.

Common LLDB commands to print out the debugging description of an instance are po , p or v .

48
4.3.4 What is Instruments and how can it help us when developing iOS
applications?
Instruments is a performance-analysis and testing tool that’s part of the Xcode tool set.

Instruments can help us find memory problems such as leaks, identify power-consumption or networking issues,
improve responsiveness and more.

4.3.5 What is the Accessibility Inspector and how can it help us when
developing iOS applications?
The Accessibility Inspector is part of Xcode's developer tools and helps testing applications for accessibility.

For example, after choosing a simulator in Accessibility Inspector's devices menu, we can select UI elements in our
iOS applications and it shows us all relevant accessibility values.

49
50
SwiftUI
Since 2019, SwiftUI is Apple's main framework for building user interfaces for iOS. This chapter provides questions
and answers on the main aspects of SwiftUI.

Questions Overview
5.1 App Structure

5.1.1 What is the entry point of a SwiftUI application?

5.1.2 What is a scene in a SwiftUI application?

5.1.3 How can we perform work in response to life cycle events in a SwiftUI application?

5.1.4 What is the purpose of the UIApplicationDelegateAdaptor property wrapper?

5.2 SwiftUI Views

5.2.1 What is a view modifier in SwiftUI?

5.2.2 What is an example of a view modifier to align and position a view?

5.2.3 What is the core concept for aligning views in SwiftUI?

5.2.4 How do lazy stacks like LazyHStack or LazyVStack differ from standard stacks like HStack and VStack ?

5.2.5 What is a Spacer in SwiftUI and when is it useful?

5.2.6 How does a List differ from a combination of LazyVStack and ScrollView ?

5.2.7 What is the meaning of \.self in the following line of code?

ForEach(texts, id: \.self) { } // texts is of type Array<String>

5.2.8 Why should we avoid using AnyView when working with SwiftUI if possible?

5.2.9 What is the ViewBuilder attribute and in which cases is it useful when working with SwiftUI views?

5.2.10 How can we animate a color change when tapping on a button in SwiftUI?

5.3 SwiftUI Property Wrappers

5.3.1 When using a TextField in SwiftUI, we need to pass it a text argument of type Binding<String> . What is the
purpose of this type?

5.3.2 What is the difference between the State and the Binding property wrapper?

5.3.3 While State and Binding are used for value types, which property wrappers can we use for reference types
to setup a binding?

5.3.4 What is the difference between StateObject and ObservedObject ?

5.3.5 In which cases is the EnvironmentObject property wrapper useful?

5.4 SwiftUI & UIKit

5.4.1 How can a UIKit view be shown inside a SwiftUI view?

51
5.4.2 How can a UIViewController be used in a SwiftUI view?

5.4.3 How can we use a SwiftUI view inside UIKit?

52
5.1 App Structure
5.1.1 What is the entry point of a SwiftUI application?
Starting with iOS 14, Apple introduced the App protocol for pure SwiftUI applications which mostly replaced
AppDelegate and SceneDelegate . A basic SwiftUI App implements the body property as entry point of the
application.

@main

struct ExampleApp: App {

var body: some Scene {

WindowGroup {

ContentView()

5.1.2 What is a scene in a SwiftUI application?


A scene represents a part of the app’s user interface that has its own life cycle managed by the system.

An App instance presents the scenes it contains, while each Scene acts as the root element of a view hierarchy.

For example, the body property, that defines the entry point of a SwiftUI application, is of type some Scene . The
returned WindowGroup is a Scene which wraps SwiftUI views.

var body: some Scene {

WindowGroup {

ContentView()

5.1.3 How can we perform work in response to life cycle events in a SwiftUI
application?
We can observe scene life cycle changes by adding an environment property scenePhase and subscribe to
changes with the onChange(of:perform:) modifier .

53
struct ExampleApp: App {

@Environment(\.scenePhase) private var scenePhase

var body: some Scene {

WindowGroup {

ContentView()

.onChange(of: scenePhase) { (newScenePhase) in

switch newScenePhase {

case .active:

// The scene is in the foreground and interactive.

case .background:

// The scene isn't currently visible in the UI.

@unknown default:

break

We can also use this modifier on views.

5.1.4 What is the purpose of the UIApplicationDelegateAdaptor


property wrapper?
When managing life cycle events in SwiftUI, we still may need UIKit's AppDelegate or SceneDelegate in some
cases, for example to register for remote push notifications or to add quick home screen actions. In those cases, we
can use the UIApplicationDelegateAdaptor property wrapper.

struct ExampleApp: App {

@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

In doing so, SwiftUI instantiates the AppDelegate and calls the delegate's methods in response to life cycle events
or communications with the iOS system as we know it from UIKit.

54
5.2 SwiftUI Views
5.2.1 What is a view modifier in SwiftUI?
View modifiers in SwiftUI are used to modify a view's appearance for example font(_:) , padding(_:_:) ,
foregroundColor(_:) etc.

5.2.2 What is an example of a view modifier to align and position a view?


For example, the frame(width:height:alignment:) modifier wraps the view into a container of a given size and
aligns the view within this container.

Other modifiers like position(x:y:) and offset(x:y:) can also be used to position a child in its parent's
coordinate space.

5.2.3 What is the core concept for aligning views in SwiftUI?


The core concept for aligning views in SwiftUI is by using stacks, for example VStack for vertical, HStack for
horizontal alignment and ZStack to layer views on top of one another.

struct ContentView: View {

var body: some View {

VStack {

Text("Hello")

Text("world")

}.padding()

5.2.4 How do lazy stacks like LazyHStack or LazyVStack differ from


standard stacks like HStack and VStack ?
The standard stack views load their child views all at once while lazy stacks load their subviews as they become
visible.

In scenarios where we want to display a large scrolling set of data, we might want to use a lazy stack instead of a
standard one to improve performance.

ScrollView(.vertical) {

LazyVStack {

ForEach(recipes) { recipe in

RecipeView(recipe)

5.2.5 What is a Spacer in SwiftUI and when is it useful?


We can use Spacer to align views. It is a flexible space that expands as long as there is space.

55
For example, we can use a spacer to align views as follows:

HStack {

Image(systemName: "hare")

Spacer()

Image(systemName: "tortoise")

Since the spacer takes up as much space as possible, the turtle image is aligned to the right.

5.2.6 How does a List differ from a combination of LazyVStack and


ScrollView ?
Both are conceptually similar with the difference that a List provides additional styling by default, for example
separator lines between rows.

A List also supports actions for common tasks like inserting, ordering or removing items. For example, we can
add the onDelete(perform:) modifier to enable swipe-to-delete.

5.2.7 What is the meaning of \.self in the following line of code?

ForEach(texts, id: \.self) { } // texts is of type Array<String>

When we use List or ForEach to create dynamic views, SwiftUI needs to know how to identify them. If an object
conforms to the Identifiable protocol, SwiftUI will automatically use its id property for identifying.

In the example above, we use ForEach on an array of strings. Since a String does not conform to the
Identifiable protocol, we need to add the id as the second parameter.

By using \.self , we tell SwiftUI to use the whole object as the identifier. In our case, it tells SwiftUI that each
String value in the array is unique, so it can be used to identify each item in the loop.

5.2.8 Why should we avoid using AnyView when working with SwiftUI if
possible?
SwiftUI uses a so called structural identity mechanism, where it uses the view's type to identify it and to determine
when it should be updated. AnyView is a type-erased view that can be used as a wrapper for any other SwiftUI
view. Since it erases the view's type, it reduces SwiftUI’s ability to efficiently update the views.

5.2.9 What is the ViewBuilder attribute and in which cases is it useful


when working with SwiftUI views?
The ViewBuilder attribute allows us to compose multiple views into a single return type. It's useful in cases where
we want to move certain code parts of the view's body into our own function or property.

56
@ViewBuilder

private var nameView: some View {

if isEditable {

TextField("Your name", text: $name)

} else {

Text(name)

The example above only works with the ViewBuilder attribute applied. Without it, we would get the compiler error
saying that the return statements do not have matching underlying types.

5.2.10 How can we animate a color change when tapping on a button in


SwiftUI?
SwiftUI provides different possibilities to add view animations. For example, we could use the animation(_:value:)

view modifier which applies a specified animation when a certain state value changes:

@State private var color = Color.brown

var body: some View {

Button("Change color") {

color = color == .brown ? .mint : .brown

.padding(20)

.background(color)

.foregroundColor(.white)

.animation(.easeInOut, value: color)

57
5.3 SwiftUI Property Wrappers
5.3.1 When using a TextField in SwiftUI, we need to pass it a text
argument of type Binding<String> . What is the purpose of this type?
A binding creates a two-way connection between the property that stores data and the view that displays and
changes the data.

struct ExampleView: View {

@State private var username: String = ""

var body: some View {

VStack {

Text(username)

TextField("username", text: $username)

In the example above, we store the username value in a State wrapped property and pass the binding of that
state property into the TextField that will update it whenever the binding changes.

5.3.2 What is the difference between the State and the Binding
property wrapper?
State is used for the view's private state, whereas Binding creates a two-way connection between a view and a
state property defined outside of that view.

5.3.3 While State and Binding are used for value types, which
property wrappers can we use for reference types to setup a binding?
We can use the StateObject and ObservedObject property wrappers to setup a binding for reference types. The
reference type needs to conform to ObservableObject .

class User: ObservableObject {

@Published var firstName: String = ""

@Published var lastName: String = ""

struct ExampleView: View {

@ObservedObject var user: User

...

58
With the setup in the example above, SwiftUI will automatically update ExampleView whenever firstName or
lastName of the User object change.

5.3.4 What is the difference between StateObject and


ObservedObject ?
The difference between StateObject and ObservedObject is the same as between State and Binding . While
StateObject is used for the view's private state, ObservedObject creates a two-way connection between a view
and a state property defined outside of that view. The view should not create the instance of the ObservedObject

itself.

5.3.5 In which cases is the EnvironmentObject property wrapper


useful?
The EnvironmentObject property wrapper is useful for complex view hierarchies where we would have to pass the
observable object through several view initializers before it reaches the view where it's needed. With
EnvironmentObject , we only need to supply the environment object within one of the view’s parents. SwiftUI will
take care of the rest.

struct ExampleView: View {

@EnvironmentObject var user: User

SomeParentViewOfExampleView()

.environmentObject(user)

We can even make an EnvironmentObject available in the whole app by passing it into the root view of the app.

59
5.4 SwiftUI & UIKit
5.4.1 How can a UIKit view be shown inside a SwiftUI view?
To use a UIKit view in a SwiftUI view, we wrap the UIKit view in a view that conforms to the UIViewRepresentable

protocol.

struct ExampleView: UIViewRepresentable {

@Binding var title: String

func makeUIView(context: Context) -> UILabel {

let label = UILabel()

return label

func updateUIView(_ label: UILabel, context: Context) {

label.text = title

To conform to the UIViewRepresentable protocol, the following methods need to be implemented:

makeUIView to create and return a UIKit view


updateUIView gets called from SwiftUI every time state changes occur, so we can update the UIKit view
accordingly

5.4.2 How can a UIViewController be used in a SwiftUI view?


In SwiftUI, there is no concept of a view controller, everything is a view. But we can use the same approach as for
using UIKit views in SwiftUI views.

Only this time, the SwiftUI view needs to conform to the UIViewControllerRepresentable protocol which has
equivalent methods as the UIViewRepresentable protocol.

struct ExampleView: UIViewControllerRepresentable {

func makeUIViewController(context: Context) -> UIViewController {

// Create and return a UIViewController instance

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {

// Update view controller whenever changes occur

5.4.3 How can we use a SwiftUI view inside UIKit?


For using SwiftUI views inside UIKit, Apple provides a class named UIHostingViewController which is initialized
with a SwiftUI view. The instance can then be used like a standard UIViewController .

60
let vc = UIHostingController(rootView: Text("Hi"))

61
UIKit
With SwiftUI slowly pushing UIKit into the background, UIKit is still part of many existing iOS applications. A deep
knowledge of UIKit may be required for an iOS position. This chapter provides questions and answers around key
aspects of UIKit.

Questions Overview
6.1 App Structure

6.1.1 What is the difference between UIApplicationDelegate and UISceneDelegate ?

6.1.2 Which delegate method can we use to create and assign a root view controller when using UIKit?

6.1.3 What is the concept behind a view controller?

6.1.4 What is a storyboard?

6.1.5 What are .xib files?

6.1.6 Is there an alternative to using storyboards or xib files to create user interfaces with UIKit?

6.2 UIKit Views

6.2.1 What is the purpose of UINavigationController ?

6.2.2 Besides a navigation controller, which other container view controllers does UIKit offer?

6.2.3 What is the main concept behind Auto Layout that allows us to align views?

6.2.4 What is the purpose of a UIStackView ?

6.2.5 When working with a UITableView , what is the main purpose of a data source?

6.2.6 What is the concept behind intrinsic content size of a UIView ?

6.2.7 What are content-hugging and compression-resistance priorities?

6.2.8 How can a UIKit view be animated, for example, faded out?

6.2.9 Is there a way to draw on a UIView ?

6.3 User Interactions in UIKit

6.3.1 What is the concept behind the first responder in UIKit?

6.3.2 What is the target action mechanism in UIKit?

6.3.3 What is a gesture recognizer? What are examples of gesture recognizers provided by UIKit?

62
6.1 App Structure
6.1.1 What is the difference between UIApplicationDelegate and
UISceneDelegate ?
Prior to iOS 13, only UIKit's UIApplicationDelegate existed. It was the main entry point of the app and the center of
life cycle events where app launch, foreground, background modes etc. were handled.

Starting with iOS 13, Apple introduced so called scenes to support multiple windows on the iPad. For that, some
responsibilities of the AppDelegate were outsourced to the UISceneDelegate .

6.1.2 Which delegate method can we use to create and assign a root view
controller when using UIKit?
If the iOS application supports scenes, we could use the SceneDelegate's scene(_:willConnectTo:options:)

delegate method which is called when a scene was added to the app.

class SceneDelegate: NSObject, UISceneDelegate {

func scene(

_ scene: UIScene, willConnectTo session: UISceneSession,

options connectionOptions: UIScene.ConnectionOptions) {

if let windowScene = scene as? UIWindowScene {

let window = UIWindow(windowScene: windowScene)

window.rootViewController = UIViewController()

window.makeKeyAndVisible()

Otherwise, we can use the AppDelegate's application(_:didFinishLaunchingWithOptions:) delegate method which


is called when the launch process is almost done and the app almost ready to run.

63
class AppDelegate: NSObject, UIApplicationDelegate {

var window: UIWindow?

func application(

_ application: UIApplication, didFinishLaunchingWithOptions

launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

let window = UIWindow(frame: UIScreen.main.bounds)

window.makeKeyAndVisible()

window.rootViewController = RootViewController()

self.window = window

return true

6.1.3 What is the concept behind a view controller?


Every window has a root view controller, which provides the initial content. We change the app's interface by
presenting and dismissing view controllers.

A view controller manages a view hierarchy, interactions with those views and state needed to keep them up to
date. Every view controller contains a content view which is accessible by the view property and serves as the
root view of its view hierarchy.

A view controller is represented by the UIViewController type which we can subclass to create custom view
controllers.

6.1.4 What is a storyboard?


A storyboard is a file that can be used to layout the user interface of an iOS application which uses UIKit.

Xcode provides a visual editor for storyboards which can be used to add view controllers and views, configure them
and set constraints. In addition, we can connect view controllers and manage transitions between them.

6.1.5 What are .xib files?


Like a storyboard, a .xib file can be used to layout UIKit views.

Using .xib files was the main concept of layouting views in iOS before Apple introduced storyboards. A .xib file
is usually tied to a specific view controller.

6.1.6 Is there an alternative to using storyboards or xib files to create user


interfaces with UIKit?
Yes, we can use the programmatic approach to create user interfaces with UIKit. Anything that can be done with
storyboards or xibs can also be done in code.

64
6.2 UIKit Views
6.2.1 What is the purpose of UINavigationController ?
A navigation controller is a container view controller that provides a way to manage hierarchical child view
controllers.

A common example is a view controller displaying a table view and another view controller displaying the details of
a table view's item. By embedding the first one into a navigation controller, we can push the second one onto the
stack.

6.2.2 Besides a navigation controller, which other container view controllers


does UIKit offer?
Besides a navigation controller, UIKit provides other container view controllers to manage child view controllers, for
example:

UITabBarController that manages a tab bar displaying associated child view controllers when a tab gets
selected.
UIPageViewController that manages navigation between pages where a child view controller manages each
page.

6.2.3 What is the main concept behind Auto Layout that allows us to align
views?
When using Auto Layout, we use a constraint-based approach to layout views. Auto Layout dynamically calculates
the size and position of the views based on those constraints. It's able to dynamically respond to both internal and
external changes.

For example, when activating a constraint on a UIButton to be positioned 8px below a UILabel , its position will
change when the UILabel position changes.

6.2.4 What is the purpose of a UIStackView ?


With a UIStackView , we can position views in rows or columns.

let stackView = UIStackView()

stackView.axis = .vertical

stackView.spacing = 16.0

stackView.alignment = .center

stackView.addArrangedSubviews(imageView, titleLabel, textLabel)

In the example above, we setup a stack view which arranges views centered below each other with a spacing of
16px between them.

6.2.5 When working with a UITableView , what is the main purpose of a


data source?
Table views only manage the presentation of their data, not the data itself. That is why we tell the table view which
data to display by setting its data source to an object that conforms to UITableViewDataSource .

65
Its main responsibilities are providing the count of sections and cells, providing cells for each row, providing titles or
views for section headers, footers etc.

func tableView(UITableView, cellForRowAt: IndexPath) -> UITableViewCell

func tableView(UITableView, numberOfRowsInSection: Int) -> Int

Only the two protocol methods shown above are required.

6.2.6 What is the concept behind intrinsic content size of a UIView ?


Intrinsic content size represents the amount of space a view needs to display its content. A view can have an
intrinsic content height, width or both.

For example, the intrinsic content size of a UILabel will be the size of the text it contains.

Not all views have an intrinsic content size. When defining a custom view, we are able to decide if it has an intrinsic
content size depending on how we setup the view's constraints.

6.2.7 What are content-hugging and compression-resistance priorities?


Content-hugging and compression-resistance priorities indicate which constraints are more important, allowing Auto
Layout to make appropriate tradeoffs when satisfying the constraints.

The higher the content-hugging priority, the more a view resists growing larger than its intrinsic content size. The
higher the compression-resistance priority, the more a view resists shrinking smaller than its intrinsic content size.

titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)

As shown in the example above, we can use predefined priorities, for example .required , .defaultLow or
.defaultHigh .

6.2.8 How can a UIKit view be animated, for example, faded out?
UIView provides a type method animate(withDuration:animations:) we can use for animations.

UIView.animate(withDuration: 1) {

view.alpha = 0

The first argument withDuration is the animation's duration measured in seconds. The second parameter is a
closure containing changes to commit to views.

6.2.9 Is there a way to draw on a UIView ?


Yes. We can use Core Graphics to draw on a UIView by overriding func draw(_ rect: CGRect) .

66
override func draw(_ rect: CGRect) {

guard let context = UIGraphicsGetCurrentContext() else { return }

guard let gradient = CGGradient(

colorsSpace: CGColorSpaceCreateDeviceRGB(),

colors: [startColor.cgColor, endColor.cgColor] as CFArray,

locations: [0.0, 1.0]) else { return }

context.drawLinearGradient(

gradient,

start: CGPoint.zero,

end: CGPoint(x: 0, y: bounds.height),

options: []

When func draw(_ rect: CGRect) is called, UIKit has created and configured a graphics context for drawing. We
get a reference to it by using the UIGraphicsGetCurrentContext() function.

67
6.3 User Interactions in UIKit
6.3.1 What is the concept behind the first responder in UIKit?
Apps receive and handle events using responder objects. When an app receives an event, for example a touch
event from a user, UIKit automatically directs that event to the most appropriate responder object, known as the first
responder.

In case of a touch event, the first responder is the view in which the touch occurred. If the view does not handle the
event, UIKit sends the event to its parent UIView object. Unhandled events are passed from responder to
responder in the active responder chain.

6.3.2 What is the target action mechanism in UIKit?


Views that are subclassing the UIControl class such as buttons or sliders use the target-action mechanism to
report user interactions.

The addTarget(_:action:for:) method associates a target object and action method with the control.

loginButton.addTarget(

self,

action: #selector(loginButtonTapped(_:)),

for: .touchUpInside)

In the example above, the loginButtonTapped(_:) method gets called, when a user taps the loginButton .

6.3.3 What is a gesture recognizer? What are examples of gesture


recognizers provided by UIKit?
Gesture recognizers provide a way to implement user gesture interaction with views. We can add a gesture
recognizer to a view by calling the addGestureRecognizer(_:) method.

let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap(_:)))

view.addGestureRecognizer(tapGestureRecognizer)

UIKit provides gesture recognizers for various scenarios, for example:

UITapGestureRecognizer for single or multiple taps


UILongPressGestureRecognizer for long-press gestures
UISwipeGestureRecognizer for swipe gestures

68
Combine
With the release of Combine at WWDC 2019, Apple introduced a native way to write functional reactive code. This
chapter provides questions and answers on Combine and on functional reactive programming concepts in general.

Questions Overview
7.1 Publishers & Subjects

7.1.1 What is the difference between a subject and a publisher in Combine?

7.1.2 One of the main components of Combine are subjects. What is happening in the following line of code?

let countSubject: CurrentValueSubject<Int, Never>

countSubject.send(5)

7.1.3 What is the difference between a CurrentValueSubject and a PassthroughSubject ?

7.1.4 In which cases is the eraseToAnyPublisher() method of a subject helpful?

7.1.5 What purpose does a ConnectablePublisher have?

7.1.6 How does Combine's Future type work?

7.2 Subscribers in Combine

7.2.1 How can we receive new values from a publisher or a subject?

7.2.2 What is the purpose of Combine's Cancellable protocol?

7.2.3 When subscribing with methods like sink(receiveValue:) , are [weak self] references needed to avoid
retain cycles?

7.3 Operators in Combine

7.3.1 What is the difference between the map(_:) and compactMap(_:) operator in Combine?

7.3.2 How can we filter all even numbers from a publisher that emits Int values in Combine?

7.3.3 How can we add up all Int values emitted by a publisher into a single result value in Combine?

7.3.4 What is the difference between the reduce(_:_:) and scan(_:_:) operator in Combine?

7.3.5 What is the difference between the combineLatest(_:) and zip(_:) operator in Combine?

7.3.6 How can we convert a Combine publisher that emits Array<Int> values into a publisher that emits Int

values those arrays contain?

7.3.7 The following Combine code example combines multiple operators to produce an output. What is the printing
result?

69
let publisher1 = PassthroughSubject<[Int], Never>()

let publisher2 = PassthroughSubject<[Int], Never>()

publisher1

.merge(with: publisher2)

.flatMap { $0.publisher }

.filter { $0 % 2 == 0 }

.sink { print($0) }

publisher1.send([1, 3])

publisher2.send([6, 10])

publisher2.send([4, 19, 8])

7.3.8 The collect(_:) operator in Combine bundles a given amount of elements to an array. What is the output of
the following code?

cancellable = subject

.collect(2)

.sink { value in

print("received \(value)")

subject.send(1)

subject.send(2)

subject.send(3)

subject.send(4)

7.3.9 What does the autoconnect() operator do in the following code?

cancellable = Timer.publish(every: 1.0, on: .main, in: .default)

.autoconnect()

.sink{ date in

print("received \(date)")

7.3.10 What is the concept of back pressure in Combine, or reactive programming in general?

70
7.1 Publishers & Subjects
7.1.1 What is the difference between a subject and a publisher in Combine?
A publisher exposes values of a certain type over time and can be completed or optionally fail with an error. A
subject is basically a mutable publisher i.e. it has the ability to send new values after its initialization.

7.1.2 One of the main components of Combine are subjects. What is


happening in the following line of code?

let countSubject: CurrentValueSubject<Int, Never>

countSubject.send(5)

In the example above, we define a subject that can send Int values and never fails. In the second line, we send a
new value by using the subject's send method.

7.1.3 What is the difference between a CurrentValueSubject and a


PassthroughSubject ?
As the name indicates, a CurrentValueSubject subject has access to its current value whereas a
PassthroughSubject only passes the current value through, i.e. it has no access to it.

7.1.4 In which cases is the eraseToAnyPublisher() method of a


subject helpful?
A subject provides the possibility to send new values. A common practice is to keep the subject private, but making
it public as a publisher. This can be achieved by using the eraseToAnyPublisher() method on the subject:

struct Counter {

var countPublisher: AnyPublisher<Int, Never> {

countSubject.eraseToAnyPublisher()

private let countSubject: CurrentValueSubject<Int, Never>

This form of type erasure allows the outside world to subscribe to the publisher and receive new values from the
subject but prevents that anyone outside the struct or class can send new values.

This approach also preserves abstraction across APIs. By exposing the AnyPublisher type, we can change the
underlying implementation over time without affecting the outside usage.

7.1.5 What purpose does a ConnectablePublisher have?


When working with publishers in Combine, we sometimes need more control over when the values start to emit. For
example, if we have multiple subscribers and want them all to receive the first value a publisher emits.

For this scenario, Combine provides the ConnectablePublisher type which doesn’t produce any elements until we
call its connect() method.

71
We can convert any publisher or subscriber into a ConnectablePublisher by calling the method makeConnectable() .

let publisher = Just(5).makeConnectable()

someSubscriber = publisher.sink { newValue in

print(newValue)

// later on

someOtherSubscriber = publisher.sink { newValue in

print(newValue)

publisher.connect()

In the example above, the value will be emitted after we call the connect() method on the publisher, so both
subscribers receive it.

7.1.6 How does Combine's Future type work?


Combine's Future type is a single-output publisher. A common scenario to use a Future type is when working
with network requests. A network request is a one time asynchronous event that either succeeds or fails.

func randomNumberPublisher(min: Int, max: Int) -> Future<Int, Error> {

return Future() { promise in

guard min <= max else {

promise(.failure(RandomNumberError.minGreaterMax))

return

// load random number asynchronously

promise(.success(randomNumber))

A Future publisher is initialized with a closure that takes a Future.Promise . After doing some asynchronous work,
we call that closure with a Result that is either a success or a failure. Combine then automatically maps the result
into proper publisher events.

72
7.2 Subscribers in Combine
7.2.1 How can we receive new values from a publisher or a subject?
Common ways to receive new values from a publisher or a subject is by subscribing with the sink(receiveValue:)

or assign(to:on:) methods.

The sink(receiveValue:) method attaches a subscriber with closure-based behavior to a publisher. Every time the
publisher emits a new value, the closure is called.

cancellable = countPublisher

.sink { value in

print(value)

The assign(to:on:) method assigns each element from a publisher to a property of an object.

cancellable = countPublisher

.assign(to: \.count, on: someInstance)

7.2.2 What is the purpose of Combine's Cancellable protocol?


Combine's Cancellable protocol lets us control how long a subscription stays alive.

When subscribing with methods like sink(receiveValue:) or assign(to:on:) , Combine returns an object of
AnyCancellable type to which we can keep a reference to as long as we want to receive new values. As soon as a
cancellable is cancelled or deallocated, the subscription it was assigned to is automatically invalidated.

7.2.3 When subscribing with methods like sink(receiveValue:) , are


[weak self] references needed to avoid retain cycles?
When subscribing with methods like sink(receiveValue:) , Combine returns an AnyCancellable to which we keep
a reference as long as we want to receive new values.

In cases where the cancellable property is strongly captured by self and self is strongly captured by the
completion, we need to use [weak self] references to avoid retain cycles.

var cancellable: AnyCancellable?

func startTimer() {

cancellable = Timer.publish(every: 1, on: .main, in: .default)

.autoconnect()

.sink { [weak self] date in

self?.lastUpdated = date

73
7.3 Operators in Combine
7.3.1 What is the difference between the map(_:) and
compactMap(_:) operator in Combine?
Both operators transform each element with a provided closure. While map(_:) publishes all transformed
elements, compactMap(_:) only publishes elements that are not nil .

7.3.2 How can we filter all even numbers from a publisher that emits Int
values in Combine?
We can do that by using the filter operator which republishes all elements that match a provided closure.

7.3.3 How can we add up all Int values emitted by a publisher into a
single result value in Combine?
We can do that by using the reduce operator which applies a closure to all elements and publishes a final result
when the upstream publisher completes.

7.3.4 What is the difference between the reduce(_:_:) and


scan(_:_:) operator in Combine?
While the reduce(_:_:) operator only produces an end result after the upstream publisher completes, the
scan(_:_:) operator emits every in-between result.

74
7.3.5 What is the difference between the combineLatest(_:) and
zip(_:) operator in Combine?
Both operators publish a tuple of elements. While combineLatest(_:) publishes the most recent element every time
any of the publishers emits a value, zip(_:) waits for both publishers to emit a value.

7.3.6 How can we convert a Combine publisher that emits Array<Int>


values into a publisher that emits Int values those arrays contain?
We can do that by using the flatMap(maxPublishers:_:) operator which transforms all elements from an upstream
publisher into a new publisher.

7.3.7 The following Combine code example combines multiple operators to


produce an output. What is the printing result?

75
let publisher1 = PassthroughSubject<[Int], Never>()

let publisher2 = PassthroughSubject<[Int], Never>()

publisher1

.merge(with: publisher2)

.flatMap { $0.publisher }

.filter { $0 % 2 == 0 }

.sink { print($0) }

publisher1.send([1, 3])

publisher2.send([6, 10])

publisher2.send([4, 19, 8])

The merge operator produces [1, 3], [6, 10], [4, 19, 8, 6] by combining elements from publisher1 with
those from publisher2 .

The flatMap operator produces 1, 3, 6, 10, 4, 19, 8 by flattening the Int array into a sequence of Int

values.

The filter operator filters out all uneven values producing the end result 6, 10, 4, 8 . Those values will be
printed out one by one.

7.3.8 The collect(_:) operator in Combine bundles a given amount of


elements to an array. What is the output of the following code?

cancellable = subject

.collect(2)

.sink { value in

print("received \(value)")

subject.send(1)

subject.send(2)

subject.send(3)

subject.send(4)

The collect operator in the example above bundles every two emitted values to an array. The output is:

received [1, 2]

received [3, 4]

7.3.9 What does the autoconnect() operator do in the following code?

cancellable = Timer.publish(every: 1.0, on: .main, in: .default)

.autoconnect()

.sink{ date in

print("received \(date)")

76
Since Timer.publish(every:on:in:) produces a ConnectablePublisher , it won't produce elements until we explicitly
connect to it. By using the autoconnect() operator we tell the publisher to connect automatically as soon as a
subscriber attaches.

7.3.10 What is the concept of back pressure in Combine, or reactive


programming in general?
When working with subscribers in Combine, we mostly use sink(receiveValue:) and assign(to:on:) to receive an
unlimited amount of values from a publisher.

There may be some cases though, where processing those received values takes longer while new values arrive. In
this case, we may need to control the maximum amount of values that arrive to avoid some kind of blocking or
overflow.

This concept of limiting elements a subscriber receives is called back pressure. The publisher will only produce
elements when it has pending demand.

To apply back pressure in Combine, we can either create a custom subscriber or by using Combine's buffering or
timing operators like debounce(for:scheduler:options:) , throttle(for:scheduler:latest:) or collect(_:) .

77
Server Communication
Server communication is part of almost every iOS application. This chapter provides questions and answers on
general computer networking concepts and on specific iOS networking topics.

Questions Overview
8.1 HTTP Protocol

8.1.1 What are the main components of a HTTP URL? What purpose do they have?

8.1.2 What purpose do HTTP request methods like GET or POST have?

8.1.3 A server response contains a HTTP status code. What are some examples and their meaning?

8.1.4 What is the basic idea behind the OAuth protocol?

8.2 Working with JSON

8.2.1 A common format in which we get a server response is JSON. What value types are supported by a JSON
schema?

8.2.2 How can we convert a JSON response into native Swift types?

8.2.3 How can we convert a JSON that uses snake case notation into custom Swift types that use camel case
notation?

8.2.4 How can we decode or encode JSON dates to Swift's Date type?

8.2.5 When decoding JSON data in Swift, how can we map JSON keys to Swift properties with different namings?

8.3 Sending Requests in iOS

8.3.1 How can we create a HTTP request when developing an iOS application?

8.3.2 How can we send HTTP requests in iOS?

8.3.3 How can we download data from a server in iOS?

8.3.4 How can we upload data to a server in iOS?

8.3.5 When working with URLRequest , the default cache strategy is .useProtocolCachePolicy . What does that
mean?

8.3.6 On Apple platforms, a networking feature called App Transport Security requires that all HTTP connections
use HTTPS. Why?

78
8.1 HTTP Protocol
8.1.1 What are the main components of a HTTP URL? What purpose do they
have?
Every HTTP URL consists of the following main components:

scheme://host:port/path?query

Scheme - The scheme identifies the protocol used to access a resource, e.g. http or https.
Host - The host name identifies the host that holds the resource.
Port - Host names can optionally be followed by a port number to specify what service is being requested.
Path - The path identifies a specific resource the client wants to access.
Query - The path can optionally by followed by a query to specify more details about the resource the client
wants to access.

8.1.2 What purpose do HTTP request methods like GET or POST have?
HTTP defines a set of request methods to indicate the desired action to be performed for a given resource.

Examples are:

GET - retrieve a resource


POST - create a resource
PUT - update a resource
DELETE - delete a resource

8.1.3 A server response contains a HTTP status code. What are some
examples and their meaning?
The HTTP response status codes are separated into five categories:

1xx Informational: Request was received, process is continuing.


2xx Successful: Request was successfully received, understood and accepted.
3xx Redirection: Further action needs to be taken to complete the request.
4xx Client Error: Request contains bad syntax or cannot be fulfilled.
5xx Server Error: Server failed to fulfill an apparently valid request.

Examples:

200 OK: Request was successful.


201 Created: Request was successful, a new resource was created.
400 Bad Request: Server did not understand the request.
401 Unauthorized: Authentication has failed.

8.1.4 What is the basic idea behind the OAuth protocol?


OAuth allows users to grant third-party services access to their web resources without sharing their passwords.
This is possible through a security object known as access token.

79
If a third-party app wants to connect to the main service, it must get its own access token. The user's password is
kept safe inside the main service and cannot be obtained from the access token. Access tokens can be revoked if
the user does not want to use the third-party app anymore.

80
8.2 Working with JSON
8.2.1 A common format in which we get a server response is JSON. What
value types are supported by a JSON schema?
JSON supports the following data types:

string : a sequence of zero or more Unicode characters enclosed with with double quotation marks.
number : a signed decimal number that may contain a fractional part.
boolean : either of the values true or false .
null : an empty value
object : an unordered set of key-value pairs.
array : an ordered collection of values.

8.2.2 How can we convert a JSON response into native Swift types?
We can use Swift's Codable protocol to convert JSON to native Swift types. The first step is to define a Swift type
that has the same keys and value types as the JSON and conforms to Codable . For example:

struct Dog: Codable {

let name: String

let age: Int

Then we can use Swift's JSONDecoder type to convert JSON data to the corresponding type:

let decoder = JSONDecoder()

do {

let doggies = try decoder.decode([Dog].self, from: jsonData)

} catch {

In the same way, we can use Swift's JSONEncoder type to convert Swift types into JSON data.

8.2.3 How can we convert a JSON that uses snake case notation into
custom Swift types that use camel case notation?
All we need to do is to set the keyEncodingStrategy property of Swift's decoder to .convertFromSnakeCase :

let decoder = JSONDecoder()

decoder.keyDecodingStrategy = .convertFromSnakeCase

8.2.4 How can we decode or encode JSON dates to Swift's Date type?
To decode JSON dates to Swift's Date type, we can set the decoder's dateDecodingStrategy property. For
example:

81
let dateFormatter = DateFormatter()

dateFormatter.dateFormat = "yyyy-MM-dd"

let jsonEncoder = JSONEncoder()

jsonEncoder.dateEncodingStrategy = .formatted(dateFormatter)

We can choose between the following strategies:

.deferredToDate

.iso8601

.millisecondsSince1970

.secondsSince1970

formatted(DateFormatter)

custom((Decoder) -> Date)

8.2.5 When decoding JSON data in Swift, how can we map JSON keys to
Swift properties with different namings?
We can use the CodingKey protocol to do that. Codable types allow to declare a nested enumeration named
CodingKeys that conforms to the CodingKey protocol. For example:

struct User: Codable {

let username: String

let isValidated: Bool

enum CodingKeys: String, CodingKey {

case username

case isValidated = "validated"

By specifying raw-values, we control which JSON properties are assigned to which properties of our custom Swift
types.

82
8.3 Sending Requests in iOS
8.3.1 How can we create a HTTP request when developing an iOS
application?
One way of creating a HTTP request in Swift is by using URLRequest combined with URLComponents .

var components = URLComponents()

components.scheme = "https"

components.host = "api.github.com"

components.path = "/search/repositories"

components.queryItems = [

URLQueryItem(name: "q", value: "swift"),

URLQueryItem(name: "sort", value: "stars")

let url = components.url

let urlRequest = URLRequest(url: url)

URLComponents lets us construct a URL from its constituent parts and then access the full url to create a request.

8.3.2 How can we send HTTP requests in iOS?


One possibility to send HTTP requests is by using URLSession . With URLSessionConfiguration , we can configure a
caching behaviour, timeout values and HTTP headers.

A session works with tasks. After creating a session we can use URLSessionDataTask to send requests and receive
responses:

let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in

// Handle response.

task.resume()

Or when using async/await :

let (data, response) = try await URLSession.shared.data(for: urlRequest)

8.3.3 How can we download data from a server in iOS?


To download data from a server, we can use Swift's URLSessionDownloadTask type. A download request works
almost the same as a standard URL request with the only difference that we get a file url instead of a data object as
a response.

let task = URLSession.shared.downloadTask(with: url) { fileURL, response, error in

// Handle response

task.resume()

83
Or when using async/await :

let (fileURL, response) = try await URLSession.shared.download(from: url)

8.3.4 How can we upload data to a server in iOS?


To upload data to a server, we can use URLSessionUploadTask .

let task = urlSession.uploadTask(with: urlRequest, from: data) { data, response, error in

// Handle response

task.resume()

Or when using async/await :

let (data, response) = try await URLSession.shared.upload(for: urlRequest)

Instead of passing in a Data object, URLSession also provides the possibility to upload from a file URL.

8.3.5 When working with URLRequest , the default cache strategy is


.useProtocolCachePolicy . What does that mean?
When working with HTTP or HTTPS, available caching capabilities are built into the protocol. This way, a server is
able to communicate with a client on how the content should be cached. The .useProtocolCachePolicy policy uses
the caching logic defined in the protocol implementation.

For example, the server might send the following cache control headers:

Last-Modified: Mon, 08 Dec 2014 19:23:51 GMT

ETag: "adg2jl4kfmm5"

Cache-Control: max-age=533280

Expires: Sun, 03 May 2024 11:02:37 GMT

We don't need to dive deeper into what each header means, since the caching policy will do all the work for us.
Depending on the values a server sends, the caching policy will basically perform the following behaviour:

In case no cached response is available, the system fetches the data from the URL.
In case a cached response is available, not expired and doesn't always require revalidation, the system returns
the cached response.
In case a cached response is available, but expired or requires revalidation, a HEAD request is send to the
server to check if the resource changed. If it's changed, the system fetches the data from the URL. Otherwise,
it returns the cached response.

8.3.6 On Apple platforms, a networking feature called App Transport


Security requires that all HTTP connections use HTTPS. Why?
HTTPS is an extension of HTTP which is used for secure computer network communication. In HTTPS, the
communication protocol is encrypted using TLS.

The goal of HTTPS is to protect privacy and integrity of the exchanged data against eaves-droppers and man-in-
the-middle attacks. It is achieved through bidirectional encryption of communications between client and server.

84
Through App Transport Security (ATS), Apple is trying to ensure privacy and data integrity for all apps. There is a
way to circumvent these protections by setting NSAllowsArbitraryLoads to true in the app's Information Property

List file, for example for testing purposes. Apple rejects apps using this flag without a specific reason.

85
Concurrency Q&A
Apple introduced Swift's async/await syntax during WWDC21, allowing us to write asynchronous code in a shorter
and safer way. This chapter focuses on this concurrency model with async/await. It also provides questions and
answers on more low-level technologies like Grand Central Dispatch and Operations. Since they were used in iOS
applications before async/await, knowledge around these technologies may be required for an iOS position.

Questions Overview
9.1 Swift Concurrency with async/await

9.1.1 How can we call a function marked with async from another asynchronous function?

9.1.2 How can we call a function marked with async or async throws from a synchronous function?

9.1.3 How can we run an asynchronous task when a SwiftUI view appears?

9.1.4 How can we bridge a completion handler into an async function in Swift?

9.1.5 What are Swift actors?

9.2 Grand Central Dispatch & Operations

9.2.1 What is Grand Central Dispatch?

9.2.2 Dispatch queues can be either serial or concurrent. What is the difference?

9.2.3 What is the difference between a main and a global queue in Grand Central Dispatch?

9.2.4 How can we cancel a running asynchronous task in Grand Central Dispatch?

9.2.5 How can we avoid race conditions when using Grand Central Dispatch?

9.2.6 The Foundation framework provides the OperationQueue type. How is it different from a DispatchQueue ?

86
9.1 Swift Concurrency with async/await
9.1.1 How can we call a function marked with async from another
asynchronous function?
An async function can be called from another async function by using the await keyword. For example:

func loadCount() async -> Int {

...

func load() async {

let result = await loadCount()

...

9.1.2 How can we call a function marked with async or async throws
from a synchronous function?
We can call an async function from a synchronous function by using tasks.

A task is represented by the Task struct in Swift. When using a Task , we provide a closure that can contain
synchronous or asynchronous code to perform.

func someSynchronousFunction() {

Task {

let result = await loadCount()

For throwing functions, we can add a do catch block inside the closure:

func someSynchronousFunction() {

Task {

do {

let result = await loadCount()

} catch {

// Handle error

9.1.3 How can we run an asynchronous task when a SwiftUI view appears?
SwiftUI provides the task(priority:_:) method that we can use to start an asynchronous task right before the
view appears.

87
var body: some View {

Text(viewModel.status)

.task {

await viewModel.refreshStatus()

9.1.4 How can we bridge a completion handler into an async function in


Swift?
To write our own async/await functions, Swift provides so called continuations which give us control over
suspending and resuming a function.

To bridge a completion handler function, we can use Swift's withCheckedContinuation function:

func load() async -> Int {

return await withCheckedContinuation({ continuation in

load() { result in

continuation.resume(returning: result)

})

By using withCheckedContinuation , we suspend from the function and call resume() after we get a result from the
completion handler. Resuming from a continuation must happen exactly once.

9.1.5 What are Swift actors?


At the WWDC 2021, Apple introduced Swift actors as a new synchronization tool to prevent data races when
working with concurrent code. Data races occur when shared mutable data is accessed from different threads while
one or more of the threads writes the data.

Actors are represented by a new reference type called actor .

actor SomeActor {

let id = "abc"

var count = 10

func update() {

count-=1

The update method is not marked as async , but we still need to await it when calling.

let someActor = SomeActor()

Task {

await someActor.update()

88
An actor only gives us asynchronious access to its mutable data. Actors prevent data races by isolating data from
the outside world, mainly doing 3 things:

making sure that only one task (thread) at a time accesses their data
denying synchronious access to their mutable state from the outside
denying direct modification on their mutable state

89
9.2 Grand Central Dispatch & Operations
9.2.1 What is Grand Central Dispatch?
Grand Central Dispatch (GCD) provides a high-level API for managing concurrent tasks which is built on top of
threads. When using GCD, we execute blocks of code on dispatch queues represented by the DispatchQueue type.

9.2.2 Dispatch queues can be either serial or concurrent. What is the


difference?
Serial queues execute one task at a time in the order in which they are added to the queue. A serial queue
guarantees that a task finishes before another task starts while concurrent queues allow multiple tasks to run at the
same time.

let serialQueue = DispatchQueue(label: "serialQueue")

let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)

9.2.3 What is the difference between a main and a global queue in Grand
Central Dispatch?
The main queue runs on the main thread and is a serial queue. A common case for using the main queue is to
update the UI after doing some asynchronous work.

Global queues are concurrent queues with different priorities shared by the system.

DispatchQueue.global(qos: .default).async {

let text = loadDescription()

DispatchQueue.main.async {

label.text = text

9.2.4 How can we cancel a running asynchronous task in Grand Central


Dispatch?
When working with dispatch queues, instead of passing in a closure, we can execute a task by using the
DispatchWorkItem type. By referencing it, we can use its cancel() method when needed.

let workItem = DispatchWorkItem { [weak self] in

// Execute time consuming code.

// Start the task.

DispatchQueue.main.async(execute: workItem)

// Cancel the task.

workItem.cancel()

90
9.2.5 How can we avoid race conditions when using Grand Central
Dispatch?
A race condition can occur when multiple threads access the same data without synchronization and at least one
access is a write. For example if we read values from an array from the main thread while a background thread is
adding new values to that same array.

Data races can be the root cause behind unpredicatable program behaviours and crashes that are hard to find.

To avoid race conditions we could access the data on the same serial queue or write the data on a concurrent
queue with a barrier flag. For example:

var queue = DispatchQueue(label: "messages.queue", attributes: .concurrent)

queue.sync {

// Code for accessing data

queue.sync(flags: .barrier) {

// Code for writing data

By using a barrier, a concurrent queue becomes a serial queue for a moment. A task with a barrier is delayed until
all running tasks are finished. After the last task finishes, the queue executes its barrier block and continues its
concurrent behavior after that.

9.2.6 The Foundation framework provides the OperationQueue type.


How is it different from a DispatchQueue ?
An OperationQueue uses Grand Central Dispatch internally and provides a higher level of abstraction to implement
concurrency. It gives us more control over how the executed operations, for example by defining dependencies
between individual operations.

let operation1 = BlockOperation {

// Execute time consuming code.

let operation2 = BlockOperation {

// Execute time consuming code.

let operation3 = BlockOperation {

// operation1 and operation2 are finished.

operation3.addDependency(operation1)

operation3.addDependency(operation2)

91
Persisting Data in iOS
Persisting pieces of data is required in many applications. For different scenarios, Apple offers different ways to
store data. This chapter contains questions and answers on the iOS File System, UserDefaults, Keychain and the
Core Data framework.

Questions Overview
10.1 UserDefaults

10.1.1 For which use cases would you use the UserDefaults class?

10.1.2 Which datatypes does UserDefaults support out of the box?

10.1.3 Does writing and reading values to and from UserDefaults happen synchronously or asynchronously?

10.1.4 Is it possible to store a Swift struct that conforms to the Codable protocol in UserDefaults ?

10.1.5 Are there any pitfalls when storing Codable Swift structs in UserDefaults by using a JSON encoder?

10.2 iOS File System

10.2.1 When in comes to reading and writing files in iOS applications, what is a so called sandbox directory?

10.2.2 How can we access resource files, for example a json file that we included in our app?

10.2.3 How can we save a user-generated file in the documents directory?

10.3 Core Data

10.3.1 When working with Core Data, what is a .xcdatamodeld file?

10.3.2 When working with Core Data, what are managed objects?

10.3.3 When working with Core Data, what is a managed object context?

10.3.4 When working with Core Data, what is a fetch request?

10.3.5 What is a database migration? What is the difference between a lightweight and a heavyweight migration
when working with Core Data?

10.3.6 Are there alternatives to doing a migration after changing the data model in Core Data?

92
10.1 UserDefaults
10.1.1 For which use cases would you use the UserDefaults class?
The UserDefaults class provides an interface to persistently store key-value pairs when developing iOS
applications. It can be used for example to store user preferences.

10.1.2 Which datatypes does UserDefaults support out of the box?


Out of the box, UserDefaults supports data types which are also supported by property lists. Those are Bool ,
Float , Double , Int , String , URL , Date and Data .

We can also store arrays and dictionaries if their elements are one of the types listed above.

10.1.3 Does writing and reading values to and from UserDefaults


happen synchronously or asynchronously?
UserDefaults caches all values we store so it can provide a synchronous API for reading and writing data. Under
the hood, it asynchroniously writes those values to the underlying database.

// Persisting a value in UserDefaults

UserDefaults.standard.set(true, forKey: "isValidated")

// Getting a value from UserDefaults

let isValidated = UserDefaults.standard.bool(forKey: "isValidated")

10.1.4 Is it possible to store a Swift struct that conforms to the Codable


protocol in UserDefaults ?
Not directly. To store a Swift struct conforming to Codable , we would need to convert it to one of the supported
types, for example by using a JSON encoder to convert it to a Data type:

if let contentData = try? JSONEncoder().encode(content) {

userDefaults.set(contentData, forKey: "content")

10.1.5 Are there any pitfalls when storing Codable Swift structs in
UserDefaults by using a JSON encoder?
Yes. In case we need to change the struct in future developments we might not be able to read previously saved
data with a JSON decoder, for example when adding non-optional properties.

93
10.2 iOS File System
10.2.1 When in comes to reading and writing files in iOS applications, what
is a so called sandbox directory?
Each iOS application has its own sandbox directory for reading and writing files. For security reasons, every
interaction of the iOS app with the file system is limited to this sandbox directory. Exceptions are access requests to
user's data like photos, music, contacts etc.

The sandbox directory has a specific structure. It contains the app's bundle with all of its resource files that we have
read-only access to. It also contains data directories like Documents to store user-generated content and
Library/Application Support or Library/Caches that we can use for app support files and temporary data.

10.2.2 How can we access resource files, for example a json file that we
included in our app?
To access a resource in our app's bundle, the Foundation framework provides the Bundle type which we can use
to locate a resource without knowing the structure of the bundle.

let url = Bundle.main.url(forResource: "nameOfTheResource", withExtension: "json")

10.2.3 How can we save a user-generated file in the documents directory?


We can use the FileManager type to manage files within system directories. For example, to locate the documents
directory:

let directoryURL = try FileManager.default.url(

for: .documentDirectory,

in: .userDomainMask,

appropriateFor: nil,

create: false)

Starting with iOS 16, we can also access directories by using one of the URL's type properties, for example
URL.documentsDirectory .

To store a file in the documents directory, we could encode the data into a Data type and then use its
write(to:options:) function:

let fileURL = URL.documentsDirectory.appendingPathComponent("fileName")

let data = try encoder.encode(someEncodable)

try data.write(to: fileURL)

94
10.3 Core Data
10.3.1 When working with Core Data, what is a .xcdatamodeld file?
A .xcdatamodeld file represents a Core Data model. Within the file, we can use Xcode's built-in editor to add
entities, their attributes, define relationships and more.

10.3.2 When working with Core Data, what are managed objects?
Each entity we setup in the .xcdatamodeld file describes an object and has a corresponding class that inherits from
NSManagedObject .

A NSManagedObject subclass might look as follows:

@objc(User)

public class User: NSManagedObject {

@NSManaged public var username: String?

@NSManaged public var lastLogin: Date?

@NSManaged public var verified: Bool

@NSManaged public var posts: NSSet?

Each attribute has an @NSManaged annotation which indicates that Core Data dynamically provides its
implementation at runtime, based on the associated entity description.

10.3.3 When working with Core Data, what is a managed object context?
A managed object context is represented by the NSManagedObjectContext type and has a central role in the life cycle
of managed objects.

It allows us to retrieve objects from a persistent store, create new objects, delete and edit objects. These changes
remain in memory until we either discard the changes or save them back to the persistent store.

95
let newUser = User(context: managedObjectContext)

newUser.username = ...

// setting more attributes

do {

try managedObjectContext.save()

} catch {

// handle error

In the example above, we create a new user and then use the save() method on the context to save all changes.

10.3.4 When working with Core Data, what is a fetch request?


Represented by NSFetchRequest , we can use a fetch request to define which managed objects we want to request
from the managed context.

An example to fetch all users might look like this:

let usersFetchRequest = NSFetchRequest<User>(entityName: "User")

usersFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \User.username, ascending: true)]

do {

let users = try viewContext.execute(usersFetchRequest)

} catch {

// handle error

As we can see in the example above, a fetch request specifies which entity to search. Optionally, we can add a
predicate to filter the results, a sort descriptor, a fetch limit and more.

When working with SwiftUI, we can use the @FetchRequest property wrapper to retrieve data from Core Data.

@FetchRequest(

sortDescriptors: [NSSortDescriptor(keyPath: \User.username, ascending: true)])

private var users: FetchedResults<User>

10.3.5 What is a database migration? What is the difference between a


lightweight and a heavyweight migration when working with Core Data?
A database migration is performed whenever we need to change the structure of the objects i.e. make changes to
the data model.

For a specific set of changes, Core Data can perform an almost automatic data migration, also called lightweight
migration. Examples of those changes are adding, renaming or deleting entities, attributes or relationships,
changing the relationship type and more.

When changes to the data model exceed the capabilities of a lightweight migration, we need to do a heavyweight
i.e. manual migration.

96
10.3.6 Are there alternatives to doing a migration after changing the data
model in Core Data?
Yes. For example if we are using Core Data as a cache mechanism and all stored user data can fully be restored,
e.g. from a backend api, we could just delete and rebuild the data store instead of doing a migration.

97
Security
Many applications need to manage some kind of sensitive user data. To ensure a high level of security, Apple
provides different technologies to be used by developers. This chapter provides questions and answers on specific
security-related iOS technologies such as keychain or shared web credentials, as well as general cryptography
concepts.

Questions Overview
11.1 CryptoKit

11.1.1 What is the concept behind public-key cryptography?

11.1.2 What are common use cases for public-key cryptography?

11.1.3 How can we create a digital signature with CryptoKit?

11.1.4 What are the advantages and disadvantes of symmetric-key cryptography compared to public-key
cryptography?

11.1.5 What are characteristics of a cryptographic hash function?

11.1.6 What are use cases of a cryptographic hash function?

11.1.7 How can we create a hash value with CryptoKit?

11.2 Authentication

11.2.1 What are shared web credentials and which benefits do they provide to users?

11.2.2 Which steps are required to support shared web credentials with password autofill in iOS applications?

11.2.3 What data can we store in Apple's keychain?

11.2.4 What is multi-factor authentication?

11.2.5 Starting with iOS 16, Apple introduced a new security feature called passkeys. How do passkeys work?

98
11.1 CryptoKit
11.1.1 What is the concept behind public-key cryptography?
The concept behind public-key cryptography is based on two linked keys, a public and a private key. Anyone can
encrypt messages using a public key, but only the holder of the corresponding private key can decrypt that
message.

It is extremely difficult (and practically almost impossible) to compute the private key from the public key. Therefore
it is safe to share the public key, for example over a network.

11.1.2 What are common use cases for public-key cryptography?


Common public-key cryptography use cases are encryption and digital signatures.

Digital signatures are used when we need to verify a sender’s identity and the integrity of the data, for example
when dealing with payments, legal agreements, contracts etc. The sender uses their private key to sign, and the
receiver uses the sender’s public key to verify.

11.1.3 How can we create a digital signature with CryptoKit?


In CryptoKit, we can choose between the P256/P384/P521 ECC algorithm and the Curve25519 ECC algorithm.
They both have approximately the same security level and small key sizes.

Creating a private-public key pair and using them to create a digital signature might look as follows:

let privateKey = Curve25519.Signing.PrivateKey()

let publicKey = privateKey.publicKey

let signature = try privateKey.signature(for: messageDigest)

11.1.4 What are the advantages and disadvantes of symmetric-key


cryptography compared to public-key cryptography?
In symmetric-key cryptography, the same key is used for encrypting and decrypting data.

Compared to public-key encryption, it's less complex and executes faster.

On the other hand, a disadvantage of symmetric encryption is that a shared secret needs to be exchanged before
being able to use it.

11.1.5 What are characteristics of a cryptographic hash function?


Hash functions map a message of arbitrary size to a fixed size hash value, also called a message digest.

A cryptographic hash function is:

a one-way function - meaning that it's practically irreversible


deterministic - meaning that the same message always results in the same hash value
unique - meaning that it's practically impossible to find two different messages with the same hash value

11.1.6 What are use cases of a cryptographic hash function?

99
Cryptographic hash functions have many security applications, for example in digital signatures, message
authentication, password verification and more.

Let's look at the password verification example in more detail. When a user enters a password into our app to login,
we hash the entered password and send it to the server for verification. Passwords which are stored on the server
are also computed hash values of the original passwords. This way, we never have to store the original password or
send it over the network but still are able to verify it.

11.1.7 How can we create a hash value with CryptoKit?


CryptoKit provides the algorithms SHA-256, SHA-384 and SHA-512. The numbers indicate the digest size. Its
Insecure container also provides SHA-1 and MD5, but those are considered to be insecure and available only for
backwards compatibility.

Hashing a password with SHA-512 can be done in one line:

let hashedPassword = SHA512.hash(data: Data(password.utf8))

100
11.2 Authentication
11.2.1 What are shared web credentials and which benefits do they provide
to users?
Shared web credentials combined with Apple's password AutoFill feature allow users to login into the same account
on different devices without having to remember their credentials.

For example, when signing up in a web app, users are able to generate a password and to save the credentials in
their iCloud keychain. When they run a native app to access the same account at some point, the app suggests the
credentials stored for the website in the password QuickType bar. After the user authenticates, for example by using
Face ID, the system prefills the credentials - so there is no need to enter them again.

11.2.2 Which steps are required to support shared web credentials with
password autofill in iOS applications?
To support shared web credentials with password autofill in an iOS application, the following steps are required:

1. Creating and configuring an associated domain file for shared web credentials.
2. Adding the associated domain file to the related website.
3. Adding an associated domain entitlement to the iOS app.
4. Setting the correct AutoFill type on relevant text fields.

11.2.3 What data can we store in Apple's keychain?


Apple's Keychain API is designed to store sensitive user data like credentials, credit card information or certificates
in an encrypted database.

11.2.4 What is multi-factor authentication?


Multi-factor authentication is an authentication method in which a user is granted access to an application or a
service only after successfully presenting two or more pieces of authentication evidence.

For example when signing in into an iOS application, a user needs to enter their credentials and additionally provide
a one time code that was sent to their phone.

11.2.5 Starting with iOS 16, Apple introduced a new security feature called
passkeys. How do passkeys work?
Passkeys are credentials built to eliminate security problems like weak or reused passwords, credential leaks and
phishing attacks.

They are built on the WebAuthn standard which uses public-private key cryptography. When registering for a new
account, an identified Apple device generates a public-private key pair, for example an iPhone with Face ID
identification.

The private key is securely retained on the device and synced via iCloud Keychain with other devices, while the
public key is shared with the server for a later challenge.

When a user later returns to the app to sign in, the server generates a one-time challenge to prove that their device
has the private key associated with the public key. After accepting the challenge, the user's device signs it using the
private key and sends the signature back to the server for validation. The server verifies it and in case of success
allows the user to sign in.

101
102
Automated Testing
Testing is an important part in every software development process. Apple offers tools to write unit, UI and
performance tests for iOS applications. This chapter provides questions and answers on general testing concepts
and specific iOS testing tools.

Questions Overview

12.1 Unit Tests


12.1.1 What is a unit test and how can we write unit tests for an iOS application?

12.1.2 What is code coverage and when is it useful?

12.1.3 What is the Given, When, Then style regarding unit tests?

12.1.4 Which purpose do the setUp() and tearDown() methods have when working with the XCTest framework?

12.1.5 When writing tests, what are the benefits of mocks and stubs?

12.1.6 How can we use the XCTUnwrap method when writing unit tests?

12.1.7 How can we unit test async/await functions in Swift?

12.1.8 What are expectations when writing unit tests?

12.2 Performance Tests

12.2.1 What is a performance test?

12.2.2 How can we write performance tests for an iOS application?

12.3 UI Tests

12.3.1 What is a user interface test?

12.3.2 How can we write UI tests for an iOS application?

12.3.3 When writing UI tests, when is the waitForExistence(timeout:) method useful?

12.3.4 When writing UI tests, how can we interact with the iOS application code base?

103
12.1 Unit Tests
12.1.1 What is a unit test and how can we write unit tests for an iOS
application?
Unit tests are automated tests that validate a piece of code to make sure it behaves as intended.

When we create a project or a target, Xcode includes a unit test target in the scheme that builds the app. Unit tests
are written using Apple's XCTest framework. To start writing unit tests, we would create a subclass of XCTestCase

and define methods that start with test .

For example, one of the unit tests for Swift's joined array method could look like this:

func testJoinedTwoWords() throws {

let words = ["Hello", "world"]

let result = words.joined(separator: " ")

XCTAssertEqual(result, "Hello world")

12.1.2 What is code coverage and when is it useful?


Code coverage indicates the percentage of lines of code being executed when running the corresponding unit tests.
We can enable code coverage in the target's scheme settings.

While code coverage doesn’t tell us anything about the quality of the unit tests, it is a great way to identify code
paths that aren’t tested yet.

12.1.3 What is the Given, When, Then style regarding unit tests?
The Given, When, Then style is commonly used to make tests easier to read. The essential idea is to structure the
unit test into three parts:

1. The given part setups the pre-conditions of the test


2. The when part executes the actions we want to test
3. The then part validates the outcome

For example:

func testJoinedTwoWords() throws {

// Given

let words = ["Hello", "world"]

// When

let result = words.joined(separator: " ")

// Then

XCTAssertEqual(result, "Hello world")

104
12.1.4 Which purpose do the setUp() and tearDown() methods have
when working with the XCTest framework?
To reuse data in multiple test methods, we can define them as instance variables in our test case class. We can
then use the setUp() method to set up the initial state for each test method and the tearDown() method to clean
up after the test is complete.

12.1.5 When writing tests, what are the benefits of mocks and stubs?
Mocking and stubbing are key techniques to manage dependencies and isolate a specific behaviour of the tested
object. For example we can test a specific behaviour of an app without depending on data from the backend.

Replacing dependencies of the tested object with mocked or stubbed objects makes it more easily to assert and
verify outcomes in our tests.

12.1.6 How can we use the XCTUnwrap method when writing unit tests?
The XCTest framework provides the XCTUnwrap method which attempts to unwrap an optional and throws an error
i.e. lets the test fail if the optional is nil.

func testMixingSmoothies() throws {

let smoothieMaker = SmoothieMaker()

let bananaSmoothie = try XCTUnwrap(smoothieMaker.makeBananaSmoothie())

This approach eliminates the need to write repeating XCTFail descriptions. All we need to do is to mark our test
function with throws .

12.1.7 How can we unit test async/await functions in Swift?


We can unit test async/await functions by marking the test function with async and call await on the async call.

func testFibonacciSequence() async {

let result = await fibonacciSequence(count: 7)

XCTAssertEqual([0, 1, 1, 2, 3, 5, 8], result)

12.1.8 What are expectations when writing unit tests?


When we can't use Swift async , expectations allow us to test asynchronous code, for example when working with
completion handlers or delegates.

func testFibonacciSequence() {

let expectation = XCTestExpectation(description: "Waiting for fibonacci sequence to load.")

fibonacciSequence(count: 7) { result in

XCTAssertEqual([0, 1, 1, 2, 3, 5, 8], result)

expectation.fulfill()

wait(for: [expectation], timeout: 10.0)

105
12.2 Performance Tests
12.2.1 What is a performance test?
With performance tests, we can measure how fast code runs. Performance tests also help us identify performance
changes as the code base evolves.

12.2.2 How can we write performance tests for an iOS application?


To measure how long some code takes to execute we can use XCTest 's measure method.

func testNextMovePerformance() {

measure {

brain.calculateNextMove()

On the first run, Xcode establishes a baseline for each performance test and will fail in the future if the performance
significantly changes.

106
12.3 UI Tests
12.3.1 What is a user interface test?
User interface tests test the application from the outside, from the user's point of view. UI testing provides a way to
ensure that the app's UI interactions keep working as expected while the codebase evolves.

12.3.2 How can we write UI tests for an iOS application?


Just like with unit testing, we can use the XCTest framework to write UI tests. The approach is different though,
because we are interacting with the app itself and not with the code.

We do that by using the iOS accessibility system. The XCTest framework provides methods to access and interact
with the UI elements e.g. via their accessibility identifier.

func testRegister() throws {

let app = XCUIApplication()

app.launch()

app.buttons["registerButton"].tap()

XCTAssertTrue(app.textFields["nameTextField"].exists)

XCTAssertTrue(app.textFields["emailTextField"].exists)

...

12.3.3 When writing UI tests, when is the


waitForExistence(timeout:) method useful?
The waitForExistence(timeout:) method assures that we don't get any test failures due to animations where views
might need some time to appear on the screen.

XCTAssertTrue(loginRequestButton.waitForExistence(timeout: timeout))

12.3.4 When writing UI tests, how can we interact with the iOS application
code base?
If we need to interact with the application code base from our UI tests, for example to perform some test-specific set
up, we can use the launchArguments array:

let app = XCUIApplication()

app.launchArguments = ["setup-for-ui-testing"]

app.launch()

In the iOS application, we can check if it was started from a UI test as follows:

if CommandLine.arguments.contains("setup-for-ui-testing") {

setupForUITesting()

107
108
What's next?
Thank you for reading this book. I hope it has helped you on your iOS development journey.

If you are interested in more content, I'm publishing articles on different iOS topics once a week on tanaschita.com.

If you like to provide some feedback, or just say hi, feel free to write me an email to [email protected].

109

You might also like