Skip to main content

What are Swift’s different assertion options?

· 4 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Assertions allow us to terminate the execution of our program if a condition is not met at runtime.

While this may seem like a drastic reaction, this feature makes the debugging process far easier. By terminating the execution of our app in this way, we give ourselves an opportunity to investigate why our assertion failed and to make whateverchanges are necessary.

Swift provides three different assertion types:

assert()

This is useful for basic debugging and allows you to assert that a condition is true before the execution of your program can continue. In the event that the condition is not satisfied, the system will terminate your application, but will allow it to remain in a debuggable state.

assert() is only evaluated in Debug mode and is disabledin all Release builds.

This enables you to easily sanity check your implementation during development while ensuring that your end user’s experience is unaffected.

assert(age > 0 && age < 150 , "Either an invalid age or a medical marvel.")
assert(user.isAuthenticated, "User should be authenticated at this stage.")

guard let storageKey = model.key as? String else {
// assertionFailure() allows us to trigger the failure case directly
// without having to evaluate a conditional expression
assertionFailure("Cannot save model without a storagekey")
return
}

It’s important to note, though, that your project’s optimization settings can change the behaviorof this function:

  • In Playground and -Onone builds (the default for Xcode’sDebug configuration), if the condition evaluates to false, the program will print the specified message and stop execution, but will remain in a debuggable state.
  • In -O builds (the default for Xcode’s Release configuration),the assert() is not evaluated.
  • In -Ounchecked builds, the assert() is not evaluated, but the optimizer may assume that it always evaluates to true which can easily mask serious programming errors.

precondition()

You can think of precondition() as assert() with support for Release builds. This assertion type will also stop your program’s execution when a condition is not met.

To clarify the difference between assert() and precondition() further, assert() is generally used for sanity checking your code during development whereas precondition() is used for guarding against situations that, if they happen, mean your program cannot reasonably proceed. For example, you might use precondition() to validate that the arguments passed into your function match the requirements specified by your documentation.

precondition(state != .invalid,"App in unknown and non-recoverable state.")
precondition(containers.count > 0 , "Empty containerstack.")
preconditionFailure("Cannot cast\(type(of: objCValue)) to \(Value.self)")
preconditionFailure("Plist file not found")

Similar to assert(), the particular compiler optimizationsused in your project can influence the function’s behavior:

  • In Playground and -Onone builds (the default for Xcode’sDebug configuration), if the precondition()evaluates to false, the program willprint a message and stop execution, but will remain in a debuggable state.
  • In -O builds (the default for Xcode’s Release configuration),if theprecondition() evaluates to false, the program’s execution is stopped.
  • In -Ounchecked builds, precondition() is not evaluated,but the optimizer may assume that it always evaluates to true which can easily mask serious programming errors.

fatalError()

This assertion type is used in situations where the application has encountered a significant enough error that there is no reasonable way to proceed. If you use fatalError() to handle such a scenario, the application will immediately terminate in both Debug and Release builds.

fatalError("Unable to load image asset named \(name).")
fatalError("init() has not been implemented")
fatalError("ViewController is not of the expected class \(T.self).")

The use of this assertion type can be a little polarizing. You’ll find that some developers are vehemently against using it while others see certain advantages and respect the deliberateness it offers. Regardless of which camp you find yourself in, you’ll want to use this sparingly and only when your application enters a truly unexpected and unrecoverable state.

Unlike the other options we’ve discussed so far, fatalError() will work regardless of any compilation or optimization settings you’ve enabled on your project.

By using these different types of assertion mechanisms, we can articulate the assumptions in our code and, as a result, write code that is extremely clear about the behavior we expect.

What are the differences between a class and a struct?

· 3 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources
FeaturesClassesStructs
TypeReferenceValue
Implement ProtocolsYesYes
Define PropertiesYesYes
Define MethodsYesYes
Define InitializersYesYes
Be ExtendedYesYes
Support InheritanceYesNo
TL/DR

