Intermediate iOS Interview Questions

Introduction

As you advance in your iOS development journey, understanding intermediate-level concepts becomes crucial for writing efficient, scalable, and maintainable code. Whether you’re preparing for a technical interview or looking to deepen your Swift expertise, tackling more complex topics like escaping closures, protocol-oriented programming, method swizzling, and localization will give you an edge.

In this post, we’ve compiled Part 1 intermediate iOS interview questions that cover essential topics every developer should know. These questions will not only test your understanding but also help reinforce best practices in Swift and iOS development. Let’s dive in! 🚀

1. What is lazy property?

A lazy property in Swift is a property whose initial value is not calculated until the first time it is accessed. This can be useful when the property’s initial value is resource-intensive or depends on other properties or values that may not be available at the time of initialization.

You declare a lazy property by adding the lazy keyword before the var keyword:

class DataLoader {
    lazy var data: String = {
        print("Data loaded!")
        return "Some heavy data"
    }()
}

let loader = DataLoader()
print("Instance created.")
print(loader.data) // "Data loaded!" is printed here, only when data is accessed

Key Characteristics of Lazy Properties

  1. Delayed Initialization: The property is initialized only once, upon first access. This is useful for optimizing performance when the initialization is expensive.
  2. Variable Only: Lazy properties can only be declared as var, not let, because they might not be initialized immediately.
  3. Class and Struct Scope: Lazy properties are typically used in classes and structs, and they can capture self since they initialize after the instance has been created.

Use Cases

  • When a property depends on values that may not be known until after initialization.
  • When creating the property’s value requires a significant amount of memory or processing.
  • When it’s necessary to initialize a property with dependencies on self, such as certain types of closures.

2. How do you make a Class-Only protocol?

In Swift, you can restrict a protocol to be adopted only by classes (reference types) by adding the AnyObject constraint to the protocol declaration. This is useful when the protocol includes requirements that are only relevant to classes, such as reference semantics, or if you need to restrict conformance to avoid value-type behaviors.

Here’s how you define a class-only protocol:

protocol MyClassOnlyProtocol: AnyObject {
    func doSomething()
}

In this example, MyClassOnlyProtocol can now only be adopted by classes. Attempting to make a struct or enum conform to it will result in a compile-time error.

Example Usage

class MyClass: MyClassOnlyProtocol {
    func doSomething() {
        print("Doing something")
    }
}

// This would cause an error because structs cannot adopt class-only protocols
// struct MyStruct: MyClassOnlyProtocol { ... }

Why Use a Class-Only Protocol?

  1. Reference Semantics: If the protocol requires reference-based behavior (e.g., for sharing state), constraining it to classes prevents value-type conformances like structs or enums.
  2. Memory Management: Class-only protocols work with weak references, which can avoid retain cycles in cases like delegate patterns.

3. What is the difference between escaping and non-escaping closure?

In Swift, closures are by default non-escaping, meaning they are expected to complete their execution within the function they are passed to. However, if a closure needs to persist beyond the function call (for example, in asynchronous operations or callbacks), it is marked as escaping.

Key Differences Between Escaping and Non-Escaping Closures

  1. Lifetime:
    • Non-Escaping: The closure is guaranteed to complete before the function it was passed to returns. This allows Swift to manage memory more efficiently.
    • Escaping: The closure may outlive the function’s execution scope and be stored to be executed later (e.g., for callbacks or completion handlers).
  2. Syntax:
    • Non-Escaping (default): No special annotation is required.
    • Escaping: Use @escaping before the closure type in the function’s parameter.
  3. Memory Management:
    • Non-Escaping: Swift can optimize memory usage because it knows the closure’s lifetime is limited to the function call.
    • Escaping: When closures escape, they can potentially create strong reference cycles if they capture self strongly, so [weak self] is commonly used to avoid memory leaks.

Example

// Non-Escaping Closure
func performTask(operation: () -> Void) {
    print("Starting task")
    operation()  // Executed and completed within the function
    print("Task completed")
}

