├── CONTRIBUTING.md ├── .gitignore └── README.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to iOS Code Standards 2 | 3 | Thank you for your interest in contributing to this repository! Please follow these steps to propose changes: 4 | 5 | 1. **Fork the repository**: 6 | - Click the "Fork" button in the top-right corner of this repository to create your copy. 7 | 8 | 2. **Clone your fork**: 9 | - Clone your forked repository to your local machine: 10 | ```bash 11 | git clone https://github.com/shobhakartiwari/iOS_Lead_Interview 12 | cd iOS_Lead_Interview 13 | ``` 14 | 15 | 3. **Create a new branch**: 16 | - Create a branch for your changes: 17 | ```bash 18 | git checkout -b feature/your-feature-name 19 | ``` 20 | 21 | 4. **Make changes and commit**: 22 | - Add your changes and commit them with a descriptive message: 23 | ```bash 24 | git add . 25 | git commit -m "Describe your changes" 26 | ``` 27 | 28 | 5. **Push your changes**: 29 | - Push the changes to your fork: 30 | ```bash 31 | git push origin feature/your-feature-name 32 | ``` 33 | 34 | 6. **Submit a pull request**: 35 | - Go to your forked repository on GitHub. 36 | - Click **Compare & pull request**. 37 | - Add a title and description for your PR, then submit it for review. 38 | 39 | Thank you for contributing! 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## Obj-C/Swift specific 9 | *.hmap 10 | 11 | ## App packaging 12 | *.ipa 13 | *.dSYM.zip 14 | *.dSYM 15 | 16 | ## Playgrounds 17 | timeline.xctimeline 18 | playground.xcworkspace 19 | 20 | # Swift Package Manager 21 | # 22 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 23 | # Packages/ 24 | # Package.pins 25 | # Package.resolved 26 | # *.xcodeproj 27 | # 28 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 29 | # hence it is not needed unless you have added a package configuration file to your project 30 | # .swiftpm 31 | 32 | .build/ 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 39 | # 40 | # Pods/ 41 | # 42 | # Add this line if you want to avoid checking in source code from the Xcode workspace 43 | # *.xcworkspace 44 | 45 | # Carthage 46 | # 47 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 48 | # Carthage/Checkouts 49 | 50 | Carthage/Build/ 51 | 52 | # fastlane 53 | # 54 | # It is recommended to not store the screenshots in the git repo. 55 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 56 | # For more information about the recommended setup visit: 57 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 58 | 59 | fastlane/report.xml 60 | fastlane/Preview.html 61 | fastlane/screenshots/**/*.png 62 | fastlane/test_output 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## iOS Lead Interview Questions 2 | > Your Cheat Sheet For iOS Interview 3 | 4 | ## Prepared & maintained by [Shobhakar Tiwari](https://github.com/shobhakartiwari) 5 | 6 | ![ïOS MOCK zip - 1](https://github.com/user-attachments/assets/0bacf4e8-0655-4227-a591-9f07651bed01) 7 | 8 | - 🔗 Feel free to explore my repositories to get a taste of my work, and don't hesitate to get in touch if you have any questions or collaboration ideas. Happy coding! 🎉 9 | 10 | 11 | 12 | ## 1. **What could be the output of the following code?** 13 | 14 | ```swift 15 | DispatchQueue.main.async { 16 | print(Thread.isMainThread) 17 | DispatchQueue.main.async { 18 | print(Thread.isMainThread) 19 | } 20 | } 21 | 22 | ``` 23 | **Expected Output:** 24 | 25 | true 26 | true 27 | 28 | 29 | ## Explanation: 30 |
The outer DispatchQueue.main.async block is queued to run on the main queue.
31 |
When this block executes, it will:
32 |
a. Print the result of Thread.isMainThread
33 |
b. Queue another block to run on the main queue
34 |
The inner DispatchQueue.main.async block will then be executed, printing the result of Thread.isMainThread again.
35 | 36 | 37 | ## 2. **What is a dispatch barrier, and how can it be used in Swift?** 38 | 39 | A dispatch barrier is a mechanism in GCD (Grand Central Dispatch) used to ensure that a specific task in a concurrent queue is executed in isolation. It guarantees that the task runs exclusively, while other tasks in the queue are suspended, making it useful for thread-safe data access in concurrent environments. 40 | 41 | Below is an example that demonstrates the use of `DispatchQueue` with a **dispatch barrier**: 42 | 43 | ```swift 44 | import Foundation 45 | 46 | // A concurrent queue 47 | let queue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent) 48 | 49 | var sharedResource = [String]() 50 | 51 | // Adding elements concurrently 52 | for i in 1...5 { 53 | queue.async { 54 | print("Reading task \(i) - Shared resource: \(sharedResource)") 55 | } 56 | } 57 | 58 | // Writing to the shared resource using a barrier 59 | queue.async(flags: .barrier) { 60 | sharedResource.append("New Data") 61 | print("Writing task: Added new data") 62 | } 63 | 64 | // Continue with reading tasks after the barrier 65 | for i in 6...10 { 66 | queue.async { 67 | print("Reading task \(i) - Shared resource: \(sharedResource)") 68 | } 69 | } 70 | 71 | // Add a delay to ensure the program doesn't terminate immediately. 72 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 73 | print("Final Shared Resource: \(sharedResource)") 74 | } 75 | ``` 76 | 77 | **Expected Output:** 78 | 79 | Reading task 1 - Shared resource: [] 80 | Reading task 2 - Shared resource: [] 81 | Reading task 3 - Shared resource: [] 82 | Reading task 4 - Shared resource: [] 83 | Reading task 5 - Shared resource: [] 84 | Writing task: Added new data 85 | Reading task 6 - Shared resource: ["New Data"] 86 | Reading task 7 - Shared resource: ["New Data"] 87 | Reading task 8 - Shared resource: ["New Data"] 88 | Reading task 9 - Shared resource: ["New Data"] 89 | Reading task 10 - Shared resource: ["New Data"] 90 | Final Shared Resource: ["New Data"] 91 | 92 | The barrier ensures that no reading task occurs while the shared resource is being modified. 93 | 94 | 95 | ## 3. Remove Duplicates from Sorted Linked List 96 | 97 | **Problem Description:** 98 | Given the head of a sorted linked list, delete all duplicates such that each element appears only once. Return the linked list sorted as well. 99 | 100 | ## Solution: 101 | 102 | ```swift 103 | class SinglyLinkedList { 104 | var val: Int 105 | var next: SinglyLinkedList? 106 | 107 | init() { 108 | self.val = 0 109 | self.next = nil 110 | } 111 | 112 | init(_ val: Int) { 113 | self.val = val 114 | self.next = nil 115 | } 116 | 117 | init(_ val: Int, _ next: SinglyLinkedList?) { 118 | self.val = val 119 | self.next = next 120 | } 121 | } 122 | 123 | class Solution { 124 | func deleteDuplicates(_ head: SinglyLinkedList?) -> SinglyLinkedList? { 125 | guard head != nil, head?.next != nil else { 126 | return head 127 | } 128 | 129 | var current = head 130 | 131 | while current?.next != nil { 132 | if current!.val == current!.next!.val { 133 | current!.next = current!.next!.next 134 | } else { 135 | current = current!.next 136 | } 137 | } 138 | 139 | return head 140 | } 141 | } 142 | ``` 143 | 144 | ## Expected Output: 145 |
Node value: 1 146 |
Node value: 2 147 |
Node value: 3 148 | 149 | ## 4.What do you think the memory addresses of array1 and array2 will be? 150 | - If they are the same, why? When will it change? 151 | - If they are different, why? 152 | 153 | ```swift 154 | var array1 = [1, 2, 3] 155 | var array2 = array1 156 | ``` 157 | 158 | ## Expected Output: 159 |
they are the same due to Swift's optimization technique called Copy-on-Write (CoW). 160 | 161 | 162 | ## 5.What will be the output when this code is executed? 163 | 164 | ```swift 165 | Consider the following code snippet: 166 | class MyClass { 167 | var myProperty: Int = 0 { 168 | willSet { 169 | print("Will set to \(newValue)") 170 | } 171 | didSet { 172 | print("Did set from \(oldValue) to \(myProperty)") 173 | } 174 | } 175 | init() { 176 | defer { myProperty = 1 } 177 | myProperty = 2 178 | } 179 | } 180 | let instance = MyClass() 181 | instance.myProperty = 3 182 | ``` 183 | 184 | ## Expected Output: 185 |
Will set to 3 186 |
Did set from 1 to 3 187 | - However, property observers get triggered only after the self-scope and do not get triggered during the initialisation. I tried validating it with Playground and got the output only after the object was created. 188 | 189 | ## 6. Create a dictionary where: The first key stores an array of integers. The second key holds an array of doubles. The third key contains an array of strings.Sort the arrays, but if you encounter a string array, throw an error: "Unsupported type: Sorting is not possible." 190 | 191 | ## Solution: 192 | ```swift 193 | let dict: [String: Any] = ["array1": [8, 2, 3, 5, 1], 194 | "array2": [3.0, 1.0, 2.0], 195 | "array3": ["d", "b", "a", "c"]] 196 | 197 | func sortArrayFor(dict: [String: Any]) throws { 198 | for (key, value) in dict { 199 | if let integerArray = value as? [Int] { 200 | let sortedIntArray = integerArray.sorted() 201 | print("\(key) sorted integer array: \(sortedIntArray)") 202 | } else if let doubleArray = value as? [Double] { 203 | let sortedDoubleArray = doubleArray.sorted() 204 | print("\(key) sorted double array: \(sortedDoubleArray)") 205 | } else if let stringArray = value as? [String] { 206 | throw StringArrayError(kind: .unsupportType) 207 | } 208 | } 209 | } 210 | 211 | struct StringArrayError: Error { 212 | enum ErrorKind { 213 | case unsupportType 214 | } 215 | let kind: ErrorKind 216 | } 217 | 218 | do { 219 | try sortArrayFor(dict: dict) 220 | } catch { 221 | print("\(error)") 222 | } 223 | 224 | ``` 225 | 226 | ## 8. Consider the following SwiftUI code snippet: 227 | 228 | ```swift 229 | import SwiftUI 230 | 231 | struct ContentView: View { 232 | @State private var count = 0 233 | 234 | var body: some View { 235 | VStack { 236 | Text("Count: $$count)") 237 | 238 | Button("Increment") { 239 | count += 1 240 | } 241 | 242 | Button("Reset") { 243 | count = 0 244 | } 245 | } 246 | } 247 | } 248 | ``` 249 | ## Options: 250 | 1. **The @State variable count cannot be modified inside the Button actions.** 251 | 2. **The Text view does not update when count changes.** 252 | 3. **There is no issue; the code works as intended.** 253 | 4. **The @State variable should be declared as let instead of var.** 254 | 255 | 256 | ## 9. ## Consider the following Combine code snippet: 257 | ```swift 258 | let publisher = PassthroughSubject() 259 | let subscription = publisher 260 | .map { $0 * 2 } 261 | .filter { $0 > 5 } 262 | .sink { print($0) } 263 | 264 | publisher.send(1) 265 | publisher.send(2) 266 | publisher.send(3) 267 | publisher.send(4) 268 | ``` 269 | ## Options? 270 | A). 6 , 8
271 | B). 2, 4, 6, 8
272 | C). 6, 8,12,16
273 | D). No output
274 | 275 | Feel free to share your thoughts or answers in the comments below! 👇 276 | 277 | ## 10. What is the output of the following code snippet? 278 | ```swift 279 | class SomeClass { 280 | var member: String? 281 | func setMember(member: String) { 282 | self.member = member 283 | } 284 | } 285 | var someClass: SomeClass? 286 | if someClass?.setMember(member: "Swift") != nil { 287 | print("Assigned") 288 | } else { 289 | print("Not Assigned") 290 | } 291 | ``` 292 | ## Choose the best option: 293 | #1. Assigned
294 | #2. Not Assigned
295 | #3. Compilation error
296 | #4. None of the given options
297 | 298 | ## 11. What will the LazyVGrid display when the following code is executed? 299 | 300 | ```swift 301 | struct ContentView: View { 302 | let items = Array(1...6).map { "Item \($0)" } 303 | 304 | let columns = [ 305 | GridItem(.fixed(100)), 306 | GridItem(.flexible()), 307 | GridItem(.fixed(100)) 308 | ] 309 | 310 | var body: some View { 311 | LazyVGrid(columns: columns) { 312 | ForEach(items, id: \.self) { item in 313 | Text(item) 314 | .padding() 315 | .background(Color.blue) 316 | } 317 | } 318 | .padding() 319 | } 320 | } 321 | ``` 322 | ## Choose the option: 323 | #1. 6 items displayed in 2 rows with uneven column widths
324 | #2. 6 items displayed in 1 column
325 | #3. 6 items displayed in 3 rows with equal column widths
326 | #4. 6 items displayed in 2 rows with equal column widths
327 | 328 | ## 12. What is the main difference between throw and throws in Swift? 329 | ## Choose the best answer: 330 | #1. throws is used to mark a function that can throw an error, while throw is used to actually throw an error within the function.
331 | #2. throw is used to catch errors, while throws is used to handle them outside the function.
332 | #3. throw is used to pass errors silently, and throws is used for logging errors.
333 | #4. throws is used to declare error types, while throw is used to mark them in code.
334 | 335 | 336 | ## 13. Condition 0 != 0 is true. How? 337 | 338 | ## Choose the best answer:
339 | #1. func checkEqual(value1:T, value2:T) -> Bool { return value1 == value2 }
340 | #2. extension Int { static func ==(lhs: Int, rhs: Int) -> Bool { return lhs == rhs } }
341 | #3. extension Int { static func !=(lhs: Int, rhs: Int) -> Bool { return lhs != rhs } }
342 | #4. extension Int { static func ==(lhs: Int, rhs: Int) -> Bool { return lhs == rhs } }
343 | 344 | 345 | ## 14. What will be the output of this code ? 346 | 347 | ```swift 348 | func doTest() { 349 | let otherQueue = DispatchQueue(label: "otherQueue") 350 | 351 | DispatchQueue.main.async { 352 | print("\(Thread.isMainThread)") // Prints whether it's on the main thread or not 353 | otherQueue.sync { 354 | print("\(Thread.isMainThread)") // Prints whether it's on the main thread or not 355 | } 356 | otherQueue.async { 357 | print("\(Thread.isMainThread)") // Prints whether it's on the main thread or not 358 | } 359 | } 360 | } 361 | ``` 362 | ## Choose the best answer:
363 | #answer.true, true, false
364 | 365 | 366 | Reason: - 367 | 368 | it's common for developers to believe the .sync() will be executed in the otherQueue. In fact, to avoid unnecessary thread switch, .sync() will continue to execute on the calling thread. So in the code from the post, the closure provided to otherQueue.sync() will execute on the main thread. 369 | 370 |
DispatchQueue's .sync() method waits until a lock can be acquired on queue but doesn't actually change the running thread, whereas .async() will run the closure in the queue's thread once it has acquired the thread's lock. 371 | 372 |
Therefore the outcome is .) true, true, false 373 | 374 | ## 15. In Swift, how do you ensure thread-safe property access similar to the atomic keyword in Objective-C? 375 | 376 | ## Options:
377 | #1. Swift properties are atomic by default.
378 | #2. Swift uses a @synchronized attribute to make properties atomic.
379 | #3. Use a serial DispatchQueue or NSLock to manage access to shared properties.
380 | #4. Swift has an atomic keyword that needs to be added to property declarations.
381 | Answer : Option #3 382 | 383 | ## 16. What should be the output from this code : - 384 | ```swift 385 | unc testDispatchGroup() { 386 | let group = DispatchGroup ( ) 387 | group.enter () 388 | DispatchQueue.global().async { 389 | sleep(2) 390 | print("1") 391 | group.leave() 392 | } 393 | 394 | group.enter() 395 | DispatchQueue.global().async { 396 | sleep(1) 397 | print("2") 398 | group.leave( ) 399 | } 400 | 401 | group.notify( queue: .main) { 402 | print( "All tasks completed") 403 | print( "Tasks" ) 404 | } 405 | } 406 | 407 | ``` 408 | 409 | ## Output :
410 | 2
411 | 1
412 | All tasks completed
413 | Tasks
414 | 415 | ## Explanation :
416 | Both tasks will start almost simultaneously on global queues.
417 | After about 1 second, "2" will be printed (from Task 2).
418 | After about 2 seconds, "1" will be printed (from Task 1).
419 | Once both tasks have completed, the notification block will run on the main queue
420 | 421 | 422 | ## 17. What potential issues do you see with this code? How would you improve it? 423 | ```swift 424 | 425 | // iOS Interview Question #16 426 | func testThreadSafetyiniOSQuestion() { 427 | let group = DispatchGroup() 428 | var sharedResource = 0 429 | 430 | for _ in 1...1000 { 431 | group.enter() 432 | DispatchQueue.global().async { 433 | sharedResource += 1 434 | group.leave() 435 | } 436 | } 437 | 438 | group.notify(queue: .main) { 439 | print("Final value: \(sharedResource)") 440 | } 441 | } 442 | 443 | // function call 444 | testThreadSafetyiniOSQuestion() 445 | ``` 446 | ## Answer :
447 | 448 | ## Key Issues 449 | Race Condition: Multiple threads access and modify sharedResource without synchronization. 450 | Non-Atomic Operation: The += operation isn't atomic, leading to potential data races. 451 | Unpredictable Results: The final value of sharedResource is likely to be less than 1000 and may vary between runs. 452 | 453 | 454 | ## Solutions 1. Using Atomic Operations 455 | ```swift 456 | import Foundation 457 | 458 | class AtomicInteger { 459 | private var value: Int32 460 | private let lock = DispatchSemaphore(value: 1) 461 | 462 | init(value: Int32 = 0) { 463 | self.value = value 464 | } 465 | 466 | func increment() -> Int32 { 467 | lock.wait() 468 | defer { lock.signal() } 469 | value += 1 470 | return value 471 | } 472 | } 473 | 474 | func improvedThreadSafetyQuestion() { 475 | let group = DispatchGroup() 476 | let sharedResource = AtomicInteger() 477 | 478 | for _ in 1...1000 { 479 | group.enter() 480 | DispatchQueue.global().async { 481 | _ = sharedResource.increment() 482 | group.leave() 483 | } 484 | } 485 | 486 | group.notify(queue: .main) { 487 | print("Final value: $$sharedResource.increment())") 488 | } 489 | } 490 | ``` 491 | 492 | ## Solutions 2. Using Actor (Swift 5.5+) 493 | ```swift 494 | actor SharedResource { 495 | private(set) var value = 0 496 | 497 | func increment() -> Int { 498 | value += 1 499 | return value 500 | } 501 | } 502 | 503 | func actorBasedSolution() async { 504 | let resource = SharedResource() 505 | await withTaskGroup(of: Void.self) { group in 506 | for _ in 1...1000 { 507 | group.addTask { 508 | await resource.increment() 509 | } 510 | } 511 | } 512 | print("Final value: $$await resource.value)") 513 | } 514 | ``` 515 | 516 | ## Solutions 3.Using Serial Queue 517 | ```swift 518 | func serialQueueSolution() { 519 | let group = DispatchGroup() 520 | var sharedResource = 0 521 | let serialQueue = DispatchQueue(label: "com.example.serialQueue") 522 | 523 | for _ in 1...1000 { 524 | group.enter() 525 | serialQueue.async { 526 | sharedResource += 1 527 | group.leave() 528 | } 529 | } 530 | 531 | group.notify(queue: .main) { 532 | print("Final value: $$sharedResource)") 533 | } 534 | } 535 | ``` 536 | 537 | 538 | ## 18. Find the output of this: 539 | ```swift 540 | func createCounter () -> () -> Void { 541 | var counter = 0 542 | func incrementCounter () { 543 | counter += 1 544 | print (counter) 545 | } 546 | return incrementCounter 547 | } 548 | 549 | let closure = createCounter() 550 | closure() 551 | closure() 552 | closure() 553 | ``` 554 | 555 | ## 19. Analyze this class for race conditions. How would you fix this using Swift concurrency tools! 556 | ```swift 557 | ///#iOS Onsite #Interview Series - Question #19 558 | class DataCache { 559 | private var cache: [String: Data] = [:] 560 | 561 | func setData(for key: String, data: Data) { 562 | cache[key] = data 563 | } 564 | 565 | func getData(for key: String) -> Data? { 566 | return cache[key] 567 | } 568 | } 569 | ``` 570 | 571 | ## Explanation :: 572 | 573 | - Race conditions: When two or more threads try to modify the same data concurrently, unpredictable results may occur. This needs careful synchronization. 574 | - DispatchQueue: Use serial queues or DispatchQueue barriers to manage thread safety without locks. 575 | - NSLock: This is another option for mutual exclusion, but it can introduce deadlocks if not used carefully. 576 | - Atomic properties: Can help ensure thread safety but are more complex to implement in Swift directly. 577 | - Serial queue vs. Barrier: Serial queues ensure that only one task runs at a time. Barriers, when used on concurrent queues, block other tasks while the barrier task runs. 578 | 579 | ## solution: 1. OSAllocatedUnfairLock 580 | To use a low-level lock like OSAllocatedUnfairLock to synchronize access to the cache. This method will block any other thread from accessing the cache while a write or read operation is in progress, preventing race conditions while maintaining high performance. 581 | ```swift 582 | import os.lock 583 | 584 | class DataCache { 585 | private var cache: [String: Data] = [:] 586 | private var lock = OSAllocatedUnfairLock() 587 | 588 | func setData(for key: String, data: Data) { 589 | lock.lock() // Acquire the lock 590 | cache[key] = data 591 | lock.unlock() // Release the lock 592 | } 593 | 594 | func getData(for key: String) -> Data? { 595 | lock.lock() // Acquire the lock 596 | let data = cache[key] 597 | lock.unlock() // Release the lock 598 | return data 599 | } 600 | } 601 | 602 | ## Pros :: 603 | - Low-overhead locking mechanism. 604 | - Fast performance for high-concurrency scenarios. 605 | - Maintains the same synchronous function signatures, making it easy to integrate into existing codebases. 606 | ## Cons: 607 | - More prone to human error (e.g., forgetting to release the lock). 608 | - Can introduce deadlocks if not handled carefully. 609 | ``` 610 | 611 | >- Why to choose this ? 612 | >- It’s simple to use, relatively lightweight and performs well for many simple cases such as protecting a resource. It saves you from having to change all the existing calling code, were you to use an actor instead. Also, actors are not « free » either with respect to context switching. 613 | In turn, Apple has guidance to avoid using Mutex and various other thread locking mechanisms unless you absolutely need them (which is very unlikely) when making use of concurrency. 614 | >- [Credit: Thanks to Michael Long & Patrick D for providing these inputs] 615 | 616 | ## Solution 2. NSLOCK 617 | ```swift 618 | import Foundation 619 | 620 | class DataCache { 621 | private var cache: [String: Data] = [:] 622 | private let lock = NSLock() 623 | 624 | func setData(for key: String, data: Data) { 625 | lock.lock() 626 | defer { lock.unlock() } 627 | cache[key] = data 628 | } 629 | 630 | func getData(for key: String) -> Data? { 631 | lock.lock() 632 | defer { lock.unlock() } 633 | return cache[key] 634 | } 635 | } 636 | 637 | let cache = DataCache() 638 | let group = DispatchGroup() 639 | 640 | // Writing to cache 641 | group.enter() 642 | DispatchQueue.global().async { 643 | for i in 0..<100 { 644 | let data = "Data \(i)".data(using: .utf8)! 645 | cache.setData(for: "key1", data: data) 646 | Thread.sleep(forTimeInterval: 0.01) // Small delay to allow reading 647 | } 648 | group.leave() 649 | } 650 | 651 | // Reading from cache 652 | group.enter() 653 | DispatchQueue.global().async { 654 | for _ in 0..<100 { 655 | if let data = cache.getData(for: "key1"), 656 | let stringData = String(data: data, encoding: .utf8) { 657 | print("Read data: \(stringData)") 658 | } 659 | Thread.sleep(forTimeInterval: 0.01) // Small delay to allow writing 660 | } 661 | group.leave() 662 | } 663 | 664 | // Wait for both operations to complete 665 | group.wait() 666 | print("All operations completed") 667 | ``` 668 | 669 | ## Solution 3. actor 670 | ```swift 671 | ## Pros: 672 | 673 | - Simplifies thread-safe code, as Swift automatically handles access to cache. 674 | - No risk of deadlocks or race conditions. 675 | 676 | ## Cons: 677 | - Changes the API to asynchronous, which could introduce complexity at the call site (requiring await). 678 | ``` 679 | 680 | ## Solution 4. Dispatch Barriers 681 | ```swift 682 | class DataCache { 683 | private var cache: [String: T] = [:] 684 | private let queue = DispatchQueue(label: "com.datacache.queue", attributes: .concurrent) 685 | 686 | // Set data (write) using a barrier to ensure exclusive access 687 | func setData(for key: String, data: T) { 688 | queue.async(flags: .barrier) { // The barrier flag ensures exclusive write access 689 | self.cache[key] = data 690 | } 691 | } 692 | 693 | // Get data (read) allowing concurrent reads 694 | func getData(for key: String) -> T? { 695 | return queue.sync { // Sync call to ensure thread safety 696 | return self.cache[key] 697 | } 698 | } 699 | } 700 | 701 | ``` 702 | - Why Use Dispatch Barrier? 703 | - Efficiency: It allows for concurrent reads, which is highly efficient, especially when your application reads more than it writes. 704 | - Exclusive Writes: The barrier ensures that only one write operation occurs at a time, making writes safe and preventing data races. 705 | ## Pros: 706 | - Optimized for Reads: Since reads don’t block each other, this approach is highly performant when you have more frequent reads than writes. 707 | - Simple and Effective: It provides simple, easy-to-use concurrency control without changing function signatures to async, which maintains ease of use in synchronous contexts. 708 | ## Cons: 709 | - Potential Write Delays: If there are many read operations, writes can potentially be delayed, as they must wait for the queue to be free. 710 | 711 | ## Explanation:: 712 | - Uses defer to ensure the lock is always unlocked. 713 | - Adds small delays to allow interleaving of read and write operations. 714 | - Uses a DispatchGroup to ensure the program doesn't exit before operations complete. 715 | - Prints a message when all operations are done. 716 | 717 | ## 20. Scenario: Refactor the UserPreferences class to support multiple storage methods including UserDefaults, Plist. 718 | 719 | - You're working with a UserPreferences singleton in an iOS app that currently uses UserDefaults for storing settings. The app needs the flexibility to switch between different storage methods like UserDefaults, Plist. How would you refactor this to allow easy switching between storage options? 720 | 721 | ```swift 722 | /// 🚀 #iOS Lead Onsite #Interview Series - Question #20 🚀 723 | class UserPreferences { 724 | static let shared = UserPreferences() 725 | 726 | private let userDefaults = UserDefaults.standard 727 | 728 | private init() {} 729 | 730 | // Method to set a value for a given key 731 | func setValue(_ value: String, forKey key: String) { 732 | userDefaults.set(value, forKey: key) 733 | } 734 | 735 | // Method to get a value for a given key 736 | func getValue(forKey key: String) -> String? { 737 | return userDefaults.string(forKey: key) 738 | } 739 | } 740 | 741 | // Example usage 742 | let preferences = UserPreferences.shared 743 | preferences.setValue("Shobhakar", forKey: "username") 744 | 745 | if let username = preferences.getValue(forKey: "username") { 746 | print("Retrieved username: \(username)") // Output: Retrieved username: Shobhakar 747 | } 748 | ``` 749 | ## Answer :: 750 | ```Swift 751 | import Foundation 752 | import CoreData 753 | 754 | enum StorageMethod { 755 | case userDefaults 756 | case plist 757 | case coreData 758 | } 759 | 760 | class UserPreferences { 761 | static let shared = UserPreferences() 762 | 763 | private var storageMethod: StorageMethod = .userDefaults 764 | 765 | private init() {} 766 | 767 | // Method to set the storage method 768 | func setStorageMethod(_ method: StorageMethod) { 769 | storageMethod = method 770 | } 771 | 772 | // Set value based on the current storage method 773 | func setValue(_ value: String, forKey key: String) { 774 | switch storageMethod { 775 | case .userDefaults: 776 | UserDefaults.standard.set(value, forKey: key) 777 | case .plist: 778 | saveToPlist(value: value, forKey: key) 779 | case .coreData: 780 | saveToCoreData(value: value, forKey: key) 781 | } 782 | } 783 | 784 | // Get value based on the current storage method 785 | func getValue(forKey key: String) -> String? { 786 | switch storageMethod { 787 | case .userDefaults: 788 | return UserDefaults.standard.string(forKey: key) 789 | case .plist: 790 | return loadFromPlist(forKey: key) 791 | case .coreData: 792 | return loadFromCoreData(forKey: key) 793 | } 794 | } 795 | 796 | // PList methods 797 | private func saveToPlist(value: String, forKey key: String) { 798 | let filePath = getPlistPath() 799 | var plistDict = NSDictionary(contentsOf: filePath) as? [String: String] ?? [:] 800 | plistDict[key] = value 801 | (plistDict as NSDictionary).write(to: filePath, atomically: true) 802 | } 803 | 804 | private func loadFromPlist(forKey key: String) -> String? { 805 | let filePath = getPlistPath() 806 | let plistDict = NSDictionary(contentsOf: filePath) as? [String: String] 807 | return plistDict?[key] 808 | } 809 | 810 | private func getPlistPath() -> URL { 811 | let fileManager = FileManager.default 812 | let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask) 813 | return urls[0].appendingPathComponent("UserPreferences.plist") 814 | } 815 | 816 | // Core Data methods 817 | private func saveToCoreData(value: String, forKey key: String) { 818 | // Assuming CoreData setup is done 819 | let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 820 | let newPreference = UserPreferenceEntity(context: context) 821 | newPreference.key = key 822 | newPreference.value = value 823 | do { 824 | try context.save() 825 | } catch { 826 | print("Failed to save to Core Data: \(error)") 827 | } 828 | } 829 | 830 | private func loadFromCoreData(forKey key: String) -> String? { 831 | let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 832 | let request: NSFetchRequest = UserPreferenceEntity.fetchRequest() 833 | request.predicate = NSPredicate(format: "key == %@", key) 834 | 835 | do { 836 | let results = try context.fetch(request) 837 | return results.first?.value 838 | } catch { 839 | print("Failed to load from Core Data: \(error)") 840 | return nil 841 | } 842 | } 843 | } 844 | 845 | // Example usage 846 | let preferences = UserPreferences.shared 847 | preferences.setStorageMethod(.plist) // Switch to Plist storage 848 | preferences.setValue("Shobhakar", forKey: "username") 849 | 850 | if let username = preferences.getValue(forKey: "username") { 851 | print("Retrieved username: \(username)") // Output: Retrieved username: Shobhakar 852 | } 853 | ``` 854 | 855 | 856 | ## 21. Are lazy vars computed more than once in Swift? 857 | ## Explanation:: 858 | - Lazy vars are not thread safe by default, meaning if a lazy property is not yet initialized & multiple threads try to access it, there is a possiblility that it will be initialized more than once. 859 | 860 | ## 22. Structs Inside Classes and Memory Management in Swift? 861 | - In Swift, structs are value types and are generally stored on the stack, while classes are reference types and are stored on the heap. If you have a struct inside a class, where is the memory for the struct allocated, and why? 862 | 863 | ## Explanation:: 864 | - In Swift, the memory allocation for value types and reference types depends on their context. 865 | 866 | - Structs are value types and are generally stored on the stack when used alone or in isolation. 867 | Classes are reference types and are stored on the heap. 868 | Now, if you have a struct inside a class, the memory for that struct is allocated on the heap, along with the memory for the class instance. This happens because when you create an instance of a class, all of its properties (whether value types or reference types) are stored as part of the class’s memory block on the heap. The class instance holds a reference to its memory location on the heap, and all its properties—including the struct—are stored in that same memory. 869 | 870 | - Why is it stored on the heap? 871 | When a value type (such as a struct) is a property of a reference type (such as a class), it becomes part of the class's internal data. Since the class is stored on the heap, all of its contents, including value types, are stored on the heap to ensure they remain alive as long as the class instance exists. In this case, the struct is "lifted" to the heap with the class for proper memory management. 872 | 873 | ## 23. What is Smart MVVM? 874 | Smart MVVM is an enhanced version of the traditional MVVM (Model-View-ViewModel) architectural pattern specifically designed for iOS applications. It aims to improve the separation of concerns, enhance maintainability, and increase the efficiency of data binding between the UI and business logic. Smart MVVM integrates reactive programming concepts, dependency injection, and asynchronous data handling to create a more robust development experience. 875 | 876 | ### Key Differences from Traditional MVVM: 877 | 878 | 1. **Reactive Programming**: 879 | - Smart MVVM often utilizes reactive frameworks like Combine or RxSwift to facilitate automatic updates in the UI based on changes in the ViewModel. This minimizes boilerplate code and improves responsiveness. 880 | 881 | 2. **Dependency Injection**: 882 | - In Smart MVVM, dependency injection is used to decouple components, making the ViewModel more testable and maintainable. It allows for easy swapping of implementations, which is beneficial for unit testing. 883 | 884 | 3. **Asynchronous Data Handling**: 885 | - Smart MVVM embraces async/await patterns for handling asynchronous operations such as network requests. This leads to cleaner, more readable code and better error handling. 886 | 887 | ### Benefits of Using Smart MVVM: 888 | 889 | - **Clean Architecture**: Enforces clear separation of responsibilities, making the codebase easier to manage and extend. 890 | - **Enhanced Testability**: The use of DI and decoupled components allows for easier unit testing of the ViewModel. 891 | - **Responsive UI**: Reactive programming ensures that the UI remains in sync with the underlying data model seamlessly. 892 | 893 | ## 24. When displaying images in a UITableView, how would you approach caching those images to improve performance? What factors would you consider in deciding how much data to store in the cache? 894 | - To improve performance when displaying images in a UITableView, I would implement a caching strategy that includes both in-memory and on-disk caching. 895 | 896 | ### In-Memory Caching: 897 | - I would use an in-memory cache to store recently accessed images. This allows for quick retrieval as the user scrolls, improving the overall responsiveness of the app. Libraries like SDWebImage or Kingfisher can be helpful because they have built-in memory caching mechanisms. 898 | On-Disk Caching: 899 | 900 | - In addition to in-memory caching, I would implement on-disk caching to store images for offline access and reduce redundant network requests. This is especially useful for images that are not frequently updated. 901 | 902 | ### Determining Cache Size: 903 | - To determine how much data to store in the cache, I would consider the following factors: 904 | - Available Memory: Monitor the app’s memory usage to set a reasonable limit for the in-memory cache, typically around 10-20% of available memory. 905 | - Image Size and Frequency of Access: Analyze the average size of images and their access patterns. Frequently accessed images can be prioritized for caching. 906 | - Cache Eviction Policy: Implement an eviction policy, such as Least Recently Used (LRU), to remove the least accessed images from the cache when the size - - limit is reached. 907 | 908 | ### Cache Invalidation: 909 | - Lastly, I would implement strategies for cache invalidation, such as checking for updates on the server or allowing users to refresh images manually to ensure that they see the most current data. 910 | - By combining these strategies, I can optimize image loading in the UITableView while managing memory effectively and enhancing user experience. 911 | 912 | ## 25. If an iOS app is suspended, will it still receive push notifications? If yes, will the push notification delegate method execute when the notification is received? 913 | - Answer: Yes, an iOS app can receive push notifications even when it’s suspended. In iOS, push notifications are managed by the system rather than the app itself. When a push notification arrives for a suspended app, the system displays the notification in the notification center. 914 | - However, the delegate method (userNotificationCenter(_:didReceive:withCompletionHandler:)) will not execute immediately when the app is in the background or suspended. Instead, the method is called only if: 915 | - The user taps the notification, which launches or brings the app to the foreground. 916 | - The notification is configured with a "content-available" key, allowing for silent pushes that might wake the app in the background (if permitted). 917 | To summarize: 918 | - Yes, the app can receive push notifications while suspended. 919 | - No, the delegate method won’t execute unless the user interacts with the notification or the notification is configured for background updates. 920 | 921 | ## 26. Apple push notifications Interview Questions ( i have created this supporting till iOS 13 , will update this shortly) 922 | - Download complete flow diagram along with interview questions from this link : https://github.com/shobhakartiwari/Push-Notifications-Interview-guide.git 923 | 924 | ## 27. You need to migrate a Core Data model that has significant changes, including adding new entities, relationships, and attributes. Which of the following approaches is most appropriate for handling this migration while preserving existing data? 925 | - 1. Use Lightweight Migration by setting NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to true in your persistent store options, as Core Data will infer the mapping model for complex changes. 926 | 927 | - 2. Create a Custom Migration Mapping Model with a custom mapping model file to define transformation logic and manually handle complex data migrations between old and new versions. 928 | 929 | - 3. Remove the old data store and start fresh with a new Core Data stack, letting users start with clean data in the updated model. 930 | 931 | - 4. Enable Automatic Schema Updating by modifying the Core Data model directly in place without versioning, as Core Data will automatically adjust to new attributes and relationships. 932 | ## Answer: 933 | - The correct answer is B.Lightweight Migration (Option A) only supports simple changes like adding attributes or entities without significant restructuring. For complex changes, creating a custom mapping model (Option B) is necessary, where developers can explicitly define how data should transform and map between versions, ensuring data integrity through the migration. 934 | 935 | ## 28. Given the code snippet below, which aims to create an array of tuples containing the index and value of each element in an array, rewrite it in a more "Swifty" and efficient way. 936 | ```swift 937 | let list = [Int](1...5) 938 | var arrayOfTuples = [(Int, Int)]() 939 | 940 | for (index, element) in list.enumerated() { 941 | arrayOfTuples += [(index, element)] 942 | } 943 | 944 | print(arrayOfTuples) // Expected output: [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)] 945 | 946 | ``` 947 | ### Answer: - 948 | ```swift 949 | let list = [Int](1...5) 950 | let arrayOfTuples = Array(list.enumerated()) 951 | print(arrayOfTuples) // Expected output: [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)] 952 | ``` 953 | ## 29. Question: Can you discuss a specific instance where you used Dependency Injection to solve a problem in an iOS project? 954 | - In one of my iOS projects, we needed a modular and testable way to handle network requests across multiple view controllers. Initially, network dependencies were tightly coupled, making it hard to mock data for unit testing. To solve this, I introduced Dependency Injection by designing a protocol, NetworkServiceProtocol, with required networking methods and then created a NetworkService class that conformed to this protocol. 955 | 956 | - Using DI, I passed NetworkServiceProtocol instances into the view controllers rather than directly instantiating them. This allowed us to inject mock network services during testing, making it much easier to validate different network responses without calling actual endpoints. This approach reduced code duplication, improved testability, and significantly lowered maintenance costs as we scaled the project. 957 | 958 | ## 30. Given an array of integers with duplicate elements (which may be sorted or unsorted), how would you filter out the unique elements? 🤔 959 | The challenge here isn’t just to solve the problem — but to solve it efficiently. 960 | ```swift 961 | /// Approach 1: Without Using a Set 962 | func removeDuplicatesWithoutSet(from array: [Int]) -> [Int] { 963 | var seen: [Int] = [] 964 | return array.filter { element in 965 | if seen.contains(element) { 966 | return false 967 | } else { 968 | seen.append(element) 969 | return true 970 | } 971 | } 972 | } 973 | 974 | /// Approach 2: Using a Set for Faster Lookups 975 | func removeDuplicatesUsingSet(from array: [Int]) -> [Int] { 976 | var seen = Set() 977 | return array.filter { seen.insert($0).inserted } 978 | } 979 | ``` 980 | - Answer : Approach 2. Time Complexity: O(n) 🚀 This method achieves linear time complexity thanks to the O(1) average lookup time of the Set. It's the preferred approach for larger datasets! 981 | 982 | 983 | ### iOS Developer Interview: Scenario-Based Exploration 984 | 985 | ##31. Scenario: You’re assigned to improve the performance of a slow-running iOS app. How would you approach this task, and what tools or methods would you use to diagnose and fix the issues? 986 | - for answer follow my article over medium: https://medium.com/@shobhakartiwari/ios-developer-interview-scenario-based-exploration-c3c941a024ae 987 | 988 | ## 32. fint the output - 989 | ```swift 990 | let value = [1,2,3, 4,5,6,7,8,9] 991 | let result = value.lazy.filter{ value in 992 | value % 2 != 0 993 | }.map { value in 994 | value * value 995 | }.prefix (3) 996 | print (Array (result)) 997 | ``` 998 | - Answer : [1,9,25] 999 | 1000 | ## 33. How errors been handled in swift? 1001 | ### 1. throws Keyword 1002 | - Functions that might produce an error are marked with the throws keyword. Calling such functions requires the use of try, try?, or try!. 1003 | ```swift 1004 | func readFile(at path: String) throws -> String { 1005 | // Code to read a file, throwing an error if the file isn't found 1006 | } 1007 | ``` 1008 | ### 2. try, try?, and try! Explained 1009 | - try: Used with do-catch to handle errors explicitly. 1010 | - try?: Converts the result into an optional. Returns nil if an error occurs. 1011 | - try!: Forces the operation to execute. Crashes at runtime if an error is thrown. 1012 | ```swift 1013 | do { 1014 | let content = try readFile(at: "path/to/file") 1015 | print(content) 1016 | } catch { 1017 | print("Error: \(error)") 1018 | } 1019 | 1020 | // Optional error handling 1021 | let content = try? readFile(at: "path/to/file") 1022 | 1023 | // Force unwrapping (not recommended) 1024 | let content = try! readFile(at: "path/to/file") 1025 | ``` 1026 | ### 3. do-catch for Explicit Error Handling 1027 | - Use a do-catch block to handle errors thrown by a function. You can also pattern-match specific errors. 1028 | ```swift 1029 | do { 1030 | let content = try readFile(at: "path/to/file") 1031 | print("File content: \(content)") 1032 | } catch FileError.notFound { 1033 | print("File not found.") 1034 | } catch { 1035 | print("An unexpected error occurred: \(error).") 1036 | } 1037 | ``` 1038 | - you can also create your own custom error enum and throw it. 1039 | 1040 | ## 34. Difference between classes and structures. [ credit goes to K Motwani ] 1041 | Screenshot 2024-11-21 at 10 28 31 PM 1042 | 1043 | ## 35. What is a computed property? 1044 | - A computed property in Swift is a property that doesn’t store a value directly. Instead, it provides a getter (and optionally a setter) to retrieve or compute a value indirectly each time it’s accessed. 1045 | ```swift 1046 | struct Circle { 1047 | var radius: Double 1048 | 1049 | // Computed property is defined with var for diameter with a getter and setter 1050 | var diameter: Double { 1051 | get { 1052 | return radius * 2 1053 | } 1054 | set { 1055 | radius = newValue / 2 1056 | } 1057 | } 1058 | } 1059 | 1060 | var circle = Circle(radius: 5) 1061 | print(circle.diameter) // Output: 10 1062 | 1063 | circle.diameter = 20 // Sets `radius` to 10 1064 | print(circle.radius) // Output: 10 1065 | ``` 1066 | 1067 | ## 36. What are access specifiers? 1068 | Screenshot 2024-11-21 at 10 31 20 PM 1069 | 1070 | ## 37. Difference between GCD and Operation queues ? 1071 | Screenshot 2024-11-21 at 10 31 58 PM 1072 | 1073 | ## 38. Explain the lifecycle of a view controller in iOS. 1074 | - View controller’s lifecycle 1075 | init :- Initialize properties and objects within the view controller.
1076 | load view :- Manually create and assign the view if needed.
1077 | viewDidLoad :- Setup initial configurations and view setup.
1078 | ViewWillAppear :- Update views, refresh data, and make final layout changes.
1079 | ViewWillLayoutSubviews :- make adjustments to the layout of your views before they are displayed on the screen
1080 | ViewDidLayoutSubviews :- make adjustments to your views after their layout has been finalized
1081 | viewDidAppear :- Trigger animations or track view display events.
1082 | viewWillDisappear :- Save data or state changes, prepare for view to disappear.
1083 | viewDidDisappear :- Stop ongoing tasks that are unnecessary off-screen.
1084 | deinit :- Clean up resources before the view controller is removed.
1085 | didReceivememoryWarning() :- Release unused memory in low-memory situations.
1086 | viewWillTransition(to:with:) :- handle changes in the view’s layout when the device rotates or when the view’s size class changes (like switching between portrait and landscape modes , interface orientation of the device)
1087 | 1088 | ## 39. What is URLSession, and how do you use it to make network requests? 1089 | - URLSession is a class in Swift that provides an API for downloading, uploading, and managing network data. It is used for making network requests like GET, POST, PUT, etc and handling responses in iOS apps.It is part of the Foundation framework and can manage background downloads, API calls, file transfers, and more. 1090 | 1091 | ## 40. Difference between synchronous and asynchronous tasks in Swift? 1092 | Screenshot 2024-11-21 at 10 34 07 PM 1093 | 1094 | ## 41. How do you handle background tasks in iOS? 1095 | - Background tasks allow apps to continue executing code when they’re not actively in use. Handling background tasks, such as background fetch, data uploads, location updates, or processing that should continue for a limited time. 1096 | 1097 | ## 42. What is SwiftUI and its benefits? Difference between Swift UI and UI Kit? 1098 | - SwiftUI is a declarative framework introduced by Apple in 2019 to build user interfaces across all Apple platforms using a unified codebase. SwiftUI simplifies the UI development process, allowing developers to describe how the UI should look and behave in a declarative way, which can reduce the complexity of UI code and enable faster, more maintainable UI development. 1099 | - Benefits :- 1100 | Screenshot 2024-11-21 at 10 35 54 PM 1101 | 1102 | ## 43. Explain the difference between @State and @Binding in SwiftUI ? 1103 | Screenshot 2024-11-21 at 10 36 30 PM 1104 | 1105 | ## 44. What is @environment and @Published in SwiftUI ? 1106 | Screenshot 2024-11-21 at 10 36 57 PM 1107 | 1108 | ## 45. What is a Protocol and Where Do We Use It? [ credit goes to : Mihail Salari] 1109 | - A protocol defines a blueprint of methods, properties, and other requirements for a particular task. It’s like an interface in other programming languages. 1110 | - You can use protocols in various situations: 1111 | - Delegation: For passing data between objects. 1112 | - Data Modeling: When you need a consistent API for your models. 1113 | - Reusable Components: Protocols can make your components more flexible and reusable. 1114 | - In design patterns like Strategy, Observer, and Factory. 1115 | - For mocking in unit tests. 1116 | 1117 | ## 46. What is a Semaphore in Swift? 1118 | - A semaphore is a powerful synchronization tool that controls access to a resource by multiple threads. In Swift, you can use DispatchSemaphore to implement it. 1119 | ```swift 1120 | let semaphore = DispatchSemaphore(value: 1) 1121 | 1122 | DispatchQueue.global().async { 1123 | semaphore.wait() 1124 | /// Critical section of code 1125 | semaphore.signal() 1126 | } 1127 | ``` 1128 | ## 47. What is Parallel Programming in Swift? 1129 | - Parallel programming refers to performing multiple tasks simultaneously. In Swift, this is typically accomplished using DispatchQueue.concurrentPerform. 1130 | ```swift 1131 | DispatchQueue.concurrentPerform(iterations: 10) { index in 1132 | print("This is task \(index)") 1133 | } 1134 | ``` 1135 | ## 48. What is an Autorelease Pool and What is Its Main Reason? 1136 | - Autorelease pools store objects that are sent autorelease message. The main reason for using an autorelease pool is to control memory usage during the lifetime of an app, particularly within loops. 1137 | ```swift 1138 | autoreleasepool { 1139 | /// Your code that creates many temporary autoreleased objects 1140 | } 1141 | ``` 1142 | - Autorelease pools help in reducing the peak memory footprint, providing a more responsive user experience. 1143 | 1144 | ## 48. Can delegation be implemented without a protocol? If yes, then why do we need a Protocol? [ FAANG Asked Question] 1145 | - Yes, delegation can be implemented without a protocol in Swift, but using a protocol makes delegation more structured, reusable, and type-safe. Here's a breakdown of why you can do it without a protocol and why using a protocol is beneficial: 1146 | ``` swift 1147 | class Manager { 1148 | var delegate: AnyObject? // No protocol required 1149 | func performTask() { 1150 | (delegate as? Employee)?.executeTask() 1151 | } 1152 | } 1153 | 1154 | class Employee { 1155 | func executeTask() { 1156 | print("Task executed by Employee.") 1157 | } 1158 | } 1159 | 1160 | let manager = Manager() 1161 | let employee = Employee() 1162 | manager.delegate = employee // Assigning without a protocol 1163 | manager.performTask() // Output: Task executed by Employee. 1164 | ``` 1165 | ## 49. Why Do We Need Protocols for Delegation? 1166 | - While it’s possible to implement delegation without a protocol, using a protocol offers key advantages: 1167 | 1168 | - 1. Enforces a Contract 1169 | 1170 | - A protocol ensures that the delegate adheres to a defined contract by implementing required methods. This avoids runtime errors caused by missing or incorrectly implemented methods. 1171 | ```swift 1172 | protocol TaskDelegate { 1173 | func executeTask() 1174 | } 1175 | 1176 | class Manager { 1177 | var delegate: TaskDelegate? // Protocol-constrained delegate 1178 | func performTask() { 1179 | delegate?.executeTask() 1180 | } 1181 | } 1182 | 1183 | class Employee: TaskDelegate { 1184 | func executeTask() { 1185 | print("Task executed by Employee.") 1186 | } 1187 | } 1188 | 1189 | let manager = Manager() 1190 | let employee = Employee() 1191 | manager.delegate = employee 1192 | manager.performTask() // Output: Task executed by Employee. 1193 | 1194 | ``` 1195 | - Compile-Time Safety 1196 | - Without a protocol, you must cast the delegate to a specific type, which can fail at runtime. Protocols eliminate this risk by allowing the compiler to check conformance. 1197 | - manager.delegate = "InvalidDelegate" // Compile-time error if delegate must conform to `TaskDelegate`. 1198 | - Flexibility and Reusability 1199 | - Using a protocol makes the delegation pattern more flexible, allowing different types to conform to the protocol. This enables reusability across various classes or modules. 1200 | ```swift 1201 | class Freelancer: TaskDelegate { 1202 | func executeTask() { 1203 | print("Task executed by Freelancer.") 1204 | } 1205 | } 1206 | 1207 | let freelancer = Freelancer() 1208 | manager.delegate = freelancer 1209 | manager.performTask() // Output: Task executed by Freelancer. 1210 | ``` 1211 | - 4. Decoupling 1212 | - Protocols reduce tight coupling between the delegator and delegate classes. The delegator doesn't need to know the exact type of the delegate, only that it conforms to the protocol. 1213 | - 5. Scalability 1214 | - Protocols support default implementations via extensions, making it easier to scale functionality without modifying existing classes. 1215 | 1216 | ## 50. When to use , the Singleton pattern? 1217 | ### When you need to manage a shared resource, such as: 1218 | - A network manager for making API requests. 1219 | - A database connection or cache manager. 1220 | - A logging system to record events. 1221 | 1222 | ### Global State Management 1223 | - When maintaining global state, such as user session data or configuration settings, that should only exist once throughout the app lifecycle. 1224 | ### Centralized Coordination 1225 | - For managing cross-cutting concerns like: 1226 | - A theme manager for UI appearance. 1227 | - A notification manager to handle system-wide notifications. 1228 | ### Thread-Safe Lazy Initialization 1229 | - If creating the instance is expensive or requires coordination across threads, a Singleton provides a mechanism for thread-safe, lazy initialization. 1230 | 1231 | ## When not to use the singleton pattern? 1232 | - Tight Coupling (If classes depend directly on a Singleton, it creates tight coupling, making the code harder to test and maintain. This violates the Dependency Inversion Principle.) 1233 | - Global State Pollution ( Overusing Singletons for mutable shared states can lead to hard-to-debug issues, especially in concurrent or multi-threaded environments. 1234 | Example Problem: Two parts of the app modify the same Singleton state simultaneously, causing unintended side effects.) 1235 | - Testing and Mocking Challenges (Singletons make it difficult to test code in isolation since you cannot easily mock or replace their functionality. 1236 | Better Alternative: Use protocols and dependency injection to allow mock implementations during tests.) 1237 | - Overhead for Simple Scenarios (If the same functionality can be achieved with simpler patterns, avoid the Singleton for unnecessary complexity. For instance, static methods or structs may suffice.) 1238 | -Hidden Dependencies ( Singletons introduce hidden dependencies that are not immediately apparent from the API of a class or module, making the code less readable and harder to refactor.) 1239 | 1240 | ## 51. Why are IBOutlets weak by default? What happens when we make them strong? Is it even a problem? 1241 | - IBOutlets are weak by default to avoid retain cycles since the view hierarchy already holds strong references to its subviews. Making them strong can cause memory leaks if the view controller or its views are not properly deallocated. 1242 | 1243 | ## 52. How do cocoapods work? 1244 | - CocoaPods is a dependency manager for iOS that automates adding third-party libraries to projects. You define dependencies in a Podfile, run pod install to download them, and use the generated .xcworkspace to manage your app alongside the libraries. It handles downloading, linking, and versioning efficiently. 1245 | 1246 | ## 53. Why did Apple chose to go with structures to implement primitive types in Swift? 1247 | - Apple chose structures for primitive types in Swift due to their value semantics, which ensure safety, thread safety, and simplicity. Structures are optimized for performance, use less memory (stack allocation), and avoid issues like shared mutable state, making them ideal for Swift’s design goals. 1248 | 1249 | ## 54. What is @Frozen keyword in swift ? 1250 | - follow my medium article on this: https://medium.com/@shobhakartiwari/understanding-the-frozen-attribute-in-swift-a-guide-for-developers-bbb98cf8c235 1251 | 1252 | ## 55. What are ‘@dynamicCallable’ and ‘dynamic member lookup’ in Swift? 1253 | - @dynamicCallable allows an object to be called like a function, with dynamic arguments. You define a method to handle the call, like dynamicallyCall(withArguments:) 1254 | ```swift 1255 | @dynamicCallable 1256 | struct Math { 1257 | func dynamicallyCall(withArguments args: [Int]) -> Int { 1258 | return args.reduce(0, +) 1259 | } 1260 | } 1261 | let math = Math() 1262 | let result = math(1, 2, 3) // Output: 6 1263 | ``` 1264 | - @dynamicMemberLookup allows accessing properties of an object dynamically, similar to key-value coding. You can access members on an object even if they aren’t explicitly defined in the type, enabling dynamic member resolution at runtime. 1265 | 1266 | ## 56. Can you compare the use cases for value types and reference types in app development? 1267 | - For answer follow my answer : https://github.com/shobhakartiwari/structure-vs-classes.git 1268 | 1269 | ## 57. Which Sorting Algorithm Does Swift's High-Order Function `sorted()` Use? 1270 | - TimSort 1271 | ```swift 1272 | Timsort is a powerful hybrid sorting algorithm that blends the best features of merge sort and insertion sort. Here's why it stands out: 1273 | 1274 | 🛡️ Stability: Preserves the relative order of equal elements, making it ideal for sorting complex data types. 1275 | ⚡ Efficiency: Handles large datasets with a worst-case time complexity of O(n log n), ensuring reliable performance. 1276 | 🔄 Adaptability: Excels with partially sorted arrays, optimizing sorting operations for real-world scenarios. 1277 | This combination of speed, stability, and adaptability makes Timsort a go-to algorithm for efficient sorting in Swift and beyond. 🚀 1278 | ``` 1279 | 1280 | ## 58. What will be the output of the following Swift code used in Shobhakar Tiwari's iOS Mock Interview session? 1281 | Screenshot 2024-12-07 at 11 53 21 PM 1282 | 1283 | - Options are : 1284 | #1. iOS Swift Mock 1285 | #2. UIKit Trialing 1286 | #3. Compile-time Error 1287 | #4. Runtime Error 1288 | 1289 | - Answer : #3. Compile-time Error. 1290 | 1291 | ## 59. Swift’s Triple Self: self, Self, and Self.self 1292 | - https://medium.com/@shobhakartiwari/swifts-triple-self-self-self-and-self-self-99970389aaf8 1293 | 1294 | ## Contributing 1295 | Shobhakar Tiwari welcome contributions! Please check out our [CONTRIBUTING.md](https://raw.githubusercontent.com/shobhakartiwari/iOS_Lead_Interview/refs/heads/main/CONTRIBUTING.md) file for guidelines on how to get started. 1296 | 1297 | --------------------------------------------------------------------------------