The main takeaway here is that a class is a reference-type and a struct is a value-type.

When you pass reference-types around in your program (i.e. as a parameter), you are actually passing a direct reference to the memory location of that object. As a result, different parts of your program can easily share and modify your object since they’re all referencing the exact same place in memory.

When you pass a struct or enum to a function, you’re only passing along a copy of the data in them. So, even if you modify properties of the passed in value-type, the original one remains unchanged as you’re effectively just modifying a duplicate. This type of behavior is called pass-by-value semantics and is helpful in preventing situations where one part of the code inadvertently changes values or unknowingly affects the application’s state.

Both a class and a struct can implement protocols, define properties, methods, initializers, and be extended, but only a class can support inheritance and by extension polymorphism.

Since a struct requires less overhead to create and is safer to use due to it’s immutability and pass-by-value semantics, Apple’s recommendation is to start with a struct when you can and change to aclassonly when needed.

However, if the entity you’re working with contains a lot of data, then it’s probably useful for it to be a class so you can share a reference to it and only incur the memory cost once.

In Bullets

Structs

  • Value Type: Copied when assigned or passed around.
  • Immutability: Instances are immutable by default (unless marked as var).
  • Memory Management: Managed on the stack for performance.
  • Inheritance: Cannot inherit from other structs.
  • Initialization: Automatically gets a memberwise initializer.
  • Use Case: Ideal for small, lightweight data structures like points, sizes, or ranges.

Classes

  • Reference Type: Referenced when assigned or passed around; changes affect all references.
  • Mutability: Can change its state and properties if it’s a var reference.
  • Memory Management: Managed on the heap, with automatic reference counting (ARC).
  • Inheritance: Can inherit from other classes, supporting polymorphism.
  • Initialization: Requires explicit initializers and has default initializer if not provided.
  • Use Case: Suitable for more complex data structures, especially when you need inheritance or shared mutable state.

What are the differences between a delegate and a notification?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Delegates and notifications are different mechanisms for informing objects about events or actions taking place in other parts of your app. Though they differ fundamentally in how they communicate, both of these options can help reduce coupling between entities in your code.

Let's look at delegates first.

A delegate is like a phone call - not only can you communicate with the person on the other end, but they can also communicate with you. In a similar way, delegates are used to establish two-way communication between objects.

There's no need for the delegating object to know specifics about the object it's talking to; the receiving object simply needs to implement the required protocol.

Typically, delegates are used when you want the object receiving the events to influence the sending object. We know, for example, that a class that implements the UITableViewControllerDelegate will be notified whenevents occur in a UITableView (e.g. the selection of a cell). This class can then handle the event in whatever way it wishes (e.g. present a new view, issue a command toUITableView,etc.).

In contrast, a notification is more of a broadcast than a strict two-way communication. For example, whenever a user changes time zones or night mode is activated, iOS notifies all listening objects so they can adjust accordingly.

Notifications help reduce coupling between the sending and receiving objects as the sending object simply publishes a notification, but is unaware of who or if anyone is listening. Unlike with delegates, the objects receiving the notification cannot communicate with or interact with the sender as notifications are only a one-way communication.

In summary, the main difference between a delegate and a notification is that the former is used for one-to-one messaging while the latter is used for one-to-many messaging.

The appropriate choice will depend on your use case.

What are the differences between a library and a framework?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The difference between a library and a framework is that you’ll typically call a library, but a framework will call you.

Library

A library is a collection of functions that each perform some work and then return control to the caller. Libraries can only contain executable code; no other assets or media.

Libraries usually contain highly tested and sophisticated code meant to address some particular problem. So, it’ll often make sense to reuse code written by other developers rather than implementing it from scratch yourself. For example, many libraries exist for complicated topics like physics, audio processing, image manipulation, etc. Clearly, it would be unrealistic to implement all of that yourself.

You can think of a library like a trip to IKEA. You already have a home filled with furniture, but there's a few rooms that you need help furnishing. Instead of making it yourself, you can go to IKEA (the library) and pick and choose the relevant pieces you need. Throughout this experience, you - the programmer - are in control.

