Top iOS Interview Questions on Enumeration in Swift
Oct 25, 2023Q1. What is an enumeration?
An enumeration is a common type that defines a group of related values. Enums are used to represent a set of distinct cases, and each case can have an associated value or a raw value.
Here's how to declare an enumeration:
enum NetworkError {
case noInternet
case serverNotFound
case unauthorized
case internalServerError
}
// How to create a variable?
let error: NetworkError = .serverNotFound
print(error) // Output: serverNotFound
In this case, error
is of type NetworkError
, and it can only hold one of the four values specified in the NetworkError
enumeration.
Q2. How do you declare a raw value in enumeration?
In enumeration , you can declare a raw value by assigning each case a pre-defined raw value of a compatible data type. Raw values are useful when you want to associate simple values, such as integers or strings, with each case.
Here's an example of a raw value named NetworkError
with associated raw values:
enum NetworkError: Int {
case noInternet = 1001
case serverNotFound = 404
case unauthorized = 401
case internalServerError = 500
}
let error: NetworkError = .serverNotFound
print(error.rawValue) // Output: 404
In this example, we've declared the NetworkError
enum with raw values of type Int. Each case is associated with an integer error code.
Q3. Explain the difference between a raw value and an associated value.
Enums can have associated values or raw values, and these two concepts serve different purposes.
Raw Value: A raw value is a predefined value that you assign to each case of an enumeration when you define it. Raw values are of the same type, and they must be unique within the enumeration.
Here's an example with raw values:
enum NetworkError: String {
case noInternet = "No internet connection"
case serverNotFound = "Server not found"
case unauthorized = "Unauthorized access"
case internalServerError = "Internal server error"
}
let error = NetworkError.serverNotFound
print(error.rawValue) // Output: "Server not found"
Raw values are typically used for situations where you have a finite set of possible values that are known in advance and don't change during runtime.
Associated Value: It allow you to associate additional data with each case of an enumeration. Associated values are used when the data associated with each case can vary, and you need to store different types of information for different cases.
Here's an example with associated values:
enum NetworkError {
case noInternet
case serverNotFound(url: String)
case unauthorized(username: String)
case internalServerError(statusCode: Int)
}
let serverError = NetworkError.serverNotFound(url: "base_url")
let unauthorizedError = NetworkError.unauthorized(username: "user_name")
The key difference between raw values and associated values is that raw values are fixed, predefined values associated with each case and are of the same type, while associated values allow you to associate variable data of different types with each case.
Q4. How do you create a custom initializer for an enumeration with associated values?
To create a custom initializer for an enumeration with associated values, you can define an initializer method inside the enum itself.
Here's an example of enum with a custom initializer:
enum NetworkError: Error {
case noInternet
case serverNotFound
case unauthorized
case internalServerError
case customError(message: String)
init(errorCode: Int) {
switch errorCode {
case 401:
self = .unauthorized
case 404:
self = .serverNotFound
case 500:
self = .internalServerError
default:
self = .customError(message: "Unknown error with code \(errorCode)")
}
}
}
let error = NetworkError(errorCode: 404) // Creates a .serverNotFound error
let customError = NetworkError(errorCode: 403) // Creates a .customError with a custom message
In the above example, we've defined a custom initializer init(errorCode:)
that takes an integer errorCode
as a parameter. This initializer allows you to create a NetworkError
instance based on an error code.
Q5. What are the enum methods?. When and how would you use them?
In enum, you can create associated methods, also known as instance methods, for each case of an enumeration. These methods are specific to each case and allow you to encapsulate behavior associated with that particular case.
This is a powerful way to attach behavior to enum cases and make your code more organized and readable.
enum NetworkError {
case noInternet
case serverNotFound
case unauthorized
case internalServerError
func errorMessage() -> String {
switch self {
case .noInternet:
return "No internet connection."
case .serverNotFound:
return "Server not found."
case .unauthorized:
return "Unauthorized access."
case .internalServerError:
return "Internal server error."
}
}
}
let error = NetworkError.noInternet
let errorMessage = error.errorMessage()
print(errorMessage) // Output: "No internet connection."
In this example, we've added an errorMessage()
method to the NetworkError
enum. Each case of the enum has its own implementation of this method, which returns an appropriate error message for that case.
Q6. How can you iterate through all cases of an enumeration?
To iterate through all cases of an enumeration in Swift, you can use the CaseIterable
protocol, which automatically provides a allCases
property that contains an array of all the enumeration cases.
enum NetworkError: CaseIterable {
case noInternet
case serverNotFound
case unauthorized
case internalServerError
}
// Accessing all cases
let allNetworkErrors = NetworkError.allCases
// Iterating through all cases
for error in allNetworkErrors {
switch error {
case .noInternet:
print("No Internet error")
case .serverNotFound:
print("Server not found error")
case .unauthorized:
print("Unauthorized error")
case .internalServerError:
print("Internal server error")
}
}
/*
Output:
No Internet error
Server not found error
Unauthorized error
Internal server error
*/
In this code, NetworkError
conforms to the CaseIterable
protocol, which means it will automatically generate the allCases
property containing an array of all the cases defined in the enumeration.
The CaseIterable protocol allows an enumeration to automatically generate a collection of all its cases. This protocol was introduced in Swift 4.2 and later versions.
Q7. Why we should use enums where possible?
Enums offers several advantages in terms of code clarity, maintainability, and safety.
enum NetworkError {
case noInternet
case serverNotFound
case unauthorized
case internalServerError
}
Code Clarity: Enums provide a way to create a clear and concise representation of a finite set of related values. In the NetworkError
enum, it's immediately evident that the possible error scenarios related to network communication are noInternet
, serverNotFound
, unauthorized
, and internalServerError
.
Type Safety: Enums are strongly typed, which means that the compiler ensures that you handle all possible cases. This prevents runtime errors related to unexpected values. For instance, if you try to handle a NetworkError
without covering all cases, the compiler will issue an error, prompting you to handle the new case appropriately.
Testing and Debugging: Enums simplify testing and debugging because they make it easier to simulate different error scenarios. You can create mock objects with specific enum cases to ensure your error-handling code behaves as expected under various conditions.
Q8. Explain the concept of recursive enumeration.
A recursive enumeration is an enumeration that can have associated values of its own type, allowing it to represent hierarchical or nested data structures. This can be particularly useful when you have a data structure that may contain instances of itself. It allows you to create more complex and flexible data models.
enum NetworkError {
case noInternet
case serverNotFound
case unauthorized
case internalServerError
case customError(String)
indirect case chainedError(NetworkError, NetworkError)
}
let firstError = NetworkError.customError("Authentication failed")
let secondError = NetworkError.noInternet
let chainedError = NetworkError.chainedError(firstError, secondError)
With this recursive enumeration, you can handle more complex error scenarios in your networking code. For instance, you could create a tree-like structure of errors where each chainedError
case contains multiple sub-errors.
Recursive enumerations can be especially handy when you need to represent hierarchical or nested data structures, and they make your code more expressive and flexible.
Q9. What is indirect
keyword and how it is related to enumeration?
The indirect
keyword is used to indicate that an enumeration or an associated value of an enumeration should be treated as having an "indirect" relationship. This is particularly important when you're dealing with recursive data structures, such as a recursive enumeration.
In Swift, enums are value types, which means that they are typically allocated on the stack, and their size must be known at compile time. However, when you have a recursive data structure, the size of an enum can depend on how many times it refers to itself. This creates a problem because it would lead to infinite recursion in determining the size of the enum, causing a compile-time error.
By using the indirect keyword, you're essentially telling the Swift compiler to store the associated value of the enumeration case on the heap rather than the stack. This dynamic allocation on the heap allows for recursive structures because the size of the enum is no longer fixed at compile time.
Here's an example using a simple recursive list data structure:
indirect enum LinkedList<T> {
case empty
case node(T, LinkedList<T>)
}
In this example, we have a LinkedList
enum that can either be empty or a node containing a value of type T
and another LinkedList
(which may also be a node or empty). By marking the enum as indirect, we can create linked lists of arbitrary length without worrying about stack overflow errors.
We hope that these interview questions will help you in your iOS interview preparation and also strengthen your basics on Enumeration in Swift. We have launched our new e-book "Cracking the iOS Interview" with Top 100 iOS Interview Questions & Answers. Our book has helped more than 372 iOS developers in successfully cracking their iOS Interviews.