Skip to main content

What does the CaseIterable protocol do?

One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

As the name suggests,CaseIterable is a protocol that provides a handy way of iterating through all of the individual cases in an enum.

When using an enum that conforms to CaseIterable,you can access a collection of all of the enum鈥檚 cases by using the allCases property:

enum CompassDirection: CaseIterable {
case north, south, east, west
}

// "There are 4 directions."
print("There are \(CompassDirection.allCases.count) directions.")

let caseList = CompassDirection.allCases.map({ "\($ 0 )" })
.joined(separator: ", ")
//"north, south, east, west"
print(caseList)

allCases provides the cases in the order of their declaration.

If your enum does not contain any associated values or availability attributes, Swift will automatically synthesize conformance to the CaseIterable protocol for you.

What does the CodingKey protocol allow you to do?

2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In the event that the keys in a JSON response do not match the variable names in your Codable model exactly, you can use the CodingKey protocol to bridge specific properties that differ only in naming.

Simply put, the CodingKey protocol provides you with more granular control of the Codable protocol鈥檚 serialization and deserialization behavior.We can utilize thisprotocolwith the help of a nested enum defined within our Codable model.

Here鈥檚 an example JSON:

{
"url": "https://dynaimage.cdn.cnn.com/cnn/best-cakes.jpg",
"img_caption": "Japanese Dessert",
"img_source": "CNN"
}

We may want to pick more Swifty names for our variables, so our corresponding Codable model may look like this:

struct Article: Codable {
let url: String
let caption: String
let source: String
}

Since the property names and the JSON response鈥檚 keys differ,Codable鈥檚 default deserialization behavior will fail here.

We鈥檒l need to implement the CodingKey protocol:

struct Article: Codable {
let url: String
let caption: String
let source: String

enum CodingKeys: String, CodingKey {
case url
case caption = "img_caption"
case source = "img_source"
}
}

Now, we are able to influence Codable鈥檚 deserialization behavior and the JSON response can be converted to an Article without issue.

What does the File鈥檚 Owner do?

2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

When you load a .xib file and specify the owner property,the class responsible for loading the .xib now becomes the File's Owner.

open func loadNibNamed(_ name: String, owner: Any?,
options: [UINib.OptionsKey : Any]? = nil) -> [Any]?

// In ViewController.swift
Bundle.main.loadNibNamed("ExampleView", owner: self, options: nil)

Put simply, the File鈥檚 Owner is responsible for loading the .xib and facilitating communication between the code and the elements defined in the view. In the example above, ViewController initiates loading the .xib file therebymaking it the File鈥檚 Owner. As a result, it will now serve as the middle-man between the .xib file and our application鈥檚 code.

Once the .xib file has completed loading, the File鈥檚Owner is responsible for managing the view's contents and binding all of the declared IBOutlets and IBActions in your code to the view's corresponding UI components.

When we specify the File鈥檚 Owner in our .xib directly, we're effectively assigning a placeholder value that says -"This class will load me, interact with my UI, and create the necessary bindings to my various UI elements".

It's important to recognize that the File鈥檚 Owner is an independent entity and not a part of, nor bound to, the .xib itself - it鈥檚 just the class that assumes this middle-man responsibility.

What does the final keyword do?

One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Imagine you are working on a SDK or a particularly sensitive piece of code - something that needs to be performant, secure, etc. You鈥檇 likely want to ensure that your code is used in exactly the way you intend it to be.

That鈥檚 where the final keyword comes in. The final keyword prevents aclassfrom being subclassed / inherited from and indicates to other developers that thisclassisn鈥檛 designed to be subclassed.

So, in the SDK example, you would likely mark relevant classes asfinalto ensure your code is used only in the way you intended.

Marking properties and functions as final tells theSwift compiler that the method should be called directly (static dispatch) rather than looking up a function from a method table (dynamic dispatch).

This reduces function call overhead and provides a small boost in performance.

What does the lazy keyword do?

2 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The lazy keyword allows you to defer the initialization of a variable until the first time it鈥檚 used; it鈥檚 similar to the concept of "lazy loading".

In the following example, the fake User variable will not be initialized until the first time the property is accessed. This allows us to prevent the slow process of creating the object until we鈥檙e absolutely sure we鈥檒l need it:

lazy var fakeUser = try! User(dictionary:
JSONService.parse(filename:"FakeUserJSON"))

There鈥檚 a few important things to note when working with lazy variables.

Firstly, a lazy property must always be declared asa variable. The lazy property won鈥檛 have an initial value until after the containing object鈥檚 initialization is complete. So, we鈥檒l need to be able to update the variable鈥檚 value at a later point in the application鈥檚 execution. Moreover, the initial value of the lazy property could be dependenton some outside factors which means the appropriate initialization value cannot be determined until runtime. So, for both these reasons, the lazy property needs to be mutable.

It鈥檚 also common to create a lazy property for objects that are computationally expensive to initialize as there鈥檚 no point creating an expensive resource if it鈥檚 never used.

// Creating a DateFormatter is expensive.
// lazy lets us ensure we only create it if we need it.
private lazy var dateFormatter:DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
return dateFormatter
}()

It鈥檚 also important to understand the distinction between a lazy property and a computed property. A computed property regenerates its value every time it鈥檚 accessed whereas a lazy property creates its value once and then maintains it for the rest of the app鈥檚 execution.

What does the mutating keyword do?

2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In Swift,structsare value types which means the properties contained within are immutable by default. So, if we want to be able to modify the values within astruct, we鈥檒l need to use the mutating keyword. This keyword only applies to valuetypes as reference types are not immutable in this way.