An example on iOS would be the open-source Charts library which lets you easily create bar, line, and pie graphs. It provides all of the functionality, but it's up to the programmer to decide exactly when, where, and how it should be used.

Framework

A framework is similar to a library, but instead leaves openings for you to influence its behavior and execution. This is accomplished most commonly through subclassing, dependency injection, and delegation. Unlike libraries, frameworks can contain other media types (images, audio, etc.) - not just code.

The main distinction between a library and a framework is that there is an inversion of control. When you use a framework, you'll have a few opportunities to plug in your code, but the framework is in charge of when - and if at all - your custom code is executed. The framework has effectively inverted the control of the program; it's telling the developer what it needs and decides when to execute it.

Consider a framework like Vapor or a location tracking framework. They both provide complicated functionality, but leave little openings for you to introduce your own custom logic.

What are the differences between a .xib and a .storyboard file?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Both a .xib and .storyboard are stored as XML filesand are both converted to binary files - .nibs- at compile time.

A .xib file usually specifies one UIViewController or standalone view whereas a .storyboard specifies a set of UIViewControllers and the navigation behavior between them.

What are the differences between DispatchQueue, Operation, and Threads?

· 4 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The differences between DispatchQueues, Operations, and Threads can be difficult to distinguish, so let's take a moment to review their similarities and differences.

Threads
Simply speaking, threads allow a process to split itself up into multiple simultaneously running tasks.

In iOS, you have what Apple calls a "pool of threads." This is a collection of threads, other than the Main Thread, that the system can assign work to. The operating system will delegate work to one or more of these threads depending on the number of other competing tasks, the load on the processor, and a variety of other environmental factors.

The threads in iOS are so heavily optimized that if a developer were to interact with or dispatch work to them directly, they would almost certainly degrade the thread's performance or introduce issues related to reusability or locking. As a result, Apple provides abstractions over these threads like DispatchQueue and OperationQueue for developers to use rather than tasking developers with implementing their own custom thread management logic.

When we use these services, we lose visibility into exactly what threads are assigned to execute our work, but this deliberate abstraction simplifies our work tremendously. It allows us to put our trust in iOS to look at all of the relevant factors and dispatch the work intelligently on our behalf.

The main takeaway here is that you should always delegate your work to a DispatchQueue or an OperationQueue instead of trying to interact withthe threads directly.

DispatchQueue / Grand Central Dispatch
A DispatchQueue, also known as Grand Central Dispatch(GCD), allows you to execute tasks either serially or concurrently on your app’s main and / or background threads. As a developer, you simply assign work to GCD and it will delegate it to one or more threads from the system's thread pool, but makes no promises or guarantees about which threads will be used.

DispatchQueues support both serial and concurrent execution. Serial queues synchronize access to a resource, whereas concurrent queues execute one or more tasks concurrently. In the case of a concurrent queue, tasks are completed according to their complexity and not their order in the queue.

In the simplest terms,DispatchQueueprovides a layerof convenience over the system's thread pool. The developer simply needs to define the task - a DispatchWorkItem - and ship it over to a DispatchQueue which will handle all of the heavylifting and thread management.

OperationQueue
You can think of OperationQueue as a better, more powerful version of DispatchQueue.

Grand Central Dispatch (GCD) is a low-level C API that interacts directly with the Unix level of the system whereasOperationQueueis built on topof GCD and provides an Objective-C interface instead.

As we will discuss in a moment, OperationQueue comes with many advantages, but it also introduces some additional overhead. This is due to the fact that OperationQueue instances need to be allocated and deallocated whenever they are used. Although this process is heavily optimized, this additional overhead makes it slower than GCD.

