└── iOS-developer-interview.md /iOS-developer-interview.md: -------------------------------------------------------------------------------- 1 | # Swift 2 | 3 | ## Уровень Middle 4 | 5 |
6 | Чем value type отличается от reference type? 7 | 8 | * value type передаётся / присваивается по значению, т.е. каждая переменная получает свою копию объекта. 9 | * reference type передаётся / присваивается ссылке, т.е. все переменные указывают на одну копию объекта. 10 |
11 | 12 |
13 | Какие value type есть в Swift? 14 | 15 | * `struct` 16 | * `enumeration` 17 |
18 | 19 |
20 | Как устроены типы `Int`, `Float`, `Bool`? 21 | "Простые" типы: `Int`, `Float`, `Bool` и тд. являются структурами 22 |
23 | 24 |
25 | Какие reference type есть в Swift? 26 | 27 | * `class` 28 | * `closure` 29 | * `recursive enumeration` 30 |
31 | 32 |
33 | Что такое ARC? 34 | 35 | Система управления памятью на основе подсчёта количества сильных ссылок (счётчик ссылок) на объект. 36 |
37 | 38 |
39 | Для каких типов используется ARC? 40 | 41 | reference type 42 |
43 | 44 |
45 | Что такое retain cycle (strong reference cycle)? 46 | 47 | Ситуация, при которой 2+ объекта держат сильные ссылки друг на друга, не позволяя счётчикам ссылок занулиться и освободить память. 48 |
49 | 50 |
51 | Как избежать retain cycle? 52 | 53 | * `weak` reference 54 | * `unowned` reference 55 |
56 | 57 |
58 | Почему `weak` reference объявляется как `var` а не `let`? 59 | 60 | `ARC` запишет в эту ссылку `nil` при удалении объекта. 61 |
62 | 63 |
64 | Вызовется ли при этом property observer (`didSet`)? 65 | 66 | Нет 67 |
68 | 69 |
70 | В каком ещё случае не вызовется property observer (`didSet`)? 71 | 72 | * `init` 73 | * `didSet` 74 | 75 |
76 | 77 |
78 | Чем отличается `weak` от `unowned`? 79 | 80 | * `weak`: `ARC` запишет в эту ссылку `nil` при удалении объекта. 81 | * `unowned`: `ARC` НЕ запишет в эту ссылку `nil` при удалении объекта. 82 |
83 | 84 |
85 | Что произойдёт, если обратиться к объекту по `unowned` reference после его удаления? 86 | 87 | Падение приложения / `runtime error` 88 |
89 | 90 |
91 | Как решить что использовать: `weak` или `unowned`? 92 | 93 | * `unowned` - более производительный вариант, нужно использовать в тех местах, где есть уверенность что объект не будет удалён. 94 | * `weak` - во всех остальных случаях. 95 |
96 | 97 |
98 | Что такое `autoreleasepool`? 99 | 100 | 101 | * В `Objective-C`, `NSAutoreleasePool` - объект, в который помещаются ссылки на объекты. При `drain` делает `release` для каждого `autorelease` посланного объекту. 102 | * В `Swift` - функция, принимающая на вход замыкание. После выполнения замыкания делает тоже самое для `Objective-C` объектов. 103 |
104 | 105 |
106 | Что такое `enumeration`? 107 | 108 | Value type, который может принимать одно из заданных значений (case). 109 |
110 | 111 |
112 | Что такое `associated values`? 113 | 114 | Значение `enumeration` может иметь кортеж ассоциированных с ним значений любого типа. 115 |
116 | 117 |
118 | Что такое `indirect case`? 119 | 120 | Кортеж ассоциированных значений может содержать другое значение из этого же `enumeration`. 121 |
122 | 123 |
124 | Что такое `Optional`? 125 | 126 | `enumeration` с 2мя значениями: `case none`, `case some(Wrapped)` 127 |
128 | 129 |
130 | Чем `nil` отличается от `Optional.none`? 131 | 132 | * `nil` - синтаксический сахар для `Optional.none` 133 | * Возможно из-за того, что `Optional` реализует протокол `ExpressibleByNilLiteral`. 134 |
135 | 136 |
137 | Как получить значение из Optional? 138 | 139 | * `!` 140 | * `if let` 141 | * `guard let` 142 | * `??` 143 | * `switch` 144 | * `map` 145 | * `flatMap` 146 |
147 | 148 |
149 | Какие есть базовые коллекции в Swift? 150 | 151 | * `Array` 152 | * `Dictionary` 153 | * `Set` 154 |
155 | 156 |
157 | Какого они типа: class, struct, enum? 158 | 159 | * struct 160 |
161 | 162 | ## Уровень Senior 163 | 164 |
165 | Почему NSArray типа class, а Array - struct? 166 | 167 | Сделано специально, чтобы избежать ошибок из-за изменения массива из другого потока при работе с ним на текущем потоке. 168 |
169 | 170 |
171 | Если Array - struct, значит при передаче в качестве параметра он копируется. Почему это работает быстро? 172 | 173 | `Copy-on-Write` 174 |
175 | 176 |
177 | Как реализован `Array` с `Copy-on-Write`? 178 | 179 | Внутри `Array` хранится класс `ManagedBuffer`, который отвечает за фактическое хранение данных. 180 | При каждой модификации `Array` делается проверка `isKnownUniquelyReferenced`. Если на объект более одной ссылки, делается копия. 181 |
182 | 183 |
184 | Чем отличается `@objc` от `dynamic`? 185 | 186 | * `@objc` - метод доступен из `Objective-C`. В `Objective-C` вызывается динамически, в `Swift` - статически. 187 | * `dynamic` - метод вызывается в `Swift` динамически. 188 |
189 | 190 | 191 | # Замыкания 192 | 193 | ## Уровень Middle 194 | 195 |
196 | Что такое closure (замыкание)? 197 | 198 | Отдельный блок кода, который можно передавать как параметр и вызывать в процессе выполнения программы. 199 |
200 | 201 |
202 | Что будет напечатано? 203 | 204 | ```Swift 205 | func makeIncrementer(forIncrement amount: Int) -> () -> Int { 206 | var runningTotal = 0 207 | func incrementer() -> Int { 208 | runningTotal += amount 209 | return runningTotal 210 | } 211 | return incrementer 212 | } 213 | 214 | let incrementByTen = makeIncrementer(forIncrement: 10) 215 | print(incrementByTen()) 216 | print(incrementByTen()) 217 | print(incrementByTen()) 218 | 219 | let incrementBySeven = makeIncrementer(forIncrement: 7) 220 | print(incrementBySeven()) 221 | 222 | print(incrementByTen()) 223 | 224 | let alsoIncrementByTen = incrementByTen 225 | print(alsoIncrementByTen()) 226 | 227 | print(incrementByTen()) 228 | ``` 229 | 230 | 231 | 10 20 30 7 40 50 60 232 |
233 | 234 |
235 | Что такое `escaping` замыкания? 236 | 237 | Замыкания, помеченные как `escaping` параметр функции, могут быть вызваны после завершения функции. 238 |
239 | 240 | ## Уровень Senior 241 | 242 |
243 | Что такое `autoclosure`? 244 | 245 | * `autoclosure` - замыкание, которое автоматически создаётся для того, чтобы обернуть выражение, переданное в качестве аргумента при вызове функции. 246 | * `autoclosure` позволяет отложить вычисление выражения с момента вызова до того момента, когда значение понадобится. 247 |
248 | 249 |
250 | Что делает этот код? 251 | 252 | ```Swift 253 | func allValues(in array: [Int], match predicate: (Int) -> Bool) -> Bool { 254 | return array.lazy.filter { !predicate($0) }.isEmpty 255 | } 256 | ``` 257 | 258 | 259 | Проверяет, что все элементы массива удовлетворяют предикату. 260 |
261 | 262 |
263 | Какая ошибка есть в этом коде? 264 | 265 | Ошибка компиляции: `error: closure use of non-escaping parameter 'predicate' may allow it to escape` 266 | 267 | `non-escaping` замыкание используется как `escaping` 268 |
269 | 270 |
271 | Как исправить? 272 | 273 | ```Swift 274 | func allValues(in array: [Int], match predicate: (Int) -> Bool) -> Bool { 275 | return array.filter { !predicate($0) }.isEmpty 276 | } 277 | ``` 278 | Минус: Создание промежуточного массива. Лишнее потребление памяти. 279 | 280 | ```Swift 281 | func allValues(in array: [Int], match predicate: @escaping (Int) -> Bool) -> Bool { 282 | return array.lazy.filter { !predicate($0) }.isEmpty 283 | } 284 | ``` 285 | Минус: Замыкание стало escaping, что противоречит сути (оно всегда вызывается до завершения функции `allValues`), и лишает компилятор возможности оптимизировать код. 286 | 287 | Правильный вариант: 288 | ```Swift 289 | func allValues(in array: [Int], match predicate: (Int) -> Bool) -> Bool { 290 | return withoutActuallyEscaping(predicate) { escapablePredicate in 291 | array.lazy.filter { !escapablePredicate($0) }.isEmpty 292 | } 293 | } 294 | ``` 295 |
296 | 297 | # Многопоточность 298 | 299 | ## Уровень Middle 300 | 301 |
302 | Какие есть способы реализации многопоточности? 303 | 304 | * `OperationQueue` 305 | * `DispatchQueue` 306 | * `Thread` 307 | 308 |
309 | 310 |
311 | Чем они отличаются? 312 | 313 | * `DispatchQueue` - абстракция поверх потоков. Очередь, управляющая выполнением поставленных в неё задач на пуле потоков управляемых системой. 314 | * `OperationQueue` - высокоуровневая надстройка поверх `DispatchQueue`. 315 | * Предоставляет следующие возможности поверх `DispatchQueue`: 316 | 1. Установка зависимостей между задачами. 317 | 2. Отмена всех операций. 318 | 3. Получение количества операций в очереди. 319 | 4. Динамическое управление количеством одновременно выполняемых операций. 320 | 5. Приостановка выполнения. 321 | 322 |
323 | 324 |
325 | Чем отличается `DispatchQueue.sync` от `DispatchQueue.async`? 326 | 327 | * `DispatchQueue.async` - ставит задачу в очередь и продолжает выполнение. 328 | * `DispatchQueue.sync` - ставит задачу в очередь и ждёт, пока эта операция будет выполнена. 329 |
330 | 331 | ## Уровень Senior 332 | 333 |
334 | Как синхронизировать выполнение нескольких DispatchQueue операций? 335 | 336 | * `DispatchGroup` 337 | * `DispatchQueue.sync(flags: .barrier) {}` 338 |
339 | 340 |
341 | Что будет напечатано? 342 | 343 | ```Swift 344 | let queue = DispatchQueue(label: "Queue") 345 | print("1") 346 | queue.sync { 347 | print("2") 348 | queue.sync { print("3") } 349 | queue.async { print("4") } 350 | print("5") 351 | } 352 | print("6") 353 | ``` 354 | 355 | 356 | 1 2 (Приложение зависнет / упадёт) 357 |
358 | 359 |
360 | Что будет напечатано? 361 | 362 | ```Swift 363 | let queue = DispatchQueue(label: "Queue", attributes: .concurrent) 364 | print("1") 365 | queue.sync { 366 | print("2") 367 | queue.sync { print("3") } 368 | queue.async { print("4") } 369 | print("5") 370 | } 371 | print("6") 372 | ``` 373 | 374 | 375 | * 1 2 3 4 5 6 376 | * 1 2 3 5 4 6 377 | * 1 2 3 5 6 4 378 |
379 | 380 |
381 | Что такое RunLoop? 382 | 383 | Программный интерфейс обработки входных источников и операций, запускаемых на потоке, в цикле
384 | Всегда есть на главном потоке.
385 | Каждая операция на RunLoop оборачивается в `autoreleasepool`.
386 | Нужен для работы таймеров.
387 | Нет у `DispatchQueue`. 388 |
389 | 390 |
391 | Когда чистится память для `DispatchQueue`? 392 | 393 | Зависит от параметра `DispatchQueue.AutoreleaseFrequency` при создании очереди. 394 | Стандартное поведение - когда у очереди нет активных задач. 395 |
396 | 397 | # iOS 398 | 399 | ## Уровень Middle 400 | 401 |
402 | Какие есть состояния жизненного цикла iOS приложения? 403 | 404 | * Not running 405 | * Inactive 406 | * Active 407 | * Background 408 | * Suspend 409 |
410 | 411 |
412 | Какие системные события приложение может/должно обрабатывать? 413 | 414 | * **Memory warning** 415 | * **Protected data becomes available/unavailable** 416 | * **State restoration** 417 | * **Open URLs** 418 | * **Local/remote notifications** 419 | * **Location changes** 420 | * **Application shortcuts** 421 | * AV sessions 422 | * File download 423 | * Handoff tasks 424 | * Inter-app communication 425 |
426 | 427 |
428 | Жизненный цикл `UIViewController`. 429 | 430 | * `init` 431 | * `viewDidLoad` 432 | * `viewWillAppear` 433 | * `viewDidAppear` 434 | * `viewWillLayoutSubviews` 435 | * `viewDidLayoutSubviews` 436 | * `viewWillDisappear` 437 | * `viewDidDisappear` 438 | * `willMove(toParent:)` 439 | * `didMove(toParent:)` 440 | * `deinit` 441 |
442 | 443 |
444 | Какая разница между использованием делегатов и нотификаций? 445 | 446 | Общее: 447 | * Получение уведомлений при каком-то изменении наблюдаемого объекта. 448 | 449 | Различия: 450 | * Делегирование позволяет передать часть логики другому объекту. Этот объект один и отвечает только на те вопросы, которые ему задают. 451 | * Нотификации позволяют подписаться на любые изменения наблюдаемого объекта. 452 | * Количество подписчиков на нотификации не ограничено. 453 | * Подписчики не влияют на логику поведения наблюдаемого объекта. 454 |
455 | 456 |
457 | Какие есть способы подписки на нотификации? 458 | 459 | * `NotificationCenter` 460 | * `NSKeyValueObserving` 461 |
462 | 463 |
464 | Чем отличается `NotificationCenter` от `NSKeyValueObserving`? 465 | 466 | * `NotificationCenter` - ручная посылка нотификаций всем подписчикам. Для подписки нужен доступ к наблюдаемому классу, чтобы посылать нотификации. 467 | * `NSKeyValueObserving` - все наследники NSObject автоматически посылают сообщения всем своим подписчикам при изменении своих полей. Можно подписаться на изменение полей любого класса. 468 |
469 | 470 |
471 | Нужно ли вызывать `NotificationCenter.removeObserver`? 472 | 473 | Есть 2 способа подписки: через селектор и через замыкание (блок). 474 | Если используются замыкания - отписываться обязательно нужно. 475 | Если используется селектор, начиная с iOS 9.0 это не обязательно. До iOS 9.0, произойдёт падение, если объект удаляется, пока у него есть подписчики. 476 |
477 | 478 | ## Уровень Senior 479 | 480 |
481 | Что будет напечатано? 482 | 483 | ```Swift 484 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 485 | print("1") 486 | DispatchQueue.main.sync { print("2") } 487 | print("3") 488 | return true 489 | } 490 | ``` 491 | 492 | 493 | 1 (Приложение зависнет / упадёт) 494 |
495 | 496 |
497 | Что такое `NSKeyValueCoding`? 498 | 499 | Механизм непрямого доступа к полям любого наследника `NSObject` по имени или ключу. 500 |
501 | 502 | # UI 503 | 504 | ## Уровень Middle 505 | 506 |
507 | Чем `UIView` отличается от `CALayer`? 508 | 509 | * `CALayer` отвечает за представление информации на экране. 510 | * `UIView` содержит в себе `CALayer`, отвечает за взаимодействие с пользователем (`UIResponder`) и участвует в расчёте геометрии представления на экране (layout). 511 |
512 | 513 |
514 | Чем `frame` отличается от `bounds`? 515 | 516 | * `frame` - координаты UIView в родительской системе координат. 517 | * `bounds` - координаты видимой области в собственной системе координат. 518 |
519 | 520 |
521 | Как сделать закруглённые края у UIView? 522 | 523 | 1. 524 | ```Swift 525 | clipsToBounds = true 526 | layer.cornerRadius = *value* 527 | ``` 528 | Минус: часто пересчитывается. 529 | 530 | 2. 531 | ```Swift 532 | mask = MaskView() 533 | MaskView.layerClass = CAShapeLayer.self 534 | shapeLayer.path = UIBezierPath 535 | ``` 536 | Минус: Обновление маски только при изменении frame 537 | 538 | 3. 539 | ```Swift 540 | layer.cornerRadius = *value* 541 | layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, ...] 542 | layer.masksToBounds = true 543 | ``` 544 | Минус: `layer.maskedCorners` доступен с iOS 11.0+. 545 |
546 | 547 |
548 | Чем `CALayer.transform` отличается от `UIView.transform`? 549 | 550 | * `CALayer.transform` - `CATransform3D` 551 | * `UIView.transform` - `CGAffineTransform` (2D) 552 |
553 | 554 |
555 | Что поменяется при изменении `CALayer.transform`, `UIView.transform` - `frame` или `bounds`? 556 | 557 | frame 558 |
559 | 560 |
561 | Что такое autolayout, как он работает? 562 | 563 | Система динамического расчёта позиции и размера UIView. 564 | Для определения позиции и размера используются заданные для UIView правила (констреинты). 565 |
566 | 567 |
568 | Что такое hugging и resistance? 569 | 570 | Приоритеты, с которыми `UIView` противостоит попыткам растянуть / сжать её от `intrinsicContentSize`. 571 |
572 | 573 |
574 | Что такое `intrinsicContentSize`? 575 | 576 | Предпочтительный размер `UIView` для отображения всех внутренностей. Не учитывает внешние ограничения. 577 |
578 | 579 |
580 | Как сообщить autolayout, что предпочтительный размер `UIView` изменился? 581 | 582 | Вызвать метод `invalidateIntrinsicContentSize`. 583 |
584 | 585 |
586 | Как анимировать позицию/размер `UIView` при помощи autolayout? 587 | 588 | ```Swift 589 | constraint.constant = *value* 590 | UIView.animate(withDuration: ) { self.layoutIfNeeded() } 591 | ``` 592 |
593 | 594 | ## Уровень Senior 595 | 596 |
597 | Чем отличаются Layer tree, Presentation tree и Render tree? 598 | 599 | * Layer tree - объекты в этом дереве хранят конечные значения анимаций. При изменении свойств слоя используется объект из этого дерева. 600 | * Presentation tree - объекты в этом дереве хранят текущие значения анимаций. 601 | * Render tree - используется для фактической отрисовки. Не доступно для разработчика. 602 |
603 | 604 |
605 | Как работает `UIView.transform` и autolayout? 606 | 607 | autolayout работает с `frame` `UIView` до трансформации. 608 | Значение фрейма после трансформации не определено и должно игнорироваться. 609 |
610 | 611 |
612 | Чем отличаются методы `layoutIfNeeded`, `layoutSubviews`, `setNeedsLayout`? 613 | 614 | * `layoutIfNeeded` - немедленно обновляет layout, если это необходимо. Может начать выше по дереву. 615 | * `layoutSubviews` - непосредственный layout, начиная с текущего `UIView` и ниже по дереву. В документации не рекомендуется вызывать этот метод напрямую. 616 | * `setNeedsLayout` - помечает layout как требующий обновления. layout произойдёт на следующий цикл обновления UI. 617 |
618 | 619 |
620 | Как бы ты написал `hitTest`? 621 | 622 | ```Swift 623 | func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 624 | guard point(inside: point, with: event) else { return nil } 625 | guard isUserInteractionEnabled && !isHidden && alpha > 0.01 else { return nil } 626 | for subview in subviews { 627 | if let hitView = subview.hitTest(convert(point, to: subview), with: event) { 628 | return hitView 629 | } 630 | } 631 | return self 632 | } 633 | ``` 634 |
635 | --------------------------------------------------------------------------------