0% found this document useful (0 votes)
369 views

Cracking - iOS Interview - Questions Notes

Uploaded by

Pqrswed
Copyright
© © All Rights Reserved
Available Formats
Download as RTF, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
369 views

Cracking - iOS Interview - Questions Notes

Uploaded by

Pqrswed
Copyright
© © All Rights Reserved
Available Formats
Download as RTF, PDF, TXT or read online on Scribd
You are on page 1/ 12

Cracking - interview - questions notes

Q1. What is the difference between Static and Class


variable?
- Both are similar in nature. Only difference is Class variables can be
overridden by subclasses while static variables cannot be overridden.

Q2. Are lazy vars computed more than once?


- No, Lazy variable is a variable that is initialised only when it is
accessed for the first time. The
initialisation code is executed only once, and the result is stored in the
variable. Subsequent accesses
to the variable return the stored value, without recomputing it.
- Also Lazy variables are thread-safe, which means that their initialisation
code is executed only once,
even in a concurrent environment.
- Lazy variables are not suitable for cases where the initialisation code
has side effects or modifies
state, as the side effects or state changes will occur only once, when the
variable is first
accessed.

Q3. Explain the use of the defer keyword in Swift.


**A:** The `defer` keyword was introduced in Swift 2.0. In a defer
statement, code is executed before
program control is transferred outside of the scope where the statement
appears.
Multiple Defer Statements:
If multiple defer statements appear in the same scope, they are
executed in the reverse order of their
appearance.

Q4. How to implement a property which is public/internal


but mutation is private?
** In Swift, you can use the `private(set)` keyword to make a property's
setter private, and the
`public` keyword to make its getter public.
private(set) public var age: Int

Q25. You have a complex layout that needs to be


dynamically resized based on the device's screen size.
How would you use Auto Layout to ensure that the
layout is always correct?
- Use Constraints:

- Use Size Classes:

- Use Stack View:

- Use Content Hugging and Compression Resistance:

Q39. What is Dependency Injection and what are it's


advantages?
Dependency Injection is a design pattern that promotes loosely coupled
code by separating the creation and management of dependencies from
the class that uses them.

- Dependency Injection can be implemented in several ways. (CPM)

Constructor injection:

Property injection:

Method injection:

- Advantages of Dependency Injection : (TFSR)

Testability

Flexibility

Separation of concerns:**

Reusability:

Q40. Describe the Singleton pattern and its drawbacks.


How would you implement a thread-safe Singleton?
Singleton is a creational design pattern. The key feature of a Singleton
class is there can be only one instance of the class in complete lifecycle
of an application and it can be accessed globally in the project modules.
Some common examples of Singleton design pattern found in Apple
APIs are:

`UserDefaults.standard` `FileManager.default` `URLSession.shared`


Drawbacks of using Singleton

1. Hard to Mock:

2. Poor State Management

3. Not Thread safe

4. - How to make a Singleton class thread safe?

5. We can use concurrent queues with barrier flag. With this


approach, we can concurrently execute the read operations

6. but when write operation takes place, the barrier will behave as a
synchronous dispatch. It'll block other threads to execute

7. on the shared resource until barrier block execution completes.

8. Here's an example of how concurrent queues with barrier work:

9. private let concurrentQueue = DispatchQueue(label:


"concurrentQueue",

attributes: .concurrent)
private init() {}
func log(key: String, message: String, logLevel : LogLevel = .info) {
concurrentQueue.asyncAndWait(flags: .barrier, execute: {
let timestamp = DateFormatter.localizedString(from: Date(),
dateStyle: .short, timeStyle: .long)
logEvents[key] = message
print("\(timestamp) \(logLevel.rawValue) \(message)")
})
}

func readValue(key: String) -> String? {


concurrentQueue.sync {
value = logEvents[key] as? String
}
return value
}

Q43. Explain the types of sessions and tasks supported


by URLSession class.
There are three types of sessions supported by URLSession:

-Default Session: This is the most common and default session type
used in the URLSession class. A global cache, credential storage object,
and cookie storage object are used.

let config = URLSessionConfiguration.default

- Ephemeral Session:

t it doesn't use any storage like caches, cookies, or credentials. It's


useful when you need to make requests with different or temporary
credentials,

like when you're authenticating with an API. It's also useful when you
want to keep your requests and responses private and not stored on
disk.