So, what's the advantage of OperationQueue if it's slower than GCD?OperationQueue allows you to manage scheduled operations with much greater control than GCD provides. OperationQueues make it easy to specify dependencies between individual operations, to cancel and suspend enqueued operations, or to re-use operations, none of which are possible with GCD. Apple's official recommendation is to always use the highest level of abstraction available, but if you only need to dispatch a block of code to execute in the background and you don't necessarily needOperationQueues’additional features,I thinkDispatchQueueis a reasonable choice. If you need more functionality, you can always upgrade to anOperationQueue`.

What are the differences between Keychain and UserDefaults?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

UserDefaults and Keychain are both useful in storing small key-value pairs on the user's device, but their security capabilities differ greatly.

Keychain is the only native option for storing data on an iOS device in an encrypted manner.

It's typically meant for storing small amounts of data like an access token, credentials, or other sensitive information. However, while it is still the most secure offering on iOS, it’s important to know that Keychain data can be accessed on jailbroken devices.

Since Keychain is implemented as an SQLite database stored on the file system, it is slower than UserDefaults. Lastly, values stored in the Keychain will persist across application deletions and re-installs unless explicitly deleted.

UserDefaults also allows you to store key-value pairs across different invocations of your app, but it is not secure.

Values stored in UserDefaults are eventually written to a .plist file which are entirely human-readable.UserDefaults are usually used to store basic key-value pairs and user preferences. If you don't need the Keychain's security features, UserDefaults are the more convenient choice. Finally, unlike in the Keychain,items saved in UserDefaults will not persist across application deletions and re-installs.

What are the differences between Swift’s access control modifiers?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Access controls allow us to restrict parts of our code from other source code files and modules.

This enables us to abstract away certain implementation details and instead share a preferred interface through which our code should be accessed and used.

Swift provides five different access level modifiers that can be used on classes, structs, enums, properties, methods, and initializers.

Going from the least to the most restrictive:

open: classes and methods marked asopencan be subclassedand overridden outside of their defining module.

public: provides read / write access to an entityto all other parts of the code regardless of the defining module.

internal: is the default access level in Swift. Propertiesdeclared asinternalare accessible in the scope of their defining module, but are inaccessible by parts of the code belonging to other modules.

fileprivate: entities marked asfileprivateare visibleand accessible from Swift code declared in the same source file.

private: is the most restrictive access level andensures that a property is only accessible inside their defining type. This means that properties and methods declared as private are only accessible within the class or structure in which they are defined.

What are the differences between the static and class keywords?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Both static and class keywords enable us to attach methods directly to a type rather than to an instance of the type. However, they differ in their ability to support inheritance.

When we use the static keyword on a function declaration,that function can no longer be overridden by a subclass. However, if we were to use the class keyword instead, overriding this function in a subclass would still be a possibility.

As we’ve previously discussed, thefinalkeyword attachedto aclassor function also prevents it from being subclassed or overridden. Therefore, it may be easier to remember that staticis equivalent tofinalclass.

class Dog {
class func bark() -> String {
return "Woof"
}

static func sit() -> Void {}
}

class ScoobyDoo: Dog {
override class func bark() -> String {
"Zoinks!"
}

// ERROR: Cannot override static method
override static func sit() -> Void {}
}

As a final point, since functions declared with the class keyword can be overridden, this means that they must be dynamically dispatched. In contrast, and unsurprisingly, when you declare a function with thestatickeyword, that functioncall is now statically dispatched.

What are the different execution states your application can be in?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

There are 5 distinct states an iOS app can find itself in:

Not running This is when the app has not been launched or was previously running but has now been terminated by the system.

Inactive

This occurs when the app is actively running in the foreground, but is not receiving events. This state tends to be brief as the app transitions to some other state.

Active

This is the normal mode for most apps; the app is running in the foreground and receiving and responding to events.

Background

This state describes an app that is running in the background, but is still executing code. Most applications tend to enter this state on the way to being suspended. Furthermore, apps that require extra execution time may remain in this stage longer.

Apps that are launched directly into the background will enter this state instead of the inactive state.

Suspended

This state describes an application that is running in the background but is not executing code.

Apps that are suspended will remain in memory, but will be terminated by the system if a low-memory condition occurs.