Mar 13, 2025 iOS

iOS interview question

What is difference between opaque , generic and Any?

In Swift, opaque types, generic types, and Any are different ways of handling type abstraction. Here’s how they differ:


1. Opaque Types (some)

Opaque types allow you to specify a return type without revealing the exact concrete type, while still maintaining type safety.

βœ… Key Features:

  • Use some keyword.
  • The underlying type remains the same throughout.
  • Provides type safety while hiding implementation details.

βœ… Example:

func getNumber() -> some Numeric {
    return 42 // Returns an Int, but callers don’t know the exact type
}

πŸ”Ή The caller knows it’s a Numeric, but doesn’t know whether it’s an Int, Double, etc.


2. Generics

Generics allow functions, types, and methods to be written flexibly so they can work with multiple data types.

βœ… Key Features:

  • Use <T> (or other placeholder types).
  • The caller determines the concrete type.
  • Ensures type consistency.

βœ… Example:

func identity<T>(value: T) -> T {
    return value
}

let num = identity(value: 5)       // num is Int
let text = identity(value: "Hello") // text is String

πŸ”Ή The type T is determined by the caller.


3. Any Type

Any is the most flexible but also the least type-safe. It can hold any type, including value types, reference types, and even functions.

βœ… Key Features:

  • Can store any value.
  • No compile-time type safety.
  • Often requires type casting.

βœ… Example:

var anything: Any = 10
anything = "Swift"  // Allowed

if let text = anything as? String {
    print(text) // Prints "Swift"
}

πŸ”Ή Since Any can be anything, it often requires explicit type casting.


Comparison Table

Feature Opaque Type (some) Generic (T) Any
Type Safety βœ… Yes βœ… Yes ❌ No
Type Erasure βœ… Yes ❌ No βœ… Yes
Flexibility ❌ Less flexible βœ… Very flexible βœ… Most flexible
Requires Casting ❌ No ❌ No βœ… Yes
Known at Compile Time βœ… Yes βœ… Yes ❌ No

When to Use What?

  • Use some (Opaque Types) β†’ When you want to return a type while hiding its exact identity, but still keeping type safety.
  • Use Generics (T) β†’ When you need to write reusable and type-safe functions or types.
  • Use Any β†’ When working with heterogeneous data where the type is unknown or irrelevant at compile time.

2. What is the benfits of using async and await in swift

Benefits of Using async and await in Swift πŸš€

Swift’s async and await provide a modern, structured way to handle asynchronous operations, making your code more readable and easier to manage compared to traditional callback-based or completion handler approaches.


βœ… 1. Improved Readability and Maintainability

  • Before (Completion Handlers) πŸ‘Ž
func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        sleep(2)
        completion("Data fetched")
    }
}

fetchData { result in
    print(result)
}
  • After (async/await) πŸ‘
func fetchData() async -> String {
    sleep(2)
    return "Data fetched"
}

Task {
    let result = await fetchData()
    print(result)
}

πŸ”Ή Why better? The async/await syntax looks like synchronous code, making it much easier to follow.


βœ… 2. Eliminates Callback Hell (Nested Closures)

  • Before: Deeply nested callbacks
fetchData { data in
    process(data) { processedData in
        save(processedData) { success in
            print("Saved successfully!")
        }
    }
}
  • After: Cleaner, structured approach
func performOperations() async {
    let data = await fetchData()
    let processedData = await process(data)
    let success = await save(processedData)
    print("Saved successfully!")
}

Task {
    await performOperations()
}

πŸ”Ή Why better? Flat, sequential execution avoids deeply nested callbacks.


βœ… 3. Better Error Handling with do-catch

  • Before: Error handling in callbacks
fetchData { result, error in
    if let error = error {
        print("Error: \(error)")
        return
    }
    print(result!)
}
  • After: try with do-catch
func fetchData() async throws -> String {
    throw NSError(domain: "NetworkError", code: 404, userInfo: nil)
}

Task {
    do {
        let data = try await fetchData()
        print(data)
    } catch {
        print("Error: \(error)")
    }
}

πŸ”Ή Why better? Standardized try-catch error handling instead of handling errors in multiple places.


βœ… 4. More Efficient and Scalable Code

  • Traditional GCD Approach (Thread Blocking)
DispatchQueue.global().async {
    let data = fetchDataSync()
    DispatchQueue.main.async {
        print(data)
    }
}
  • Using async/await (Non-blocking)
func fetchData() async -> String {
    return "Data fetched"
}

Task {
    let data = await fetchData()
    print(data)
}

πŸ”Ή Why better? The async/await method does not block threads unnecessarily, improving efficiency.


βœ… 5. Works Seamlessly with async let for Parallel Execution

async let data1 = fetchData()
async let data2 = fetchData()
let results = await [data1, data2]
print(results)

πŸ”Ή Why better? Fetches data in parallel instead of sequentially, improving performance.


πŸ”Ή When to Use async/await?

  • Networking (API calls)
  • Database queries
  • File I/O operations
  • Heavy computations
  • Background tasks

Conclusion

βœ… Simpler syntax
βœ… Easier error handling
βœ… No callback hell
βœ… Efficient & non-blocking
βœ… Better scalability

3. what is difference between merge and rebase

Difference Between git merge and git rebase πŸš€

Both merge and rebase are used to integrate changes from one branch into another in Git, but they work differently.


πŸ”Ή 1. git merge (Preserves History)

Merging combines the histories of two branches by creating a new merge commit. It keeps the commit history intact.

βœ… Key Features:

  • Creates a merge commit (extra commit).
  • Preserves commit history from both branches.
  • Good for keeping a full record of changes.

Example:

git checkout main
git merge feature-branch

πŸ”Ή This merges feature-branch into main with a merge commit.

Visualization (merge)

A---B---C  (main)
     \
      D---E  (feature-branch)
       \
        M  (merge commit)

πŸ”Ή 2. git rebase (Rewrites History)

Rebasing moves or reapplies commits from one branch onto another. Instead of merging, it reapplies each commit on top of the new base.

βœ… Key Features:

  • No merge commit (keeps history linear).
  • Moves (replays) commits from one branch to another.
  • Makes history cleaner but rewrites it.

Example:

git checkout feature-branch
git rebase main

πŸ”Ή This takes the commits from feature-branch and applies them on top of main.

Visualization (rebase)

A---B---C  (main)
          \
           D'---E'  (feature-branch, rebased)

Now feature-branch is like it was directly built on top of main, without a merge commit.


πŸ”₯ Key Differences Between Merge and Rebase

Feature git merge git rebase
Commit History Preserved (extra merge commit) Rewritten (linear)
Creates New Commit? βœ… Yes (merge commit) ❌ No
Preserves Original Context? βœ… Yes ❌ No (reapplies commits)
Use Case Keeping full history Keeping a clean, linear history
Conflict Handling Happens once at merge May happen at each commit

πŸ† When to Use What?

Scenario Use merge Use rebase
Feature branch integration βœ… βœ…
Keeping commit history intact βœ… ❌
Making history linear & clean ❌ βœ…
Collaboration (shared branches) βœ… ❌ (avoid on shared branches)

⚠️ Be Careful with git rebase

  • Avoid rebasing public/shared branches because it rewrites history, which can cause conflicts for others.
  • Use merge when working in a team to keep a clear history.
β†’ Index