Вопросы для собеседования iOS - Swift. Часть 2

Первая часть статьи.

Конкретный тип против абстрактного

В Swift имеются различные типы категорий, включая два фундаментальных понятия: конкретные и абстрактные типы.

Что такое «конкретный тип»?

Конкретный тип непосредственно инстанцируется для создания объектов:

  1. Определяются все свойства и методы.
  2. Примеры конкретных типов  —  классы и структуры.

Простой конкретный тип

struct Person {
var name: String
var age: Int
}
let person = Person(name: "John", age: 20)
print("Person name \(person.name) Age \(person.age)")

Конкретный тип с использованием класса

class Employee {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func empActiveStatus() {
print("Active")
}
}

let employee = Employee(name: "Jones", age: 35)
print("Employe name \(employee.name) Age \(employee.age)")
print("Active Status \(employee.empActiveStatus())")

Что такое «абстрактный тип»?

Это тип, которым определяется набор требований без фактической реализации:

  1. Требования относятся к свойствам и методам.
  2. Абстрактный тип не инстанцируется напрямую.
protocol Shape {
func area() -> Double
}
struct Circle: Shape {
var radius: Double
func area() -> Double {
return Double.pi * radius * radius
}
}
struct Square: Shape {
var side: Double
func area() -> Double {
return side * side
}
}

let circle = Circle(radius: 5.0)
print("Area of circle: ", circle.area())
let square = Square(side: 5.0)
print("Area of square: ", square.area())

Как получают абстрактный тип?

Основной способ  —  с помощью протокола.

Протокол  —  это абстрактный тип: им объявляется требование, но без фактической реализации.

Нужен конкретный тип с соответствием протоколу и конкретной реализацией.

Суммируем

  1. Конкретный тип инстанцируется и используется непосредственно.
  2. Как и протоколом, абстрактным типом конкретная реализация определяется, но не предоставляется.
  3. Определяется общий интерфейс с несколькими конкретными типами: полиморфизм в деле.

Обработка исключений

В Swift процесс обработки исключений или ошибок  —  это еще и управление: выбрасывание, перехват и манипулирование ими во время выполнения.

Способы обработки ошибок или исключений:

  1. do;
  2. try;
  3. catch;
  4. throw;
  5. throws.

Прежде чем обрабатывать исключения, создадим перечисление ошибок с соответствием протоколу ошибок и выбрасыванием значения ошибки внутри функции:

enum CustomError: Error {
case someError
}

С do-try-catch — do-catch перехватываем и обрабатываем ошибки, выбрасываемые блоком кода в блоке do:

enum CustomError: Error {
case someError(message: String)
}
func someFunction() throws {
let randomNum = Int.random(in: 0...1)
if randomNum == 0 {
throw CustomError.someError(message: "Error A Occurred")
} else {
print("Function Executed successfully")
}
}
do {
try someFunction()
} catch CustomError.someError {
print("Caught a specific Error: CustomError.someError")
} catch {
print("Unknown Error \(error)")
}

Выбрасывающая функция: выбрасываем ошибки выбрасывающей функцией с ключевым словом throws или словом throw внутри блока:

enum CustomError: Error {
case someError(message: String)
}
func someFunction() throws {
let randomNum = Int.random(in: 0...1)
if randomNum == 0 {
throw CustomError.someError(message: "Error A Occurred")
} else {
throw CustomError.someError(message: "Error B Occurred")
}
}
try someFunction()

try: даем понять, что знаем о потенциальной ошибке, как ее обработать, еще до выбрасывания ее функцией:

enum CustomError: Error {
case someError
}
func someFunction() throws -> Int {
let randomNum = Int.random(in: 0...1)
if randomNum == 0 {
throw CustomError.someError
} else {
return 100
}
}

do {
try someFunction()
} catch CustomError.someError {
print("Caught a specific Error: CustomError.someError")
} catch {
print("Unknown Error \(error)")
}

try!: используем эту функцию, только убедившись, что никакой ошибки ею не выдается:

enum CustomError: Error {
case someError
}
func someFunction() throws -> Int {
let randomNum = Int.random(in: 0...1)
if randomNum == 0 {
throw CustomError.someError
} else {
return 100
}
}
let result = try! someFunction()
print("Result \(result)")

try?: необязательная, но внутри возможен ответ или ошибка. Так отключится блок catch, в случае ошибки просто вернется nil:

enum CustomError: Error {
case someError
}
func someFunction() throws -> Int {
let randomNum = Int.random(in: 0...1)
if randomNum == 0 {
throw CustomError.someError
} else {
return 100
}
}
let result = try? someFunction()
print("Result \(result ?? 0)")

Спецификаторы доступа

Спецификаторы доступа характеризуются видимостью и доступностью членов класса, структур, перечислений.

Но прежде снова о базовых понятиях приложения iOS:

  1. Модуль.
  2. Фреймворк.
  3. Исходный файл.
  4. Цель.
  5. Пакет.

Модуль  —  единый блок распределения кода, импортируемый ключевым словом import.

Цель  —  пакет приложения или фреймворк, называемый отдельным модулем.

Исходный файл  —  единый исходный файл в одном модуле, фреймворке или приложении.

Пакет  —  набор файлов для приложения сборки, делаются с помощью ipa-файла.

