> Как разделить функции для чтения и записи значений (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Revolut

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Разделение чтения и записи в Swift достигается через протоколы с разными уровнями доступа, computed properties с отдельными геттерами и сеттерами, а также через value types с приватными мутирующими методами. Основная цель - инкапсуляция состояния и контроль над изменениями. Для iOS-приложений это особенно важно при работе с моделями данных, UserDefaults, Core Data и реактивными потоками, где нужно предотвратить несанкционированную запись при свободном чтении.

Подробное объяснение

В Swift разделение доступа к чтению и записи реализуется несколькими способами:

  1. Протоколы с разным уровнем доступа - определяете два протокола: Readable и Writable. Тип conforms к обоим, но внешний код получает только Readable reference. Это классический принцип interface segregation.

  2. Computed properties с отдельными геттерами и сеттерами - используете private(set) для свойства, которое можно читать снаружи, но изменять только внутри типа. Либо полностью приватное stored property с публичным computed getter.

  3. Value types с мутирующими методами - структуры с приватными свойствами и публичными методами, которые изменяют состояние через mutating. Это позволяет контролировать инварианты.

  4. Акторы (actors) - для конкурентного доступа в Swift 5.5+ акторы изолируют состояние и предоставляют асинхронные методы чтения и записи.

  5. Функциональный подход - иммутабельные структуры данных, где "запись" - это создание новой копии с изменённым значением.

Для iOS-приложений часто комбинируют эти подходы: модель данных - иммутабельная структура, а менеджер состояния - класс с private(set) и методами обновления.

На практике

В реальном iOS-проекте разделение чтения и записи применяется:

  • В UserDefaults или Keychain обёртках: публичный getter для чтения, приватный setter внутри сервиса.
  • В ViewModel для SwiftUI: @Published свойство с private(set) - view читает, view model пишет.
  • В Core Data: NSManagedObject с readonly computed properties для отображения, отдельный сервис для мутаций.
  • В реактивных потоках (Combine): CurrentValueSubject приватный, наружу - AnyPublisher только для чтения.

Ключевой паттерн - "read-only interface": вы возвращаете протокол или обёртку, которая не позволяет писать, даже если реальный объект мутабелен.

Пример кода

SWIFT
// Протокольный подход
protocol UserReadable {
var id: UUID { get }
var name: String { get }
}
protocol UserWritable: UserReadable {
func updateName(_ newName: String)
}
struct User: UserWritable {
private(set) var id: UUID
private(set) var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
// Использование
func displayUser(_ user: UserReadable) {
print(user.name) // только чтение
}
var user = User(id: UUID(), name: "Alice")
displayUser(user)
// private(set) с классом
final class UserStore {
private(set) var currentUser: User
private let repository: UserRepository
func updateUser(_ user: User) {
// валидация, логирование
currentUser = user
}
}
// Актор для конкурентности
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
value
}
}

Как отвечать на собеседовании

Начните с протоколов и private(set), затем упомяните акторы для конкурентности. Покажите понимание trade-off: протоколы добавляют абстракцию, private(set) проще, но не защищает от мутаций внутри класса. Приведите пример из реального iOS-проекта - например, ViewModel с @Published private(set) var state. Подчеркните, что выбор зависит от контекста: для простых моделей достаточно private(set), для сложных систем - протоколы или акторы. Если спросят про SwiftUI - объясните, как это связано с @State и @Binding.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание инкапсуляции и принципов SOLID (особенно interface segregation).
  • Знание возможностей Swift: access control, протоколы, акторы.
  • Умение применять паттерны в контексте iOS: Combine, SwiftUI, Core Data.
  • Способность объяснить trade-off между простотой и гибкостью.
  • Понимание конкурентности и потокобезопасности при разделении доступа.

Типичные ошибки

  • Использование private(set) для классов, когда нужна потокобезопасность - это не защищает от гонок.
  • Создание избыточных протоколов для тривиальных случаев - overengineering.
  • Забывание про mutating для структур при изменении свойств.
  • Путаница между let и private(set): let - константа, private(set) - изменяемая внутри.
  • Возврат мутабельного массива или словаря наружу - копирование ссылочных типов внутри коллекций.
  • Игнорирование акторов при работе с shared state в многопоточных сценариях.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью