Preparing For A Technical iOS Job Interview
Preparing For A Technical iOS Job Interview
1. Introduction
About this book
Acknowledgements
Copyright
2. Swift
Swift - Questions Overview
3. Objective-C
Objective-C - Questions Overview
3.3 Protocols
3.5 Blocks
4. Xcode
Xcode - Questions Overview
5. SwiftUI
SwiftUI - Questions Overview
1
5.1 App Structure
5.2 Views
6. UIKit
UIKit - Questions Overview
6.2 Views
7. Combine
Combine - Questions Overview
7.2 Subscribers
7.3 Operators
8. Server Communication
Server Communication - Questions Overview
8.1 HTTP Protocol
9. Concurrency
Concurrency - Questions Overview
9.1 Swift Concurrency with async/await
11. Security
Security - Questions Overview
2
11.1 Cryptography
11.2 Authentication
12.3 UI Tests
What's next?
Thank you for reading
3
Introduction
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.
Contact: [email protected]
Website: tanaschita.com
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.3 What are optionals in Swift and what are their benefits?
2.1.5 What is the difference between value and reference types 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.3.2 When working with Swift's String type, what is the outcome of the following print statement?
fruit1 = fruit2
fruit2 = "banana"
print(fruit1)
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.4.5 What is a higher order function? What are some examples of higher order functions in Swift?
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?
extension String {
2.5.5 In addition to generic functions, Swift enables us to define generic types. What are generic types?
2.5.7 What does the some keyword do in the following Swift code?
2.6.3 How can we call a throwing function inside another throwing function?
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?
})
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 = 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.
extension String {
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.
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.
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.
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
return database
}()
Lazy stored properties allow us to avoid doing expensive work unless it's really needed, for example when creating
complex objects.
@propertyWrapper
struct UserTextInput {
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:
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.
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.
numbers.insert(7)
Dictionaries are unordered collections of key-value pairs. The key is unique and acts as the identifier for a value in
the dictionary.
2.3.2 When working with Swift's String type, what is the outcome of the
following print statement?
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.
15
let result = (15, "apples")
The example shows a tuple of type (Int, String) . The individual tuple values can be named.
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.
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
showImage(image)
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.
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.
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.
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?
The variable named action in the example above has the type of a function that takes an Int and returns
nothing.
switch inputType {
case .numberInput:
return validateNumberInput
Int(input) != nil
The validateFunction method returns a function that we can store in a variable and then use it to validate some
input.
print(validate("5"))
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.
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.
number = 8
var number = 10
modify(number)
print(number) // prints 8
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.
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 {
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 {
return ...
extension String {
The above code produces a compiler error, since extensions are not allowed to have stored properties.
protocol Shop {
associatedtype Item
The concrete type for the associated type is specified when the protocol is adopted.
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.
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.
struct Array<Element> {
The generic Element type can be then used inside the defined struct, for example as a method parameter:
To create a new Array instance, we specify the actual type to be stored in the array within angle brackets:
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:
// ...
2.5.7 What does the some keyword do in the following Swift code?
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:
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.
case wrongPassword
throw LoginError.wrongPassword
// ...
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.
23
do {
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:
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.
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.
We can support subscripts for any custom type by adding a subscript method to that type.
extension Shop {
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 age = 10
Each variable we insert into the string literal is wrapped in a pair of parentheses, prefixed by a backslash.
25
let description: String
if number % 2 == 0 {
} else {
By using the ternary conditional operator, the example above can be shortened as follows:
})
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.
To shorten it further, we can use the shorthand argument names provided by Swift.
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.
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.
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:
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.
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.
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.
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.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?
3.1.8 What is the difference between instance and class methods? How are class methods defined in Objective-C?
3.3.1 What is the difference between an Objective-C class interface and a protocol?
29
3.4.3 How can we define an enum 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.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.
SomeClass.h
@end
SomeClass.m
#import "SomeClass.h"
@implementation SomeClass
@end
31
Public properties are defined in the class header .h file. Private properties are defined in the class implementation
.m file.
For every property, these access methods are created by the compiler for us automatically with the following
naming conventions:
3.1.7 How many parameters does the following Objective-C method have
and what is its return type?
The above method has two parameters ( query and category ) and it returns an array of type NSArray<Tea *> .
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.
+ (instancetype)stringWithString:(NSString *)string;
which is basically the same as a combination of the alloc and init method:
If an object needs to be initialized with a value, we can use the corresponding initializer:
32
NSNumber *age = [[NSNumber alloc] initWithInt:38];
Furthermore, we can use a special @ literal notation for some classes, for example:
SomeObject *someObject;
In the example above, if someObject is nil , title will also become nil .
There is a way though to add some type safety by declaring the array's element type:
The only way to append text is to create a new NSString object. There are different ways to do that, for example:
33
NSMutableString *title = [[NSMutableString alloc] initWithString:@"Hello"];
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.
NSString+Common.h
-(BOOL)contains:(NSString *)string;
@end
NSString+Common.m
#import "NSString+Common.h"
-(BOOL)contains:(NSString *)string {
@end
In the example above, we extend the NSString class by adding a method to check if a string contains another
string.
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
@end
ClassName.m
@interface ClassName ()
@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.
@protocol ProtocolName
// protocol methods
@end
@protocol ProtocolName
@optional
@end
@end
@end
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:
struct Position
int x;
int y;
};
TeaCategoryHerbal,
TeaCategoryGreen,
TeaCategoryFruits
} TeaCategory;
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.
};
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.
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:
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:
Without a nullability annotation, Swift would import a type as an implicitly unwrapped optional:
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.
40
NS_ASSUME_NONNULL_BEGIN
@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:
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.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.3.1 What are breakpoints and which options does Xcode provide to configure them?
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.
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.
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.
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.
44
For example. we can configure different values for debug and release builds by clicking a setting's disclosure
triangle.
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.
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:
⌃ ⌘ → to go forward
⌃ ⌘ ← to go back
⌃ ⌘ J to reveal a file in the project navigator
⇧ ⌘ O to open a file quickly
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.
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.
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.3 How can we perform work in response to life cycle events in a SwiftUI application?
5.2.4 How do lazy stacks like LazyHStack or LazyVStack differ from standard stacks like HStack and VStack ?
5.2.6 How does a List differ from a combination of LazyVStack and ScrollView ?
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.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?
51
5.4.2 How can a UIViewController be used in a SwiftUI view?
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
WindowGroup {
ContentView()
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.
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 {
WindowGroup {
ContentView()
switch newScenePhase {
case .active:
case .background:
@unknown default:
break
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.
Other modifiers like position(x:y:) and offset(x:y:) can also be used to position a child in its parent's
coordinate space.
VStack {
Text("Hello")
Text("world")
}.padding()
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)
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.
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.
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.
56
@ViewBuilder
if isEditable {
} 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.
view modifier which applies a specified animation when a certain state value changes:
Button("Change color") {
.padding(20)
.background(color)
.foregroundColor(.white)
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.
VStack {
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 .
...
58
With the setup in the example above, SwiftUI will automatically update ExampleView whenever firstName or
lastName of the User object change.
itself.
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.
return label
label.text = title
Only this time, the SwiftUI view needs to conform to the UIViewControllerRepresentable protocol which has
equivalent methods as the UIViewRepresentable protocol.
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.2 Which delegate method can we use to create and assign a root view controller when using UIKit?
6.1.6 Is there an alternative to using storyboards or xib files to create user interfaces with UIKit?
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.5 When working with a UITableView , what is the main purpose of a data source?
6.2.8 How can a UIKit view be animated, for example, faded out?
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.
func scene(
window.rootViewController = UIViewController()
window.makeKeyAndVisible()
63
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
window.makeKeyAndVisible()
window.rootViewController = RootViewController()
self.window = window
return true
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.
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.
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.
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.
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.
stackView.axis = .vertical
stackView.spacing = 16.0
stackView.alignment = .center
In the example above, we setup a stack view which arranges views centered below each other with a spacing of
16px between them.
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.
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.
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.
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.
66
override func draw(_ rect: CGRect) {
colorsSpace: CGColorSpaceCreateDeviceRGB(),
context.drawLinearGradient(
gradient,
start: CGPoint.zero,
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.
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 .
view.addGestureRecognizer(tapGestureRecognizer)
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.2 One of the main components of Combine are subjects. What is happening in the following line of code?
countSubject.send(5)
7.2.3 When subscribing with methods like sink(receiveValue:) , are [weak self] references needed to avoid
retain cycles?
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
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>()
publisher1
.merge(with: publisher2)
.flatMap { $0.publisher }
.filter { $0 % 2 == 0 }
.sink { print($0) }
publisher1.send([1, 3])
publisher2.send([6, 10])
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)
.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.
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.
struct Counter {
countSubject.eraseToAnyPublisher()
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.
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() .
print(newValue)
// later on
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.
promise(.failure(RandomNumberError.minGreaterMax))
return
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
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.
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.
func startTimer() {
.autoconnect()
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.
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.
75
let publisher1 = 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])
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.
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]
.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.
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.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.1 How can we create a HTTP request when developing an iOS application?
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:
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:
Examples:
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:
Then we can use Swift's JSONDecoder type to convert JSON data to the corresponding type:
do {
} 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 :
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"
jsonEncoder.dateEncodingStrategy = .formatted(dateFormatter)
.deferredToDate
.iso8601
.millisecondsSince1970
.secondsSince1970
formatted(DateFormatter)
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:
case username
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 .
components.scheme = "https"
components.host = "api.github.com"
components.path = "/search/repositories"
components.queryItems = [
URLComponents lets us construct a URL from its constituent parts and then access the full url to create a request.
A session works with tasks. After creating a session we can use URLSessionDataTask to send requests and receive
responses:
// Handle response.
task.resume()
// Handle response
task.resume()
83
Or when using async/await :
// Handle response
task.resume()
Instead of passing in a Data object, URLSession also provides the possibility to upload from a file URL.
For example, the server might send the following cache control headers:
ETag: "adg2jl4kfmm5"
Cache-Control: max-age=533280
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.
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.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:
...
...
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 {
For throwing functions, we can add a do catch block inside the closure:
func someSynchronousFunction() {
Task {
do {
} 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()
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.
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.
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.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 {
DispatchQueue.main.async {
label.text = text
DispatchQueue.main.async(execute: workItem)
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:
queue.sync {
queue.sync(flags: .barrier) {
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.
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.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.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.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.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.
We can also store arrays and dictionaries if their elements are one of the types listed above.
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.
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:
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 .
@objc(User)
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 = ...
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.
do {
} 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(
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.4 What are the advantages and disadvantes of symmetric-key cryptography compared to public-key
cryptography?
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.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.
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.
Creating a private-public key pair and using them to create a digital signature might look as follows:
On the other hand, a disadvantage of symmetric encryption is that a shared secret needs to be exchanged before
being able to use it.
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.
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.
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.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.3 UI Tests
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
For example, one of the unit tests for Swift's joined array method could look like this:
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:
For example:
// Given
// When
// Then
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.
This approach eliminates the need to write repeating XCTFail descriptions. All we need to do is to mark our test
function with throws .
func testFibonacciSequence() {
fibonacciSequence(count: 7) { result in
expectation.fulfill()
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.
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.
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.
app.launch()
app.buttons["registerButton"].tap()
XCTAssertTrue(app.textFields["nameTextField"].exists)
XCTAssertTrue(app.textFields["emailTextField"].exists)
...
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:
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