Спецификаторы доступа используются для управления видимостью и доступностью классов, структур, перечислений, свойств, методов.

В Swift доступно пять уровней доступа:

  1. Open.
  2. Public.
  3. Internal.
  4. File private.
  5. Private.

Open

  1. Самый разрешенный уровень доступа.
  2. Доступен из любого исходного файла любого модуля, цели или фреймворка.
  3. Возможно создание подкласса или переопределение внешними модулями.
import UIKit

// Модуль
class SomeClass {
var tableView: UITableView = UITableView()
}

// Из документации Apple
open class UITableView : UIScrollView, NSCoding, UIDataSourceTranslating {

}

// Пример
open class MyClass {
open var property: Int = 0
open func someFunc() {
}
}

// Выбросится ошибка, так как нет открытой структуры.
// Заменяем «open» на «struct»
open struct MyStruct {

}

Public

  1. Аналогично уровню доступа Open, но с ограничениями.
  2. Доступен из любого исходного файла любого модуля, цели или фреймворка.
  3. Невозможно создание подкласса или переопределение внешними модулями.

Попробуем public с фреймворком UIKit:

public class SomeButtonView: UIButton {
public func setButtonTitle(_ title: String) {
setTitle(title, for: .normal)
}
}

Internal

  1. В Swift это уровень доступа по умолчанию.
  2. Доступен из любого исходного файла того же модуля, цели или фреймворка, но не из внешних модулей.
  3. Полезен для определения деталей внутренней реализации.
// Внутренние класс и функции
internal class InternalViewController: UIViewController {
internal override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private lazy var label: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
label.text = "Internal Label View"
label.textAlignment = .center
label.font = .systemFont(ofSize: 12, weight: .bold)
label.numberOfLines = 1
label.textColor = .blue
label.sizeToFit()
return label
}()
private func setupUI() {
view.addSubview(label)
}
}

File private

  1. Доступен из того же исходного файла.
  2. Полезен для сокрытия деталей реализации в одном исходном файле.

Вот пример fileprivate и private в одном и том же файле:

class Employee {
fileprivate var firstName: String
private var nickName: String
init (firstName: String, nickName: String) {
self.firstName = firstName
self.nickName = nickName
}
fileprivate func updateFirstName(){}
private func updateNickName() {}
}
class FetchRecords: Employee {
func getEmployee() -> String {
updateFirstName() // Доступно
return firstName // Доступно
}
func getAnotherEmployee() -> String {
updateNickName() // «updateNickName» недоступен из-за уровня защиты «private»
return nickName // «nickName» недоступен из-за уровня защиты «private»
}
}
extension Employee {
fileprivate func modifyName() {
print("Employee Name is \(firstName)") // Доступно
print("Employee Nick Name is \(nickName)") // Доступно
}
}
var employee = Employee(firstName: "Sabapathy", nickName: "Saba")
employee.modifyName()

Private

  1. Самый ограниченный уровень доступа.
  2. Доступен из объявления или расширений того же исходного файла.
  3. Используется для инкапсуляции деталей реализации в пределах конкретной области.

Вот пример fileprivate и private в разных файлах:

// Main.swift
class Employee {
fileprivate var firstName: String
private var nickName: String
init (firstName: String, nickName: String) {
self.firstName = firstName
self.nickName = nickName
}
fileprivate func updateFirstName(){}
private func updateNickName() {}
}

// Extension.swift
extension Employee {
fileprivate func modifyName() {
print("Employee Name is \(firstName)")
// Имя недоступно из-за уровня защиты «file-private»
print("Employee Nick Name is \(nickName)")
// «nickName» недоступно из-за уровня защиты «private»
}
}

«Open» против «Public»

  1. Open применяется к классу и членам класса, не к структурам.
  2. Доступ, создание подкласса и переопределение класса или членов класса возможны вне определенного модуля.
  3. Public применяется к классу, структурам, перечислениям и их членам.
  4. Вне определенного модуля возможен только доступ: без создания подкласса или переопределения класса.

Когда используются «Open» или «Public»?

  1. Open при разработке общедоступного API с возможностью расширения его другими и изменения реализации вне модуля.
  2. Public при доступе к ним других модулей, но без возможности их расширения или изменения.

Когда используется «Internal»?

  1. При создании фреймворка, причем без доступа других модулей/фреймворков к сущностям.
  2. При работе над крупномасштабными проектами с несколькими модулями/фреймворками.

Когда используются «File-private» или «Private»?

  1. private при наличии модели данных с пользовательской информацией. Так данные защищены от внесения изменений.
  2. file-private применяется для служебных или вспомогательных классов. Так скрываются детали реализации, предотвращается доступ к ним.

Не забываем и о «Final» для:

  1. Запрета класса во фреймворке для создания подклассов.
  2. Обозначения свойств и методов.
  3. Осуществления статической диспетчеризации, увеличения производительности.
  4. Предотвращения переопределения методов во фреймворке и избежания проблем.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Kanagasabapathy Rajkumar: iOS — Swift Interview Questions 2023

Предыдущая статья10 проектов для изучения Golang в 2023 году
Следующая статьяГлубокое погружение в режим Copy-on-Write в pandas. Часть 1