// Escaping Closure
func performAsyncTask(operation: @escaping () -> Void) {
    print("Starting async task")
    DispatchQueue.global().async {
        operation()  // Stored and executed later
        print("Async task completed")
    }
}

Use Cases

  • Non-Escaping: Typically used in synchronous operations or where the closure needs to perform work immediately within the function’s scope.
  • Escaping: Required for asynchronous operations, such as networking calls or closures that need to be saved and executed later (e.g., callbacks or completion handlers).

4. What is a Capture list?

A capture list in Swift is a way to define how values, particularly reference types like classes, are captured by a closure. Capture lists help prevent strong reference cycles (retain cycles) that can lead to memory leaks by specifying the capture behavior (e.g., capturing self weakly or unowned). This is particularly useful when closures capture properties or methods of their enclosing scope, such as in async calls or callbacks.

Syntax of Capture List

The capture list is placed at the beginning of a closure, inside square brackets [ ], followed by the keyword in.

{ [captureList] in 
    // Closure code here
}

Each item in the capture list includes the capture type (like weak or unowned) and the variable to capture.

Example

Here’s an example of a capture list preventing a strong reference cycle by capturing self weakly:

class MyClass {
    var value = "Hello"

    func setupClosure() {
        // Using [weak self] to prevent retain cycle
        let closure = { [weak self] in
            print(self?.value ?? "No value")
        }
        
        // Imagine we pass the closure to an async operation
        closure()
    }
}

In this example:

  • [weak self] captures self as a weak reference, so if MyClass is deallocated, self becomes nil rather than holding onto the instance.
  • Using [unowned self] is an alternative if you’re confident self will not be nil at the time of closure execution, but it can cause runtime crashes if self is nil.

Capture List Syntax Summary

  1. Weak Capture ([weak self]): Allows self to be deallocated, making self an optional within the closure.
  2. Unowned Capture ([unowned self]): Assumes self will remain in memory as long as the closure exists, making self a non-optional within the closure. Use with caution, as accessing a deallocated self will crash the app.

When to Use a Capture List

  • To avoid retain cycles in closures that capture reference types (e.g., instances of a class).
  • In asynchronous callbacks or any scenario where the closure might outlive the function or object’s scope.

Using capture lists properly ensures effective memory management and prevents potential memory leaks in your code.

5. What is the difference between a protocol and a delegate?

FeatureProtocolDelegate
DefinitionA blueprint specifying methods, properties, or other requirements a type must implement.A design pattern where one object communicates with another by notifying it of events, often implemented via a protocol.
PurposeDefines shared behavior or requirements across different types (e.g., structs, classes, enums).Facilitates communication between two specific objects, often used for event handling or callbacks.
UsageGeneral-purpose; can be used for polymorphism and to define a “contract” of functionality.Specifically used for delegation, commonly found in UIKit and Cocoa frameworks (e.g., UITableViewDelegate).
ImplementationCan be conformed to by any type: class, struct, or enum.Typically used with class-only protocols (AnyObject constraint) to allow weak references and prevent retain cycles.
Instance ReferenceProtocols do not maintain a reference to an instance.Delegates are usually held as a weak reference to avoid retain cycles.
Example UsageDefining a set of behaviors that multiple types can adopt, like Equatable or Codable.Used for callbacks or event handling, like UITableViewDelegate notifying its delegate about selection or scrolling events.
When to UseWhen different types need to share a set of required properties/methods but with different implementations.When one object needs to notify another of events, in a loosely-coupled manner, without creating a direct dependency.
Real-World ExampleCustomStringConvertible protocol, which types conform to for custom string representations.UITableViewDelegate, which a view controller adopts to handle user interactions with a table view.

In summary:

  • Protocols define behaviour and properties for multiple types.
  • Delegates use protocols to handle specific, event-based interactions between two objects in a loosely coupled way.

6. What is a failable and non-failable initializer?

