Top Swift Interview Questions (2025)

author image Hirely
at 09 Jan, 2025

Question: What is the purpose of defer in Swift?

Answer:

In Swift, defer is a powerful control flow statement that allows you to execute a block of code just before the current scope exits, no matter how the scope is exited (whether through normal return, early exit due to an error, or other control flow mechanisms). The purpose of defer is to ensure cleanup code is always executed, even in the face of errors or early exits from functions.

Key Points About defer:

  1. Deferred Execution: The code inside the defer block is executed when the scope (such as a function or loop) exits, right before the function returns, but after all other code in the function has run.

  2. Cleanup Code: It’s commonly used for performing cleanup tasks, such as releasing resources, closing files, or unlocking resources that were locked earlier in the function.

  3. Multiple defer Blocks: If there are multiple defer blocks in a function, they are executed in the reverse order of their appearance (LIFO: Last In, First Out).

  4. Works Even After Early Returns or Errors: Code inside a defer block will still be executed if the function exits early due to a return statement or if an error occurs.

Syntax:

defer {
    // Code to be executed just before the scope exits
}

Example 1: Using defer for Cleanup

A common use case for defer is ensuring that resources like files or network connections are cleaned up after they are used, regardless of whether the function exits normally or through an error.

func openFile() {
    let file = openSomeFile()  // Suppose this function opens a file

    defer {
        closeFile(file)  // Ensure the file is closed when the function exits
    }

    // Do some work with the file
    // No matter what happens here, the file will always be closed
}

In this example, closeFile(file) is guaranteed to be called when openFile() exits, even if there is an error or an early return in the function.

Example 2: Multiple defer Blocks

When you have multiple defer blocks in the same function, they are executed in reverse order.

func performTask() {
    print("Task started")
    
    defer {
        print("Defer block 1")
    }
    
    defer {
        print("Defer block 2")
    }
    
    print("Task in progress")
}

performTask()

Output:

Task started
Task in progress
Defer block 2
Defer block 1

Here, the defer blocks are executed in reverse order (LIFO), so “Defer block 2” is printed before “Defer block 1” even though defer block 2 comes first in the code.

Example 3: Early Return with defer

defer can also be used in functions with early returns to ensure that cleanup code is always run, regardless of when the return happens.

func processFile(file: String) {
    if file.isEmpty {
        print("File is empty, returning early")
        return  // Early return
    }
    
    defer {
        print("Closing the file.")
    }
    
    // File processing code here...
    print("Processing the file: \(file)")
}

processFile(file: "")
// Output: File is empty, returning early

processFile(file: "document.txt")
// Output: Processing the file: document.txt
// Closing the file.

In this example, even if the function returns early because the file is empty, the deferred code will still be executed before the function exits. So, the defer ensures that the file is always closed, even when returning early.

Example 4: Using defer for Error Handling

defer is also useful in functions that throw errors, ensuring that resource cleanup or other finalization steps are executed even if an error is thrown.

enum FileError: Error {
    case fileNotFound
    case fileCorrupted
}

func readFile() throws {
    let file = openSomeFile()
    
    defer {
        closeFile(file)  // Ensure file is closed, even if an error occurs
    }
    
    // Simulate a potential error
    let isFileCorrupted = true
    if isFileCorrupted {
        throw FileError.fileCorrupted
    }
    
    // Otherwise, process the file...
}

do {
    try readFile()
} catch {
    print("Error reading file: \(error)")
}

In this case, if an error is thrown (e.g., fileCorrupted), the defer block will still be executed to close the file, ensuring cleanup happens even in the case of failure.

Key Benefits of defer:

  • Guaranteed Execution: The code inside defer runs no matter how the function exits, ensuring necessary cleanup happens (e.g., closing files, releasing resources).
  • Clarity and Readability: Instead of scattering cleanup code throughout the function, defer allows you to keep it together in one place, making your code more maintainable.
  • Avoids Duplication: By using defer, you avoid duplicating cleanup code in multiple return paths.

Summary:

  • defer ensures that a block of code is executed just before a scope exits, regardless of how the scope is exited (normal return, early exit, or error).
  • It is commonly used for resource cleanup, like closing files or releasing memory.
  • Multiple defer blocks are executed in reverse order, and they are especially useful for handling early returns and exceptions in a clean, predictable way.

In conclusion, defer is an essential tool in Swift for managing resources and ensuring that cleanup code is always executed, even in complex control flow situations.

Question: What are protocols in Swift? How are they different from interfaces in other languages?

Answer:

In Swift, protocols are a powerful feature that define a blueprint for methods, properties, and other requirements that suit a particular task or functionality. They specify what properties, methods, and other requirements an adopting class, struct, or enum should implement, without providing the implementation itself. A protocol can be thought of as a contract or an interface for the behavior that a type should conform to.

Key Characteristics of Swift Protocols:

  1. Defining Requirements:

    • Protocols can define required methods, properties, and initializers that conforming types must implement.
    • They can also define optional methods (but only for class types) using @objc and optional keywords.
  2. No Implementation:

    • Unlike classes or structs, protocols do not provide implementations. They just specify the signature of methods or properties that conforming types must implement.
  3. Conformance:

    • A type (class, struct, or enum) can conform to a protocol by implementing all the required methods and properties defined in the protocol.
    • Conformance is declared using the colon syntax (:).
  4. Adoption by Classes, Structs, and Enums:

    • Protocols can be adopted by classes, structs, and enums. They are not limited to just classes like in some other languages (e.g., Java).
  5. Protocol Inheritance:

    • Protocols can inherit from other protocols, enabling more flexibility in defining protocol-based hierarchies.
  6. Multiple Protocol Conformance:

    • A type can conform to multiple protocols at once. Swift allows for multiple protocol conformance, unlike some other languages that restrict a type to implementing only one interface.

