1. Практический путь
Прежде чем переходить к практике, необходимо прочитать теоретический материал.
- Tasks
- Actors
- Sequence
Если у вас не получается решить задачу, например:
- Ваш ответ не совпадает с ответом автора
- Не проходят тесты
- Вы не знаете что делать и с чего начать
Возвращайтесь к теории и внимательно изучайте теоретический материал. Не стесняйтесь гуглить или использовать chatGPT. Самое важное - понимание.
Задача 1¶
Что будет выведено на экран?
final class SomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
print("Is Main Thread - \(Thread.isMainThread)")
await foo()
print("Is Main Thread - \(Thread.isMainThread)")
}
Task.detached {
print("Is Main Thread - \(Thread.isMainThread)")
await foo()
print("Is Main Thread - \(Thread.isMainThread)")
}
}
func foo() async {
print("Is Main Thread - \(Thread.isMainThread)")
try? await Task.sleep(for: .seconds(1))
}
}
Задача 2¶
1) Что будет выведено на экран? 2) Отменилась ли задача? - ответ обосновать. Если да - то почему? Если нет - то почему и как сделать чтобы отменилась? 3) Что будет если вместо Task написать Task.detached?
let task = Task {
print("Task 1 has started")
await startLongTask()
print("Task 1 has finished")
print("Task 2 has started")
await startLongTask()
print("Task 2 has finished")
}
func startLongTask() async {
try? await Task.sleep(for: .seconds(5))
}
task.cancel()
Задача 3¶
Что происходит в момент вызова await? как система работает с потоком? что такое continuation?
Задача 4¶
Предположим вы разрабатываете чат и когда пользователь входит в чат, вам необходимо раз в n секунд дёргать запрос, который оповещает, что пользователь находится всё ещё в чате. Вам необходимо реализовать сервис, удовлетворяющий протоколу AnyLookingService, который бы отправлял события на сервер до тех пор пока не вызовется метод stop
public protocol AnyLookingService: Sendable {
func start()
func stop()
}
/// Метод отправки реализовывать не нужно достаточно использовать функцию ниже
/// Если метод выкинул ошибку - необходимо прекратить дальнейшую отсылку событий
/// В ином случае необходимо продолжать отправку спустя n секунд
func sendLooking() async throws { ... }
Задача 5¶
Чем отличается withCheckedContinuation и withUnsafeContinuation ?
Задача 6¶
К вам на ревью попала задача, которая имеет код ниже. Видите ли вы какие-нибудь проблемы? Если да, то необходимо подсветить их и обосновать почему это явялется проблемой?
import Foundation
actor ActivitiesStorage {
private var cache = [UUID: Data?]()
func retrieveHeavyData(for id: UUID) async -> Data? {
if let data = cache[id] {
return data
}
let data = await requestDataFromDatabase(for: id)
cache[id] = data
return data
}
private func requestDataFromDatabase(for id: UUID) async -> Data? {
print("Performing heavy data loading!")
try! await Task.sleep(for: .seconds(1))
// ...
return nil
}
}
Задача 7¶
Расскажите как работает Actor? Какие нюансы есть при работе с Actor?
Задача 8¶
Вы снова являетесь ревьюером задачи, в проекте вы используете MVVM и вам приносят на ревью код:
@MainActor
class OnboardingViewModel: ViewModel {
///
}
Будут ли у вас какие-нибудь замечания? хорошая ли идея помечать целый класс @MainActor - какие неприятные последствия могут быть в будущем при расширении проекта?
Задача 9¶
Напишите реализацию своей AsyncSequence, например чтобы отдавались числа до определённого лимита с задержкой в 1 секунду. Учитывайте возможность отмены работы AsyncSequence.
Задача 10¶
У вас есть легаси функция, которая содержит completion, код который вы пишите - асинхронный. Как можно вызвать данную функцию?
func fetchImageData(id: UUID, completion: @escaping (Result<Data, Error>)) {
/// ...
}
func loadImage(from id: UUID) async throws -> Data {
// Ваш код
}
Задача 11¶
Вы ревьюер. Что скажете про код ниже? можно ли его оптимизировать?
final class SomeInteractor: Sendable {
/// ...
func fetchUser(uid: UUID) async {
do {
let user = try await userService.fetchUserFromDatabase(by: uid)
await presenter.displayUser(user)
let updatedUser = try await userService.loadUserFromNetwork(by: uid)
try Task.checkCancellation()
await presenter.displayUser(updatedUser)
} catch {
print("Error fetching user: \(error)")
}
}
}
Задача 12¶
Напишите реализацию кеша, который бы мог хранить в себе информацию, определённое время и по истечении времени возвращать nil. На инициализацию данный кеш должен принимать TimeInterval, который означает время жизни кеша. В функционале должны быть реализованы следующие методы:
private struct Cache {
let value: T
let date: Date
}
/// Ваш класс/эктор {
private let cacheLifeTimeSeconds: TimeInterval
private var cache: Cache?
public var value: (value: T?, shouldUpdate: Bool) { ... }
public func set(_ newValue: T?) { ... }
}
Задача 13¶
Можно ли в задаче выше обойтись без actor? Если да - то как, если нет - то почему? Использовать другие механизмы синхронизации НЕЛЬЗЯ
Задача 14*¶
Вам дали задачку, в которой необходимо реализовать сервис получения аватарок пользователя. Сервис должен удовлетворять протоколу ниже
public typealias AvatarUpdatePublisher = AsyncPublisher<AnyPublisher<(UUID, Avatar), Never>>
public protocol AnyAvatarService: Sendable {
var updatesPublisher: AvatarPublisher { get }
/// Запрашивает на сервере (если нужно), готовит и возвращает аватар нужного размера
func requestAvatar(for user: UUID) async throws -> Avatar
/// Порядок в возвращаемом массиве НЕ соответствует порядку запросов
func requestAvatars(for userIds: [UUID]) async throws -> [Avatar]
func setupCacheLimits(countLimit: Int, totalCostLimit: Int) async
func clearAllAvatars() async
}
Данный сервис должен работать следующим образом. Когда UI функционал запрашивает аватарку, сервис должен проверить - находится ли она в рантайм кеше - если да, то отдать. Если нет - то необходимо проверить скачено ли изображение и хранится ли оно локально на телефоне, если да - тот необходимо обработать это изображение и вернуть пользователю в нужном размере. В остальном случае необходимо запросить изображение у сервера.
Дополнительные условия:
- Если сервер отвечает ошибкой - то необходимо сгенерировать изображение, на котором нужно написать первую букву email
- Если выполняется запрос на сервер для какого-нибудь userId, то он должен быть единственным, дублирования запросов с одинаковыми айдишниками быть не должно
- У рантайм кеша должен быть лимит и он должен правильно подчищаться
- Пользователь может запросить один из трех размеров изображения (64, 32, 16). С сервера возвращается исходное изображение размера 128x128
- Также необходимо учитывать, что запрос аватарок можно происходить из нескольких мест приложения
- *** Если вы чувствуете в себе силы, можно написать ViewModifier, чтобы было удобно работать с сервисом на UI. Подумайте, как это можно лучше сделать?
Примечание Начинайте реализовывать данный функционал последовательно - не пытайтесь написать сразу идеально. Начните с самого просто шага, когда запрос аватара происходит из 1 места на UI, подумайте что можно использовать для рантайм кеша? может есть что-то системное? или же вы захотите написать что-нибудь своё? Далее плавно повышайте сложность вашего сервиса, следите за потокобезопасностью, reetrancy. Данная задача может многому вас научить, она сочетает в себе все пройденные темы. По итогу реализации старайтесь думать о крайних кейсах, когда нужно загрузить множество аватаров, плохая сеть и другие.
Самым последним шагом приступайте к реализации updatesPublisher, он необходим для того, чтобы оповещать уже имеющиеся вьюхи о произошедших изменениях.