In Swift, failable and nonfailable initializers are used to create class, struct, or enum instances. The difference lies in their ability to handle situations where initialization may or may not succeed.

  1. Nonfailable Initializer: A nonfailable initializer always creates a fully initialized instance of a type. It assumes that the initialization process will succeed and does not have the ability to represent a failure. Nonfailable initializers are denoted by a regular initializer syntax without the ? or ! modifier.

Example of a nonfailable initializer:

struct Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

In this example, the Point struct has a nonfailable initializer init(x: Int, y: Int) that takes x and y coordinates as parameters. It assumes that the initialization will always succeed, and a valid Point instance will be created.

  1. Failable Initializer: A failable initializer, on the other hand, can represent a situation where initialization may fail. It returns an optional instance (Type?) and can be used to handle invalid or inconsistent input during initialization. Failable initializers are denoted by appending a ? after the init keyword.

Example of a failable initializer:

struct Person {
    let name: String
    
    init?(name: String) {
        guard !name.isEmpty else {
            return nil
        }
        self.name = name
    }
}

In this example, the Person struct has a failable initializer init?(name: String) that takes a name parameter. It checks if the name is not empty, and if it is empty, the initializer fails by returning nil. This allows handling cases where an empty name would not be valid.

Failable initializers are useful when there are conditions that must be met for a successful initialization. By returning an optional, they provide a way to indicate when initialization fails.

To summarize, nonfailable initializers always create a valid instance, while failable initializers can return nil to indicate failure during initialization.

7. What is the difference between static, class and Global Functions in Swift?

Key Differences

FeatureStatic FunctionClass FunctionGlobal Function
Defined InStructs, enums, or classesClasses onlyOutside any type
Type of AccessAccessed on the type itselfAccessed on the type itselfAccessible globally in the module
Override CapabilityCannot be overriddenCan be overridden by subclassesCannot be overridden
Use CaseUtility functions within a typeFunctions that may need subclass-specific behaviorGeneral-purpose functions independent of any type

8. What is the default Closure type?

In Swift, non-escaping is the default for closure parameters. This means that, by default, a closure is expected to complete its execution within the function that it’s passed to and cannot outlive the function’s scope.

If you need a closure to escape the function’s scope (e.g., for asynchronous operations, callbacks, or storing the closure to execute later), you explicitly mark it with @escaping.

Example

// Non-escaping closure (default)
func performTask(operation: () -> Void) {
    operation()  // Must complete within the function's scope
}

// Escaping closure
func performAsyncTask(operation: @escaping () -> Void) {
    DispatchQueue.global().async {
        operation()  // Can outlive the function's scope
    }
}

In this example:

  • performTask takes a non-escaping closure (operation), which means operation must execute within performTask.
  • performAsyncTask takes an @escaping closure, allowing operation to execute asynchronously after performAsyncTask has returned.

9. What are Cocoapods?

CocoaPods: A Dependency Manager for iOS & macOS

CocoaPods is a dependency manager for Swift and Objective-C projects in iOS and macOS. It simplifies the process of integrating third-party libraries into your project by automating the installation and management of dependencies.


Why Use CocoaPods?

  1. Easy Dependency Management – Instead of manually downloading, configuring, and updating libraries, CocoaPods automates the process.
  2. Version Control – CocoaPods allows you to lock specific versions of libraries to ensure compatibility and stability.
  3. Consistent Builds – It ensures that every team member has the same dependencies and versions installed.
  4. Centralized Repository – CocoaPods uses the CocoaPods Trunk to store and distribute thousands of libraries.

10. What is Indices in String?

In Swift, indices in a String refer to positions within the string where characters are located. Swift’s String type is designed to handle Unicode and multi-byte characters, so it doesn’t use integer indices (like 0, 1, 2…) as you might expect in other programming languages. Instead, Swift has a special index type called String.Index that accurately navigates the variable-width character encoding in strings.