let config = URLSessionConfiguration.ephemeral ```

- Background Session:

This session type is used when you want to download or upload data in
the background, even when your app is not running.

The system handles the session and provides progress updates through
delegate methods. The background session is useful when

you want to download large files or perform a lengthy operation that


requires more than a few seconds.

This is how we define the default session:

let config = URLSessionConfiguration.background(withIdentifier:


"com.example.background")

- There are several tasks supported by URLSession, including:

Data Task:

Download Task:
Upload Task:

WebSocket Task

Q55. What are the different ways to persist data in iOS


applications and when to use them?
-UserDefaults is suitable to use to store small and non-
sensitive information.
- FileManager is an ideal tool for storing large files to enhance
the offline experience by caching them.- The Keychain is the
best option for storing very sensitive information in a secure
and encrypted manner.
-CoreData is mostly used to store complex and large
datasets in a structured manner.

Q59. How does ARC work in Swift?


It works at compile time. The compiler automatically adds
and removes retain and release operations to the objects in
the application, depending on how the objects are being used
in the code.

Q60. What is Copy-on-Write in Swift? Explain how to


customize its implementation.
Copy-on-Write (CoW) is an optimization technique used in Swift to delay
the actual copying of an object until it is necessary. Instead of
immediately copying an object when it is assigned or passed around, Swift
uses references to the same object. The actual copy is made only when
one of the references attempts to modify the object, ensuring efficient
memory usage.

This concept is particularly relevant for value types like Array,


Dictionary, and Set, which are implemented with CoW in Swift.

How Copy-on-Write Works in Swift


When you assign or pass a value type (such as an array) to another
variable or function, Swift does not immediately create a full copy of that
data. Instead, both variables reference the same underlying storage. If
either of the variables is modified, Swift will then create a copy of the data
for the modified instance, ensuring that the other reference remains
unchanged.

Example of Copy-on-Write Behavior

var originalArray = [1, 2, 3]

var copiedArray = originalArray // No copy occurs here; they share the


same storage

copiedArray.append(4) // Now, a copy is made because copiedArray is


modified

print(originalArray) // Output: [1, 2, 3]

print(copiedArray) // Output: [1, 2, 3, 4]

In the example above:

• originalArray and copiedArray initially share the same storage.


• When copiedArray is modified with append(4), Swift creates a separate
copy of the array for copiedArray. The original array remains
unaffected.

Customizing Copy-on-Write in Swift

You can implement CoW in your custom value types by using a similar
strategy. The key is to store the actual data in a reference type (like a
class) and ensure that you perform the copy operation only when a
mutation occurs.

Steps to Implement Custom Copy-on-Write

1 Define a Reference Type for the Data: Create a class that holds the
actual data. Since classes are reference types, multiple instances can
share the same storage.

2 Use a Struct for the Public Interface: The struct will act as the value
type interface that users interact with. The struct will contain a reference
to the class that holds the data.

3 Implement the Copy-on-Write Mechanism: Before any modification,


check if the struct is the only owner of the reference. If not, create a copy
of the data before making changes.

Example Implementation
final class CoWData {
var value: Int

init(value: Int) {
self.value = value
}
}

struct MyCoWType {
private var data: CoWData

init(value: Int) {
self.data = CoWData(value: value)
}

var value: Int {


get {
return data.value
}
set {
// Perform Copy-on-Write
if !isKnownUniquelyReferenced(&data) {
data = CoWData(value: newValue)
} else {
data.value = newValue
}
}
}
}

var a = MyCoWType(value: 10)


var b = a // No copy happens here

b.value = 20 // Copy occurs here because b is modified

print(a.value) // Output: 10
print(b.value) // Output: 20

Explanation:

• Reference Type (CoWData): The CoWData class holds the actual data.
Since it’s a class, it's a reference type, and multiple MyCoWType instances
can share it.

• Struct (MyCoWType): This struct provides the interface to interact with


the data. It contains a reference to a CoWData instance.
• isKnownUniquelyReferenced(_:): This function checks if the current
struct is the only one holding a reference to the CoWData instance. If it’s
not unique, a copy is made before modifying the data.

Key Points:

• Efficient Memory Use: CoW allows multiple instances to share the same
data without unnecessary copying, reducing memory usage.
• Lazy Copying: The actual copying of data is delayed until it is absolutely
necessary, i.e., when a mutation occurs.
• Thread Safety: CoW, by design, helps avoid data races since any
mutation leads to the creation of a separate copy, isolating changes.
When to Use Custom CoW:

Custom CoW is useful when you have a value type that encapsulates data
that could be expensive to copy, and you want to ensure efficient memory
usage while maintaining value semantics. Examples include custom
collections, complex data structures, or types that interact with
performance-critical operations.

Can you explain the concept of a barrier task in GCD and its usage?
A barrier task in Grand Central Dispatch (GCD) is a special type of task
that you submit to a concurrent dispatch queue. It acts as a
synchronization point within the queue, ensuring that all tasks submitted
before the barrier task complete execution before the barrier task begins.
Similarly, tasks submitted after the barrier task will not start until the
barrier task has finished.

Barrier tasks are particularly useful when you need to perform a write
operation or any other task that should not be executed concurrently with
other tasks in a concurrent queue.

How Barrier Tasks Work

When a barrier task is added to a concurrent dispatch queue, GCD


guarantees the following:

1 Execution Order: All tasks that were added to the queue before the
barrier task must complete before the barrier task is executed.
2 Exclusive Execution: While the barrier task is executing, no other tasks
in the queue will run. The barrier task has exclusive access to the queue.
3 Subsequent Tasks: Tasks added after the barrier task will only start
executing after the barrier task has completed.
Example of a Barrier Task
let concurrentQueue = DispatchQueue(label:
"com.example.concurrentQueue", attributes: .concurrent)
var sharedResource = [Int]()
// Simulate a read operation
concurrentQueue.async {
for item in sharedResource {
print("Reading item: \(item)")
}
}

// Simulate another read operation


concurrentQueue.async {
for item in sharedResource {
print("Reading item again: \(item)")
}
}

// Perform a barrier write operation


concurrentQueue.async(flags: .barrier) {
sharedResource.append(1)
print("Writing to shared resource")
}

// Simulate another read operation


concurrentQueue.async {
for item in sharedResource {
print("Reading item after write: \(item)")
}
}

Q65. How does DispatchWorkItem perform actions?


DispatchWorkItem is a class that represents a task that can
be executed on a DispatchQueue. DispatchWorkItem
instance encapsulates a block of code (i.e., a closure) that
can be executed synchronously or asynchronously. The
closure is executed on the thread associated with the
specified DispatchQueue.
Here's an example of creating a DispatchWorkItem and
executing it asynchronously:
func incrementNumber() {
var number: Int = 5
let workItem = DispatchWorkItem {
number += 5
}

workItem.notify(queue: .main) {
print("After dispatching the item, the number is \
(number).")
}

let queue = DispatchQueue.global(qos: .utility)


queue.async(execute: workItem)
}
incrementNumber()

Q66. What are the different types of queues provided by


GCD?
Serial queues:

Concurrent queues:

Main queue:

Global queues: QoS levels include user- interactive, user-initiated, utility,


background, and default.

Q72. What are Mocks and Stubs and what is the


difference between them?
A mock is a version of a test double that can be used to
verify interactions between objects. Mocks are used to check,
if a certain method was called with expected parameters.
They often contain expectations about the interactions that
will happen during the test.
Mocks are used when you need to verify that a particular
interaction occurred, such as ensuring that a method was
called a certain number of times with specific arguments.
A stub is a simplified implementation of an object that
returns predefined responses. It is primarily used to provide
data that a test needs to run. Stubs are usually static and do
not have behaviour beyond what is required for the test.
They do not record interactions. Stubs are used when you
need to control the indirect inputs of the system under test.
For example, if a method depends on an external service or a
database, you can use a stub to return fixed data without
calling the actual service or database.

Q82. What is Capture List in Swift?


A Capture list defines which variables and constants should
be captured and how they should be captured by a closure. It
is a comma separated list within square brackets and is
written after the opening curly brace of closure and before
the parameters.
lazy var callbackClosure = { [unowned self, weak delegate =
self.delegate] in
// closure body goes here
}

```

