Skip to main content

What is the difference between static and dynamic dispatch?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Swift, like many other languages, allows a class to override methods and properties declared in its superclass. As a result, the program has to determine at runtime the correct version of the method or property to use.

This process is called dynamic dispatch.

Dynamic dispatch is implemented with the help of a method table (a.k.a. witness table). The witness table is used to figure out which implementation of a function to call - the superclass鈥檚 implementation or the subclass鈥檚 implementation.

Dynamic dispatch enables polymorphism which allows us to increase the expressiveness of our code, but it introduces a constant amount of runtime overhead every time we call a function. As a result, you鈥檒l typically want to avoid polymorphism in performance sensitive code.

If you don鈥檛 need dynamic dispatch (you don鈥檛 want the function or property to be overridden), you can improve performance by using the final keyword which prohibits overriding. This will allow us to use static dispatch instead which doesn鈥檛 incur this performance penalty. Moreover, static dispatch allows us to leverage additional compiler optimizations.

Finally, static dispatch is supported by both value and reference types. Dynamic dispatch is only supported by reference types as it requires inheritance and value types can鈥檛 support inheritance.

What is the difference between strong, weak, and unowned?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Please see the previous answer鈥檚 explanation of Automatic Reference Counting.

All of these keywords are different ways of describing how one object maintains a reference to another object.

strongis the default keyword in iOS and will incrementthe reference count of whatever object it鈥檚 referring to.

weak does not increment the reference count and the object it references can be nil. This is commonly used when working with delegates.

unowned does not increment the reference count either,but promises that the value it references will not be nil during its lifetime.

What is the difference between the App ID and the Bundle ID?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

During phone screens, especially for Senior iOS roles, I鈥檝e been tested on my understanding of provisioning profiles, development and distribution certificates, App Store Connect, and everything related to managing an iOS release.

As part of that line of questioning, I鈥檝e often been asked to clarify the difference between the App ID and the Bundle ID.

The Bundle ID is simply an identifier written in reverse DNS format that uniquely identifies a single app.

The following example should look pretty familiar to you:

com.AryamanSharda.WalkingRoutes

The Bundle ID can only contain alphanumeric characters and a period.

Since the Bundle ID is specific to an application, it鈥檚 useful in helping the system distinguish between the installation of a new app or an app update. Also, because a single Xcode project can have multiple targets and therefore output multiple apps, the Bundle ID lets you uniquely identify each of your project鈥檚 targets.

The App ID is a two-part string used to identify one or more apps from a single development team. It consists of an Apple issued Team ID and your application鈥檚 Bundle ID. Additionally, the App ID is used to specify what App Services (Game Center, iCloud, In-App Purchases, Push Notifications, etc.) are available to your application.

The Team ID is created when you open a new Developer Account with Apple and is unique to your specific development team.

Here鈥檚 an App ID that matches a specific application:

Explicit App ID:A123456789.com.AryamanSharda.WalkingRoutes

We can also have the App ID match multiple applications from the same development team:

Wildcard App ID:A123456789.com.AryamanSharda.*

What is the difference between the designated and convenience initializer?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Every class requires a designated initializer as it鈥檚 responsible for initializing stored properties and calling the superclass鈥檚 init().

A convenience initializer can be thought of as a wrapper around the designated initializer. They allow you to create a simpler initialization option for your class that either provides defaults for certain initialization parameters or helps transform some input into the exact format the designated initializer needs.

In Swift, we can create a convenience initializer by placing the keyword convenience before the init. A class can have any number of convenience initializers. These initializers can even call other convenience initializers in turn, but eventually they鈥檒l need to call the designated initializer:

class Money: NSObject {
let value: Int
let currencyCode: String

init(value: Int, currencyCode: String) {
self.value = value
self.currencyCode = currencyCode
super.init()
}

convenience init?(value: String, currencyCode:String) {
guard let numericValue =Int(value) else {
return nil
}

self.init(value: numericValue, currencyCode:currencyCode)
}
}

In this example, the designated initializer expects value to be an Int. So, we can create a convenience initializer that helps us handle scenarios when we might have a String as input instead.

Notice that the convenience initializer is eventually calling the designated initializer.

What is the difference between the stack and the heap?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The system uses the stack to store anything on the immediate thread of execution; it is tightly managed and optimized by the CPU.

When a function creates a variable, the stack will store that variable for the lifetime of the function call. Since the stack is so strictly organized, it鈥檚 very efficient and fast.

The system uses the heap to store reference types. The heap is a large pool of memory from which the system can request and dynamically allocate blocks of memory.

The lifetime of the items on the heap are flexible and dynamic as the heap doesn鈥檛 automatically destroy its data like the stack does. Instead, explicit allocation and deallocation calls are needed.

This makes creating and removing data from the heap a slower process compared to creating and removing data from the stack.

What is the difference between the UIApplicationDelegate and SceneDelegate?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Prior to iOS 13, the AppDelegate was the main entrypoint for your application. This is where you would typically start the configuration of your third-party dependencies and establish the starting conditions for your application.

However, as of iOS 13, some of the responsibilities of the AppDelegate have been transitioned to the SceneDelegate. This change is due to the new multi-window support feature introduced with iPadOS.

With multi-window support, we鈥檒l still have one application, but it can have multiple windows (e.g. imagine Google Chrome or Safari). So, we鈥檒l need a separate object whose sole purpose is to manage the window(s) of the application.