Key Points about String Indices in Swift

  1. String.Index: Swift uses String.Index as an index type to handle each character accurately, regardless of its byte size. A single character might occupy more than one byte in memory, so integer-based indexing isn’t sufficient.
  2. Accessing Characters with Indices:
    • You can’t use an integer directly; instead, you must use String.Index positions to access characters within a string.
  3. Navigating Indices:
    • Swift provides methods like startIndex and endIndex to get the start and end indices of a string.
    • Use index(after:), index(before:), or index(_:offsetBy:) to navigate the string with indices.
  4. Range of Indices:
    • Swift strings also support ranges based on indices. You can create ranges using String.Index to access substrings.

11. What is App Transport Security (ATS) in iOS?

App Transport Security (ATS) is a feature introduced by Apple in iOS 9 and macOS 10.11 to enhance the security of network communications within apps. ATS enforces strict security standards for apps by requiring secure connections over HTTPS, rather than HTTP, ensuring that data transmitted between an app and a server is encrypted and secure.

Key Features of App Transport Security

  1. HTTPS Enforcement
  2. Enhanced Privacy and Security
  3. Security Requirements
  4. Custom ATS Configuration

Example Configuration in Info.plist

To allow HTTP connections only to a specific domain:

<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
</dict>
</dict>
</dict>

12. Why is Swift a type-safe language?

Swift is considered a type-safe language because it enforces strict type-checking rules at compile time, which helps prevent errors by ensuring that the types of values match the expected types in code. This leads to more reliable and predictable code, reducing the likelihood of runtime errors due to type mismatches.

Key Reasons Swift is Type-Safe

  1. Strict Type Checking
  2. No Implicit Type Conversion
  3. Type Inference
  4. Optionals for Null Safety
  5. Generic Types

13. What is protocol-oriented programming?

Protocol-Oriented Programming (POP) is a paradigm in Swift that emphasizes the use of protocols to define and organize functionality. Unlike object-oriented programming, which relies on inheritance and class hierarchies, POP encourages composing behavior through protocols, making code more modular, flexible, and reusable. Swift was designed with POP in mind, making protocols a core feature of the language.

Key Concepts of Protocol-Oriented Programming

  1. Protocols as Behavior Contracts
  2. Protocol Extensions
  3. Value Types Over Inheritance
  4. Protocol Composition
  5. Protocol-Oriented Programming with Generics

Advantages of Protocol-Oriented Programming

  • Greater Modularity: Protocols make it easy to break down complex functionality into small, reusable components.
  • Increased Flexibility: Types can adopt multiple protocols, allowing for flexible behavior composition without rigid class hierarchies.
  • Encourages Value Types: POP promotes the use of value types (like structs and enums), which are safer and avoid common issues related to shared state and memory management in classes.
  • Reduced Code Duplication: Protocol extensions enable default implementations, reducing redundant code and making code maintenance easier.

14. What is method swizzling? When to use?

Method swizzling is a technique in Objective-C (and by extension, Swift, due to its interoperability with Objective-C) that allows you to dynamically change the implementation of an existing method at runtime. This means you can replace one method’s implementation with another, effectively swapping or “swizzling” them.

In Objective-C, method swizzling is done using the Objective-C runtime functions provided by objc/runtime.h. It’s primarily used to add custom behavior to existing methods, especially in cases where you cannot subclass or directly modify the original implementation, such as with system classes.

How Method Swizzling Works

Method swizzling works by:

  1. Locating the method implementations in the Objective-C runtime.
  2. Swapping the method implementations for the original and the custom method.
  3. Allowing calls to the original method to invoke the custom implementation instead.

In practice, swizzling is commonly used for UIKit classes in iOS, where you might want to add additional behaviour to methods like viewDidLoad in UIViewController.

When to Use Method Swizzling

Method swizzling should be used with caution. It’s typically employed for:

  1. Analytics and Logging: To add tracking for certain lifecycle events (e.g., logging viewDidLoad calls).
  2. Custom Behavior in System Classes: To add extra functionality to UIKit methods (e.g., modifying alert view behaviour).
  3. Bug Fixes for Framework Issues: Sometimes used to work around framework limitations or bugs that can’t be fixed otherwise.
  4. Dependency Injection: For testing, to inject mock implementations without changing the original code.

When Not to Use Method Swizzling

