iOS Interview Question (Part 1)
Run Loops
A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none. It’s an abstraction that provides a mechanism to handle system input sources (sockets, ports, files, keyboard, mouse, timers, etc.)
Each thread has its own run loop, which can be accessed via the currentRunLoop method.
STRONG VS WEAK VS UNOWNED
STRONG
A strong reference in Swift means that a variable or constant holds a reference to an object in memory, and as long as the reference exists, the object cannot be deallocated. By default, all references in Swift are strong unless explicitly declared as weak
or unowned
.
Example of Strong Reference
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var john = Person(name: "John") // Strong reference created
print(john.name) // Output: John
// How it Works:
// When you create an object, ARC sets the reference count to 1 because
// you have one strong reference to it.
var person = Person(name: "Alice") // Reference count = 1
var anotherPerson = person // Reference count = 2
anotherPerson = nil // Reference count = 1
person = nil // Reference count = 0, object deallocated
- The
john
variable holds a strong reference to thePerson
object. - As long as the
john
variable exists, thePerson
object cannot be deallocated.
Weak and unowned are used to solve leaked memory and retain cycles. Both do not increase the retain count.
when to use
When the object references is deallocated, a weak
reference automatically becomes nil.
which is why it’s often declared as an optional.
unlike weak
, they are not optional and assume that the referenced object will always exist. If you try to access an unowned
reference after the object has been deallocated, it will cause a runtime crash. So, unowned
is used when you are certain the reference will not become nil
as long as it's in use.
Take the example of Customer and CreditCard. Here, CreditCard cannot exist without a customer. A CreditCard instance never be deallocate the Customer that it refers to, And we only create a CreditCard instance by passing the Customer instance in the initializer. So we can absolutely guarantee that this credit card cannot exist without the Customer.
when the customer becomes nil, both the Customer and CreditCard get deallocated.
Difference between strong, weak, and unowned references in Swift?
Strong, weak, and unowned references are related to memory management. Strong is the default reference type in Swift. When an object is assigned to a strong reference, its retain count increases, meaning the object will stay in memory as long as there’s a strong reference to it.
strong references are part of the Automatic Reference Counting (ARC) system that manages the memory of objects.
How memory is manager in value type?
Stack Allocation (Default Behavior)
- When you create a struct, its data is typically stored on the stack.
- The stack is a fast, self-cleaning memory area, making value types more efficient.
- Memory is automatically deallocated when the struct goes out of scope.
Example:
struct Person {
var name: String
var age: Int
}
func createPerson() {
let p = Person(name: "Niraj", age: 30) // Stored on the stack
} // `p` is automatically deallocated after function execution
✔ Fast memory access
✔ No need for reference counting (ARC)
POLYMORPHISM
One to many form.
Polymorphism comes with two flavour.
- Method overloading (Compile Time Polymorphism)
- Method overriding (Runtime Polymorphism)
- Method overloading : same function name with different parameter.
- Method overriding: Method overriding is concept where even though the method name and parameters passed is similar, the behavior is different based on the type of object.
Output: Meooww and then wooooof
As you can see from the example above, makeNoise prints different result on the same Animal reference.
At runtime it decides the type of object and calls the corresponding method.
Difference between OOP and POP
POP emphasizes protocols to define behavior, while OOP relies on class hierarchies. POP promotes composition over inheritance, avoids tight coupling, and works better with Swift’s value types (structs, enums).
What are the advantages of protocol extensions?
- Answer: Protocol extensions provide default method/property implementations, reducing boilerplate and enabling code reuse without inheritance.
- Object VS Protocol
We deal with object and otherside we have functions
1. Inheritance vs. Composition
OOP:
- Class-based inheritance (e.g.,
Animal → Dog → Labrador
). - Hierarchical structure; subclasses inherit from parent classes.
POP:
- Types can adopt multiple protocols.
3. Value Types vs. Reference Types
OOP:
- Relies on reference types (classes).
- Instances are shared and passed by reference.
POP:
- Works well with value types (structs, enums).
- Promotes immutability and avoids shared mutable state.
4. Code Reuse
- OOP:
- Inheritance is the primary way to reuse code.
- Subclasses extend or override superclass behavior.
- POP:
- Protocol extensions allow default implementations.
- No need for strict class hierarchies.
5. Dispatch Mechanism
- OOP:
- Relies on dynamic dispatch (method calls decided at runtime).
- POP:
- Uses both dynamic and static dispatch (e.g., static for structs, final classes for optimization).
Protocol-Oriented Approach:
protocol Flyable {
func fly()
}
extension Flyable {
func fly() {
print("Flying by default!")
}
}
struct Bird: Flyable {}
struct Airplane: Flyable {}
let bird = Bird()
bird.fly() // "Flying by default!"
let airplane = Airplane()
airplane.fly() // "Flying by default!"
Object-Oriented Approach:
class Vehicle {
func drive() {
print("Driving!")
}
}
class Airplane: Vehicle {
func fly() {
print("Flying!")
}
}
let airplane = Airplane()
airplane.drive() // "Driving!"
airplane.fly() // "Flying!"
What isMultithreading in core-data
Core Data is designed to be used on the main queue by default. This means you should perform all Core Data operations, including fetching, saving, and accessing managed objects, on the main queue or main thread.
How to handle
- Create a dedicated
NSManagedObjectContext
for the main queue, which you can use to perform operations directly on the main thread. This context should be associated with the persistent store coordinator. - Background Queue Contexts: Create separate
NSManagedObjectContext
instances associated with private background queues. You should create a new context for each background queue to ensure thread safety. - Parent-Child Context Relationship: Establish a parent-child relationship between the main queue context and the background queue contexts. Set the main queue context as the parent of the background contexts. This allows you to propagate changes made in the background contexts to the main context.
- Perform Block-Based Operations: When working with a background queue context, use the
performBlock
orperformBlockAndWait
methods to execute Core Data operations within a block. This ensures that the context is used on its designated queue.
Example usages
// Create a background queue context
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
backgroundContext.parent = mainQueueContext
// Perform a fetch operation on the background context
backgroundContext.perform {
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "EntityName")
// Configure fetch request
do {
let results = try backgroundContext.fetch(fetchRequest)
// Process fetched objects
} catch {
// Handle fetch error
}
// Make changes to the fetched objects
// Save changes to the background context
do {
try backgroundContext.save()
} catch {
// Handle save error
}
// Propagate changes to the main queue context
mainQueueContext.perform {
do {
try mainQueueContext.save()
} catch {
// Handle save error on the main queue
}
}
}
By following these guidelines, you can ensure thread safety and avoid concurrency issues when working with Core Data in a multithreaded environment.
What is anchor layout in ios?
In iOS development, the anchor layout system is used to create user interfaces programmatically using Auto Layout. Auto Layout is a powerful constraint-based layout system that allows you to build adaptive and flexible user interfaces.
Anchors are objects that represent layout attributes, such as top, bottom, leading, trailing edges, and centerX or centerY positions of a view. They provide a convenient and expressive way to define the relationships between different UI elements.
Here’s an example of how you can use an anchor layout in iOS:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let redView = UIView()
redView.translatesAutoresizingMaskIntoConstraints = false
redView.backgroundColor = .red
view.addSubview(redView)
// Using anchors to define constraints
NSLayoutConstraint.activate([
redView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
redView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
redView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
redView.heightAnchor.constraint(equalToConstant: 100)
])
}
}
In the example above, we create a redView
and add it as a subview of the main view
. By setting translatesAutoresizingMaskIntoConstraints
to false
, we enable Auto Layout for the redView
. We then use anchor properties like topAnchor
, leadingAnchor
, trailingAnchor
, and heightAnchor
to define the constraints for the redView
.
By specifying the relationships between the view and its superview, we ensure that the redView
is positioned with a top spacing of 20 points, leading spacing of 20 points, trailing spacing of -20 points, and a height of 100 points.
Anchor layout provides a flexible and powerful way to create dynamic user interfaces that adapt to different screen sizes and orientations. You can use anchors to define constraints between different views as well, allowing you to create complex and responsive layouts.