Q83. Explain Recursive enumeration and Associated


values.
Recursive enumeration is a type of enumeration where one
or more of the values in the enumeration refers to the
enumeration itself. In other words, an enumeration can be
defined in terms of itself. This is useful when you need to
represent a data structure that has a recursive structure,
such
as a tree or a factorial.Let's understand recursive
enumeration with an example.
enum ArithmeticExpression {
case value(Int)
indirect case addition(ArithmeticExpression,
ArithmeticExpression)
indirect case multiplication(ArithmeticExpression,
ArithmeticExpression)
}
When you define an enumeration with associated values,
Swift creates a copy of the associated value for each instance
of the enumeration that is created. This can result in
unnecessary memory usage when the associated value is a
large or complex data structure, such as a tree or a linked
list.
*The indirect keyword allows you to avoid this duplication by
telling Swift to store the associated value indirectly, using a
reference rather than a copy. This means that the associated
value is not copied with each instance of the enumeration,
but rather, a reference to the same value is used.*

Q84. Explain @autoclosure in Swift with an example.


n Swift, @autoclosure allows a function or closure parameter
to be automatically wrapped in a closure.
When a parameter is marked with @autoclosure, the
argument passed to that parameter is automatically wrapped
in a closure. This means that the argument is not evaluated
until used inside the function. This can improve performance,
especially when working with large or complex expressions.
func sampleFunction(_ lhs: Bool, _ rhs: @autoclosure () -> Bool) ->
Bool {
if lhs {
return true
} else {
return rhs()
}
}
sampleFunction(false, 1 + 1 == 2) // returns true

You might also like