Despite its uses, swizzling can lead to unintended consequences:

  • Code Readability: Swizzling makes code less readable and harder to debug, as it alters behavior in unexpected ways.
  • Maintenance Issues: As methods are swapped at runtime, tracking changes across different versions can be difficult, especially as system methods are updated.
  • Conflicts with Other Code: If multiple swizzles occur on the same method, they can conflict and cause unexpected results.
  • Runtime Risks: Swizzling uses Objective-C runtime features, so issues won’t be caught at compile time, which increases the risk of crashes or unexpected behavior.

15. What is an Extended String Delimiter?

An Extended String Delimiter in Swift is a feature that allows you to write strings containing special characters (such as quotes, backslashes, or line breaks) without needing to escape them. You do this by adding one or more # symbols around the string. The number of # symbols on each end of the string must match, and if you include special characters inside the string, they must also be preceded by the same number of # symbols.

An Extended String Delimiter in Swift is a feature that allows you to write strings containing special characters (such as quotes, backslashes, or line breaks) without needing to escape them. You do this by adding one or more # symbols around the string. The number of # symbols on each end of the string must match, and if you include special characters inside the string, they must also be preceded by the same number of # symbols.

Usage and Syntax

If you want to include a double quote or backslash in a regular string, you’d need to escape it:

let quote = "She said, \"Hello, world!\""

With extended string delimiters, you can avoid using the escape character by adding # symbols:

let quote = #"She said, "Hello, world!""#
let filePath = #"C:\Program Files\App"#

16. What is a Backgroundtask identifier?

A background task identifier in iOS is a mechanism that allows your app to request extra execution time from the operating system to complete certain tasks after it enters the background. Since iOS limits the time an app can run in the background, using background task identifiers lets you perform important tasks, like saving data or finishing a network request, even when the app isn’t actively running in the foreground.

Purpose of Background Task Identifiers

When your app transitions to the background, the system provides a limited amount of time (usually a few seconds) to finish critical tasks. If you need more time for tasks that could take longer, you can use a background task identifier to request additional background execution time, giving you a grace period to complete these tasks.

How to Use Background Task Identifiers

  1. Begin the Background Task: When your app is about to enter the background, you start a background task by calling beginBackgroundTask(withName:expirationHandler:) on UIApplication. This method returns a unique identifier that represents the background task.
  2. Perform the Task: You can perform the required task (e.g., saving data or uploading files) during the background time allocated by the identifier.
  3. End the Background Task: Once the task is completed, or if you no longer need extra time, you should end the background task by calling endBackgroundTask(_:) and passing in the identifier. This is important because failing to end a background task can lead to system warnings or the app being terminated.

17. What is a framework?

A framework in software development is a reusable set of code, libraries, and resources designed to simplify and streamline the development of specific types of applications or functionality. Frameworks provide pre-built components, defined structures, and often establish patterns that help developers write consistent, efficient, and scalable code. They can include code libraries, tools, APIs, documentation, and other resources to support specific functionalities.

Characteristics of a Framework

  1. Reusable Code: Frameworks contain pre-written code, which developers can call and use, reducing the need to write boilerplate or foundational code from scratch.
  2. Defined Structure: Frameworks often come with defined structures or patterns, which guide developers on how to organize and interact with their code.
  3. Pre-Built Functionality: Frameworks offer tools and components to handle common tasks (e.g., network communication, database management, UI elements) so that developers can focus on building unique features.
  4. Extensibility: Frameworks allow developers to add custom functionality on top of the existing framework by extending classes, protocols, or adding custom modules.

Example of Frameworks in iOS Development

iOS has numerous frameworks that provide essential functionalities for app development:

  • UIKit: A framework for building and managing the app’s user interface.
  • CoreData: A framework for managing the data model layer, enabling data persistence.
  • AVFoundation: A framework for working with audio and video.
  • MapKit: A framework for displaying maps and adding location-based features.

