Как устроена архитектура формы логина с двумя полями и кнопкой
Что проверяют
Умеете ли вы на простом экране показать разделение ответственности, а не свалить всё в UIViewController. Следом обычно спрашивают, как это протестировать и куда положить токен после входа.
Как отвечать
Возьмите MVVM (или MVP, если так принято в команде) и разложите экран:
- View: поле логина, поле пароля (
SecureFieldв SwiftUI илиUITextFieldсisSecureTextEntryв UIKit) и кнопка. Логики нет, только привязка к состоянию и передача действий; - ViewModel: хранит
email,password, флагиisLoadingиisLoginEnabled, текст ошибки. В SwiftUI это класс с макросом@Observable(до iOS 17ObservableObject), в UIKit привязка через Combine или замыкания. Методlogin()работает на@MainActorи вызывает сервис черезasync; - сервис авторизации: протокол
AuthServiceс методомlogin(email:password:) async throws -> Session. ViewModel знает только протокол, реализация наURLSessionподставляется снаружи; - хранение сессии: токен в Keychain, не в
UserDefaults; - навигация: после успеха ViewModel сообщает координатору, а не открывает экран сама.
Проговорите состояния: пустая форма, невалидный ввод, загрузка (кнопка заблокирована, повторное нажатие не шлёт второй запрос), ошибка сети, неверные данные, успех. Проверка на клиенте нужна для удобства, сервер всё равно проверяет сам.
Про удобство тоже стоит сказать: textContentType со значениями .username и .password включает автозаполнение паролей, Return переводит фокус на следующее поле, клавиатура не перекрывает кнопку.
Тестируется ViewModel с фейковым AuthService, без запуска интерфейса и сети.
Где спотыкаются
Предлагают шифровать пароль на клиенте перед отправкой. Это не защищает: зашифрованная строка просто становится паролем. Защиту даёт HTTPS, а хэшированием занимается сервер.
Держат сетевой запрос прямо в контроллере. Экран тогда не протестировать без сети, а логика копируется в экран регистрации.
Забывают про загрузку и двойное нажатие. Два параллельных запроса на вход дают гонку, а иногда и две сессии.