The AppDelegate will continue to be responsible forthe application lifecycle and initial setup, but the SceneDelegate will now be responsible forwhat is shown on the screen.

As part of this transition, the SceneDelegate will enable us to create multiple instances of our app鈥檚 UI all backed by the same AppDelegate. This new multi-window support also means that each of these instances will appear as separate views in iOS鈥檚 application switcher.

Moreover, each window is meant to work independently from one another, so now screens can independently move from the foreground to the background or vice-versa.

AppDelegate鈥檚 responsibilities are otherwise unchanged.It will still be responsible for setting up any data needed for the duration of the application, configuring your app鈥檚 scenes, registering for external services like push notifications, and managing the application鈥檚 lifecycle.

In a nutshell, the SceneDelegate manages the iOS app鈥檚UI lifecycle methods whereas the AppDelegate only handles the iOS app鈥檚 applicationlifecycle methods.

What is the difference between try, try!, and try??

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

All of these options are different ways of calling a function that can throw an error.

try! is the most dangerous option of the bunch andshould seldomly be used as it involves force unwrapping an Optional value.

It鈥檚 effectively saying that while the function in question could throw an error, this will never happen. So, we want to proceed as if the called function will always return a value:

func fetchData() {
// try! states that even though this function may return an error,
// we know it won't happen so we can force unwrap this optional
let unwrappedData =try! thisFunctionCanThrow()

// The application will crash if unwrappedData is in fact nil
}

func thisFunctionCanThrow() throws -> [String] {
// Imagine this function can throw an error
return []
}

However, try! is sometimes used if the error is so significant and unrecoverable that no valid user flow exists - similar to the use case of fatalError().

try? can be used to ignore any errors from the throwing function. If an error is thrown, the expression will resolve to nil. As a result, we鈥檒l need to unwrap the returned value in order to use it.

This variation is often used when the error thrown isn鈥檛 important enough to block or change the user鈥檚 experience or it鈥檚 permissible to have the function call fail silently:

func fetchData() {
// The result from `thisFunctionCanThrow` will either be nil
// or a [String] which we'll need to unwrap to access
if let unwrappedData = try? thisFunctionCanThrow() {
print("Successfully retrieved data: \(unwrappedData)")
}
}

func thisFunctionCanThrow() throws -> [String] {
// Imagine this function can throw an error
return []
}

try is the safest option and forces us to explicitly catch and handle any issues that occur:

func fetchData() {
// This is a safe approach and allows us to decide how we
// handle the error
do {
let data = try thisFunctionCanThrow()
print("Successfully retrieved data: \(data)")
} catch {
print("An error occurred:\(error)")
}
}

func thisFunctionCanThrow() throws -> [String] {
// Imagine this function can throw an error
return []
}

What is the purpose of the reuseIdentifier?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

One of the performance optimizations UITableViewsandUICollectionViews make is to only initialize enough cells to fill the user鈥檚 screen. Then, whenever the user scrolls, instead of instantiating a new cell, it can just replace the contents of an existing previously allocated cell the cell that is about to be scrolled off the screen. This approach is not only very performant, but also utilizes less memory.

Imagine a UITableView with multiple custom UITableViewCells.In order to perform the optimization mentioned above, the UITableView needsto be able to quickly find a cell that differs only in content, but shares the same layout.

This is exactly the problem that reuse identifiers solve.

UITableViews use reuse identifiers to understand what rows (if any) differ only in content and not in layout. These cells then become candidates for reuse.

That鈥檚 why if you have a UITableView with multiple UITableViewCell types, you鈥檒l need to register multiple cell reuse identifiers.

tableView.registerClass(MyCustomCell.self, forCellReuseIdentifier: "MyCustomCell")

What is the Responder Chain?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

iOS handles all user interaction - touch, press, shake, etc. - through something called the Responder Chain. This is essentially the hierarchy of all objects that have an opportunity to respond to user input.

If a particular object can鈥檛 handle the event, it passes it up to the next item in the chain. This creates a hierarchy of objects that are equipped to handle user interaction of all types.

At the top of this hierarchy, you have theUIApplicationDelegate.

If you鈥檝e ever placed your finger in aUITextFieldon iOS, you鈥檒l notice that the keyboard pops up immediately. From here, all subsequent user interaction events are sent to theUITextField to handle. This is because theUITextFieldis nowthe first responder - it鈥檚 the first object in the hierarchy that has a chance to respond to user interaction.

That鈥檚 why when you want to dismiss the keyboard, you have to write textField.resignFirstResponder() which is the UITextField鈥榮 way of saying that it鈥檚 giving up control and wants to revert back to the previous Responder Chain hierarchy.

What is type inference?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Type inference is the mechanism that enables Swift to infer the type of a variable without us having to specify it explicitly. This generally lends itself to writing cleaner and more concise code without compromising readability or type safety.

That鈥檚 why we can write this:

var welcomeMessage = "Hello"

Instead of having to write (a.k.a type annotations):

var welcomeMessage: String = "Hello"

The compiler is able to infer that welcomeMessage is a String based off of the default value we鈥檝e provided.

If we don鈥檛 specify a default value, then we鈥檒l need to use type annotation to provide the compiler with the relevant information:

var red, green, blue: Double