Advantages of Using Frameworks

  • Reduces Development Time: Pre-built functionality and structure help developers quickly build features without starting from scratch.
  • Increases Code Quality and Consistency: Frameworks provide standard ways of doing things, helping maintain consistency.
  • Supports Maintainability: Frameworks encourage modular and organized code, which is easier to maintain and update.

18. What is instrument?

Instruments is a performance and debugging tool provided by Apple, part of the Xcode development suite. Instruments allows developers to analyze and troubleshoot their iOS and macOS applications in real-time, using a variety of profiling tools to track performance metrics such as memory usage, CPU usage, network activity, energy consumption, and more. It is invaluable for detecting performance bottlenecks, leaks, and other issues that can affect an app’s functionality and user experience.

Key Features of Instruments

  1. Real-Time Data Collection: Instruments captures live data from your application as it runs, which you can analyze immediately or save for later inspection.
  2. Wide Range of Profiling Tools (Instruments): Each instrument focuses on a specific aspect of app performance, such as memory allocation, CPU usage, network activity, and file I/O.
  3. Detailed Visualizations: Instruments displays data in various formats (e.g., graphs, tables) that help identify patterns and anomalies in your app’s behavior.
  4. Recording Sessions: You can record different sessions during specific workflows in your app, which allows you to capture the exact moments where issues might be occurring.
  5. Comparative Analysis: Instruments lets you compare recorded sessions or snapshots, enabling you to measure the impact of code changes on performance over time.

Common Instruments Tools in Xcode

Some commonly used instruments in the Xcode suite include:

  • Time Profiler: Analyzes how much time each method and function in your code takes to execute, helping you identify and optimize time-consuming code.
  • Allocations: Tracks memory allocation to see where memory is being allocated and deallocated, which can help detect memory leaks and optimize memory usage.
  • Leaks: Identifies memory leaks in your app, helping prevent crashes and performance degradation by showing where objects aren’t being deallocated.
  • Activity Monitor: Displays real-time CPU, memory, disk, and network usage, giving a high-level overview of system resource consumption.
  • Energy Log: Monitors energy consumption, which is useful for optimizing battery usage on mobile devices by pinpointing energy-intensive processes.
  • Network: Monitors network traffic, including the number of requests, data sent, and data received, to help optimize network efficiency and troubleshoot slow responses.

19. How would you implement localization in iOS?

To implement localization in iOS, you follow a structured process to adapt your app to support multiple languages and regions. Here’s how you can do it:

Steps to Implement Localization

  1. Prepare Your Project:
    • Enable localization in your Xcode project by selecting your project file, navigating to the Info tab, and adding the languages you want to support under Localizations.
  2. Use .strings Files:
    • Create a Localizable.strings file for each language.
    • For example, Localizable.strings (English) and Localizable.strings (Spanish).
  3. Add Localized Strings:
    • In the Localizable.strings files, add key-value pairs for each string you want to localize.
    • Example:
      • English: "greeting" = "Hello";
      • Spanish: "greeting" = "Hola";
  4. Access Localized Strings in Code:
    • Use the NSLocalizedString function to retrieve the localized string for a given key.
    • Example: let greeting = NSLocalizedString("greeting", comment: "Greeting message")
  5. Localize Storyboards and XIBs:
    • Select the storyboard or XIB file, go to the File Inspector, and check the languages under Localization.
    • Xcode will generate a strings file for the interface elements which you can translate similarly to Localizable.strings.
  6. Handle Date, Time, and Number Formats:
    • Use DateFormatter, NumberFormatter, and other locale-aware classes to automatically adjust formats based on the user’s locale.
    • Example: let formatter = NumberFormatter() formatter.numberStyle = .currency formatter.locale = Locale.current let price = formatter.string(from: 9.99)
  7. Pluralization:
    • For plural forms, use .stringsdict files, where you define different formats for singular and plural forms.
  8. Testing Localization:
    • Change the language in the Simulator or on a physical device under Settings > General > Language & Region to test different languages.
    • Use Xcode’s Preview to see how your UI adapts to different localizations without running the app.
  9. Localize App Name:
    • Add a key-value pair for CFBundleDisplayName in the InfoPlist.strings file for each language.
    • Example for Spanish: "CFBundleDisplayName" = "TuAppNombre";
  10. Localized Assets:
    • Use Asset Catalog to include localized images or other assets for different languages.