Syntax:

protocol SomeProtocol {
    var property: String { get set }
    func someMethod()
}

Example of Protocol Definition and Conformance:

protocol Drivable {
    var speed: Int { get set }
    func drive()
}

struct Car: Drivable {
    var speed: Int
    
    func drive() {
        print("The car is driving at \(speed) mph.")
    }
}

let myCar = Car(speed: 60)
myCar.drive()  // Output: The car is driving at 60 mph.

Difference Between Protocols in Swift and Interfaces in Other Languages:

1. Implementation of Methods (No Implementation in Protocols):

  • Swift Protocol: Protocols do not provide method implementations, only method signatures and requirements. The conforming type must implement these methods.
  • Java Interface: Java interfaces also only specify method signatures, but they do not provide any implementation either (until Java 8, which introduced default methods). However, interfaces in Java (prior to Java 8) are similar to Swift protocols because they require classes to provide the method implementations.
protocol Flyable {
    func fly()  // Just the method signature, no implementation
}
interface Flyable {
    void fly();  // Similar to Swift protocol, no implementation
}

2. Protocol Conformance and Multiple Conformance:

  • Swift Protocols: A type (struct, class, or enum) can conform to multiple protocols simultaneously. You can define a type that adopts multiple protocols, and it must implement all the required properties and methods for each protocol.
protocol Drivable {
    func drive()
}

protocol Flyable {
    func fly()
}

struct FlyingCar: Drivable, Flyable {
    func drive() {
        print("Driving the car!")
    }
    
    func fly() {
        print("Flying the car!")
    }
}
  • Java Interfaces: Java allows classes to implement multiple interfaces, but it does not support the concept of extending interfaces (except for defining a new interface that combines others).
interface Drivable {
    void drive();
}

interface Flyable {
    void fly();
}

class FlyingCar implements Drivable, Flyable {
    public void drive() {
        System.out.println("Driving the car!");
    }
    
    public void fly() {
        System.out.println("Flying the car!");
    }
}

3. Protocol Inheritance:

  • Swift Protocols: A protocol in Swift can inherit from one or more other protocols. This allows you to create a hierarchy of protocols that builds on top of each other.
protocol Vehicle {
    func start()
}

protocol Drivable: Vehicle {
    func drive()
}

struct Car: Drivable {
    func start() {
        print("Car started")
    }
    
    func drive() {
        print("Car is driving")
    }
}
  • Java Interfaces: In Java, an interface can extend one or more interfaces, and the implementing class must provide implementations for all methods in the inherited interfaces.
interface Vehicle {
    void start();
}

interface Drivable extends Vehicle {
    void drive();
}

class Car implements Drivable {
    public void start() {
        System.out.println("Car started");
    }
    
    public void drive() {
        System.out.println("Car is driving");
    }
}

4. Optional Methods:

  • Swift Protocols: You can mark methods as optional in a protocol, but only when the protocol is marked with @objc (i.e., it can only be adopted by class types that are compatible with Objective-C runtime).
@objc protocol SomeProtocol {
    @objc optional func optionalMethod()
}
  • Java Interfaces: Java interfaces do not allow optional methods. Every method in an interface is abstract (unless it is a default method introduced in Java 8).

5. Protocol Composition (Multiple Protocols):

  • Swift Protocols: You can create a protocol composition, which allows you to require a type to conform to multiple protocols at once, but only for certain contexts (e.g., function parameters or return types).
func doSomething(with object: Drivable & Flyable) {
    object.drive()
    object.fly()
}
  • Java Interfaces: Java supports multiple interfaces, but you can’t “compose” them in the same way as Swift’s protocol composition (though a class can implement multiple interfaces).

6. Protocol-Oriented Programming:

  • Swift Protocols: Swift emphasizes protocol-oriented programming, which focuses on defining behavior through protocols rather than relying on inheritance from base classes. This is a key differentiator from many other languages, including Java.
  • Protocols can include default method implementations, which are somewhat like extensions in classes, allowing you to extend behavior in protocols directly.
protocol Describable {
    var description: String { get }
}

extension Describable {
    var description: String {
        return "This is a describable object"
    }
}

Summary of Differences:

FeatureSwift ProtocolsJava Interfaces
ImplementationNo implementation, only method and property requirementsNo implementation (except Java 8 default methods)
ConformanceCan conform to multiple protocolsCan implement multiple interfaces
InheritanceProtocols can inherit from other protocolsInterfaces can extend other interfaces
Optional MethodsOptional methods available with @objc (only for class types)No optional methods
CompositionProtocol composition using & for multiple protocolsInterfaces can implement multiple interfaces, but not compose them
Protocol-Oriented ProgrammingEmphasizes protocol-oriented programming (POP)No direct counterpart, focused on OOP

In conclusion, Swift protocols are similar to Java interfaces, but they are more powerful in some ways, particularly with support for protocol composition, protocol inheritance, and default method implementations. Additionally, Swift’s protocol-oriented programming philosophy encourages behavior abstraction via protocols, offering more flexibility and avoiding some of the complexities of class-based inheritance.

Read More

If you can’t get enough from this article, Aihirely has plenty more related information, such as Swift interview questions, Swift interview experiences, and details about various Swift job positions. Click here to check it out.

Related Posts

Trace Job opportunities

Hirely, your exclusive interview companion, empowers your competence and facilitates your interviews.

Get Started Now