Avatar
Q8 import UIKit import Combine final class UserViewController: UIViewController { private let state: UserViewState private let iconImageView: UIImageView = .init() private let nameLabel: UILabel = .init() private var cancellables: Set<AnyCancellable> = [] init(id: User.ID) { self.state = UserViewState(id: id) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // レイアウト iconImageView.translatesAutoresizingMaskIntoConstraints = false iconImageView.layer.cornerRadius = 40 iconImageView.layer.borderWidth = 4 iconImageView.layer.borderColor = UIColor.systemGray3.cgColor iconImageView.clipsToBounds = true view.addSubview(iconImageView) nameLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(nameLabel) NSLayoutConstraint.activate([ iconImageView.widthAnchor.constraint(equalToConstant: 80), iconImageView.heightAnchor.constraint(equalToConstant: 80), iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), iconImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), nameLabel.centerXAnchor.constraint(equalTo: iconImageView.centerXAnchor), nameLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 16), ]) // View への反映 do { let task = Task { [weak self] in guard let state = self?.state else { return } for await user in await state.$user.values { guard let self = self else { return } self.nameLabel.text = user?.name } } cancellables.insert(.init { task.cancel() }) } do { let task = Task { [weak self] in guard let state = self?.state else { return } for await iconImage in await state.$iconImage.values { guard let self = self else { return } self.iconImageView.image = iconImage } } cancellables.insert(.init { task.cancel() }) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { await state.loadUser() } } } extension Published.Publisher: @unchecked Sendable where Output: Sendable {} extension UIImage: @unchecked Sendable {} import Combine import Foundation import class UIKit.UIImage actor UserViewState { let id: User.ID @Published private(set) var user: User? @Published private(set) var iconImage: UIImage? init(id: User.ID) { self.id = id } func loadUser() async { do { // User の JSON の取得 let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data = try await downloadData(from: url) // JSON のデコード let user: User = try JSONDecoder().decode(User.self, from: data) // state への反映 self.user = user // アイコン画像の取得 let iconData = try await downloadData(from: user.iconURL) // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // state への反映 self.iconImage = iconImage } catch { // エラーハンドリング print(error) } } }
1:59 PM
extension URL: @unchecked Sendable {}
2:00 PM
👍 17