By following these steps, you can ensure your app is properly localized, providing a better user experience for a global audience.

20. What do you usually use as version control? Can you explain how you use it?

The most commonly used version control system is Git. It’s widely adopted because of its powerful features, flexibility, and support from major platforms like GitHub, GitLab, and Bitbucket. Here’s an explanation of how Git is typically used:

How Git is Used

  1. Repository (Repo) Initialization:
    • A project is initialized as a Git repository using git init, or an existing repository is cloned using git clone <repository-url>.
  2. Staging Changes:
    • Changes made to files are added to the staging area using git add <file> or git add . for all changes. This prepares them to be committed.
  3. Committing Changes:
    • Staged changes are saved to the repository with a descriptive message using git commit -m "Your commit message".
    • Each commit acts as a snapshot of the project at that point in time.
  4. Branching:
    • New branches are created for features or bug fixes using git branch <branch-name> and switched using git checkout <branch-name> or git switch <branch-name>.
    • This allows parallel development without affecting the main codebase (often main or master branch).
  5. Merging:
    • Once a feature or fix is complete, it is merged back into the main branch using git merge <branch-name>. This incorporates the changes from the branch into the target branch.
  6. Pushing to Remote Repositories:
    • Local commits are pushed to a remote repository on a platform like GitHub using git push origin <branch-name>.
    • This makes the changes available to collaborators and for deployment pipelines.
  7. Pull Requests (PRs):
    • A pull request is created to propose merging changes from one branch to another. This is common in team settings to facilitate code reviews.
    • Once reviewed and approved, the pull request is merged.
  8. Pulling Changes:
    • Updates from a remote repository are fetched and merged into the local branch using git pull.
  9. Conflict Resolution:
    • When multiple changes conflict, Git marks the conflicting sections in the files. Developers resolve these conflicts manually, then stage and commit the resolved files.
  10. Version Tagging:
    • Significant points in the project’s history (like releases) are marked with tags using git tag <tag-name>.

Benefits of Using Git

  • History Tracking: Every change is recorded, allowing developers to revert to previous states if needed.
  • Collaboration: Multiple developers can work on the same project without overwriting each other’s work.
  • Branching and Merging: Supports multiple development lines for features, fixes, or experiments.
  • Distributed System: Every developer has the full history of the project, not just the latest snapshot.

This workflow ensures that code changes are well-tracked, reviewed, and integrated systematically.

21. What is Subscripts?

Subscripts in Swift are a way to access elements within a collection, list, or sequence using a custom syntax, typically similar to array indexing. Subscripts enable you to write cleaner, more concise code for getting and setting values in your objects.

Basics of Subscripts

In Swift, you define a subscript with the subscript keyword. Here’s the syntax:

subscript(index: Int) -> Int {
get {
// Return the appropriate value
}
set(newValue) {
// Set a new value
}
}

A subscript can have multiple parameters and return values of any type. The most common use case is to access elements within data structures like arrays, dictionaries, and custom types.

22. What is the difference between a static and a dynamic framework?

The key differences between static and dynamic frameworks in iOS development are as follows:

Static Framework

  1. Linking: Static frameworks are linked at compile time, and their code is copied into the final executable binary of the app.
  2. Runtime: Since the code is embedded in the app binary, the app does not need to load the framework separately at runtime.
  3. Size: The app binary size increases because the entire framework code is included.
  4. Performance: They may have a slight performance advantage since no additional loading occurs at runtime.
  5. Distribution: Typically used when the framework is private and not meant for external distribution.

Dynamic Framework

  1. Linking: Dynamic frameworks are linked at runtime, and their code is loaded into memory only when the app uses them.
  2. Runtime: The framework code is not embedded in the app binary but loaded separately, allowing for more modular app structure.
  3. Size: The app binary size is smaller, but the total app size might increase since the framework is bundled as a separate entity.
  4. Performance: There might be a slight performance overhead due to the loading process at runtime.
  5. Distribution: Commonly used for public frameworks distributed to multiple apps, such as through CocoaPods or Swift Package Manager.