Whenever we call a function that uses this keyword and modifies the struct's properties, Swift will generate a new struct in-place with the modifications applied and will overwrite our original struct.

struct User {
var firstName = "Aryaman"
var lastName = "Sharda"

func makeLowercase() {
// The following lines cause a compilation error:
// Cannot assign to property: 'self' is immutable
firstName = firstName.lowercased()
lastName = lastName.lowercased()
}
}

Let鈥檚 add the mutating keyword:

struct User {
var firstName = "Aryaman"
var lastName = "Sharda"

mutating func makeLowercase() {
firstName = firstName.lowercased()
lastName = lastName.lowercased()
}
}

When working withmutatingfunctions, we鈥檒l need to declare the as a variable since we鈥檙e making changes to the struct's properties:

let user = User()

// Compilation Error!
// Cannot use mutating member on immutable value: 'user' is
// a 'let' constant
user.makeLowercase()

When we make it a variable, we have no such issue:

// No error
var user = User()
user.makeLowercase()

What does the nil coalescing operator do?

One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

We can use the nil coalescing operator ?? to providea default value in an expression involving an Optional. If the Optional resolves to nil, our default value will be used instead.

var username: String?

// Output: Hello, stranger!
print("Hello, \(username ?? "stranger")!")

username = "@aryamansharda"

// Output: Hello, @aryamansharda!
print("Hello, \(username ?? "stranger")!")

What does the rethrows keyword do?

2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

You鈥檙e likely already familiar with the throws keyword which is one of the simplest mechanisms for propagating an error in our code.

Swift also includes the rethrows keyword which indicates that a function accepts a throwing function as a parameter. More specifically, functions declared with the rethrows keyword must have at least one throwing function parameter.

Consider Swift鈥檚 map function:

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

Simply speaking, we know that map takes in some function as input and applies that to every element in an array. If the passed in function doesn鈥檛throw, then we can call map without try:

func doubleInput(_ input: Int) -> Int {
input * 2
}

[ 1 , 2 , 3 , 4 , 5 ].map { doubleInput($ 0 ) }

This is all pretty normal so far, but what if the function passed into map can throw an error? In that case, we鈥檒l have to call map with try:

func doubleInput(_ input: Int) throws -> Int {
guard input != 0 else {
throw Error.invalidRequirement
}

return input * 2
}

try [ 1 , 2 , 3 , 4 , 5 ].map { doubleInput($ 0 ) }

The takeaway here is that if map was instead declared with throws, in both examples we鈥檇 have to call map with try even if the passed in function didn鈥檛 throw. This would be inelegant and would clutter our code with unnecessary try statements.On the other hand, if map were declared without throws or rethrows we wouldn鈥檛 be able to pass in a throwing function to begin with.

The rethrows keyword allows us to handle both cases elegantly - throwing and non-throwing functions. It enables us to create functions that don鈥檛 necessarily throw errors of their own, but simply forward errors from one or more of their function parameters when applicable.

What does the typealias keyword do?

2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The typealias keyword allows us to define our own keyword to represent an existing data type in our application. This does not create a new type - it simply provides a new name for an existing type.

An excellent example is Swift's Codable protocol which is implemented as a typealias.

public typealias Codable = Decodable & Encodable

By the way, did you notice that you can combine protocols with & in Swift?

Now, whenever the compiler sees Codable,it will replace it with Decodable & Encodable and continue compilation.

Imagine we were trying to represent time on a clock. We could use typealias to make the tuple representing our clock easier to work with:

typealias ClockTime = (hours: Int, min: Int)

func drawHands(clockTime: ClockTime) {
print(clockTime.hours) // 3
print(clockTime.min) // 30
}

ClockTime( 3 , 30 )

If you wish to make the typealias accessible throughout your codebase, declare it outside of any class or enclosing type. Otherwise, the typealias will be limited in scope as any other variable would be.

// Accessible across the codebase
typealias ClockTime = (hours: Int, min: Int)

class HelloWorld {
// Only available within this class
typealias Greeting = String

func sayGreeting(greeting: Greeting) {}
}

It's easy to overuse typealias and thereby limit the discoverability of your code, so it鈥檚 important to exercise a little restraint.

What does viewDidLayoutSubviews() do?

2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Simply put, viewDidLayoutSubviews() allows you to make customizations to views after they鈥檝e been positioned by AutoLayout, but before they are visible to the user.

Whenever the bounds change for a UIViewController鈥檚 view (i.e. device rotation), it鈥檚 likely that the position and size of all the subviews will need to be updated as well. So, the system will calllayoutSubviews() to perform this change.

Then, once your UIViewController has finished laying out all of its subviews (all of your subviews are in their correct location and their frames honor whatever AutoLayout constraints you鈥檝e specified), the system will call viewDidLayoutSubviews().

From here, if you need to further customize or override any changes prior to the view being visible on screen, viewDidLayoutSubviews() gives youan opportunity to do so.

If you鈥檙e wondering why we can鈥檛 make these changes in viewDidLoad() or viewWillAppear(), it鈥檚 because the frames of the subviews aren鈥檛 finalized by the time those functions are called. They鈥檙e only considered finalized once layoutSubviews() finishes running, so our only option to make customizations is in viewDidLayoutSubviews().

The order of the UIViewController lifecycle is as follows:

  1. loadView()
  2. viewDidLoad()
  3. viewWillAppear()
  4. viewWillLayoutSubviews()
  5. viewDidLayoutSubviews()
  6. viewDidAppear()
  7. viewWillDisappear()
  8. viewDidDisappear()