In summary, static frameworks are embedded directly into the app’s binary, while dynamic frameworks are separate entities loaded at runtime, providing more flexibility and modularity.

23. Are arrays thread-safe?

In Swift, arrays are not thread-safe by default. Accessing or modifying an array from multiple threads simultaneously can lead to unpredictable behavior and crashes due to race conditions or data corruption.

Thread Safety Issues with Arrays

  • Reading and Writing: Simultaneous read and write operations to an array can cause unexpected behavior.
  • Mutability: Arrays in Swift are mutable by default. If one thread modifies an array while another thread is reading or writing, it can cause inconsistencies.

24. What Compiler does Swift Uses?

Swift uses the LLVM (Low-Level Virtual Machine) compiler infrastructure, with its front-end called Swift Compiler (swiftc).

Key Components of Swift Compiler:

  1. Frontend (swiftc):
    • The Swift compiler frontend (swiftc) handles the syntax and semantics of Swift code.
    • It performs lexical analysis, parsing, semantic analysis, and type checking.
    • The frontend generates intermediate representation (IR) code, which is then passed to the LLVM backend.
  2. LLVM Backend:
    • The LLVM backend takes the intermediate representation generated by the Swift frontend and performs further optimizations.
    • It then generates machine code for various platforms and architectures (e.g., x86_64, ARM).
  3. REPL (Read-Eval-Print Loop):
    • Swift includes an interactive environment where you can write and run Swift code interactively.
    • It leverages the same Swift compiler and LLVM backend to compile and execute the code on the fly.

By using LLVM, Swift benefits from advanced compiler optimizations and supports multiple platforms, making it a powerful and efficient compiler choice.

25. What is the difference between push notification and remote notification?

The terms push notification and remote notification are often used interchangeably, but they have slight differences in context. Let’s break down each term:

Push Notification

  • Definition: Push notifications refer to messages that are “pushed” from a server to a device, typically through a service like Apple Push Notification Service (APNs) for iOS devices.
  • Context: The term “push” emphasizes the action of sending the notification from the server to the client (device).
  • Usage: Push notifications are generally used to notify users about new information, updates, or events, even when the app is not actively running.

Remote Notification

  • Definition: A remote notification is a specific type of push notification where the content is delivered from a remote server to the device via a push notification service.
  • Context: The term “remote” highlights that the notification content is coming from a remote server rather than being generated locally on the device.
  • Usage: Remote notifications are used to deliver updates, messages, or alerts to users that are generated on the server side.

Key Differences

  • Push Notification is a broader term that encompasses any notification pushed from a server to a device.
  • Remote Notification specifically refers to push notifications where the payload is sent from a remote server.

In essence, remote notifications are a subset of push notifications. For iOS development, remote notifications are usually handled through APNs, while local notifications are generated and scheduled directly on the device.

Conclusion

Mastering Swift and iOS development requires a deep understanding of various concepts, from memory management to protocol-oriented programming. In this post, we covered 25 intermediate-level iOS interview questions that help bridge the gap between beginner and advanced topics. By revisiting concepts like method swizzling, escaping closures, protocol-oriented programming, and localization, you can strengthen your technical knowledge and prepare for real-world challenges.

Whether you’re preparing for an interview or looking to refine your Swift skills, continuously practicing these topics will enhance your problem-solving abilities and make you a more efficient iOS developer. Keep exploring, keep coding, and stay updated with the latest advancements in Swift! 🚀

If you missed – Basic art 2 ⏩

Thanks for reading! I hope this will be useful for understanding or refreshing some of the iOS concepts. Please share your feedback, and queries regarding any of the topics in the comments below. Till then happy Coding!!! 💚💚💚💚💚💚💚💚💚💚

The Twitter contact:

Any questions? You can get in touch with me here

Leave a Reply

Your email address will not be published. Required fields are marked *