Skip to content

Conversation

@vaskhan
Copy link
Owner

@vaskhan vaskhan commented Jul 24, 2025

No description provided.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Папку xcuserdata стоит игнорировать, кладя в .gitignore. В ней хранится информация о состоянии проекта (какой файл открыт, какой таргет выбран и т.п.), брейкпоинты и схемы для текущего пользователя Xcode.
Про gitignore можно прочитать вот тут


@main
struct ScheduleTrackerApp: App {
@StateObject private var services = try! APIServicesContainer()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше не использовать force операции

@Published var tickets: [TicketModel] = []
@Published var isLoading = false
@Published var errorType: AppErrorType = .none
@Published var showTransfers: Bool? = nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= nil можно опускать

struct CarrierInfoView: View {
@Environment(\.dismiss) var dismiss
let carrier: TicketModel
@StateObject var viewModel: CarrierInfoViewModel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StateObject (как и State) должен быть приватным. Если мы модель извне передаем, то стоит ObservedObject использовать

// email
Text("E-mail")
.font(.custom("SFPro-Regular", size: 16))
if viewModel.isLoading {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Везде, где это возможно (если layout при этом не изменяется), лучше использовать opacity вместо if для сокрытия View. Вот хороший пример с ссылками на WWDC

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Непонятное замечание. Что имеете в виду?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В SwiftUI, если нужно просто скрыть View, лучше использовать .opacity, а не if-else.
Когда ты пишешь if-else, SwiftUI во время компиляции создаёт две разные версии View: одну при true, другую при false. Это значит, что в иерархии появляется больше элементов, между которыми нужно переключаться во время работы - это дополнительные ресурсы.
А если использовать .opacity, то View всегда остаётся в иерархии одна. Она просто становится невидимой, и не тратятся ресурсы на перестройку структуры. Это проще и производительнее.
Это более продвинутая информация. Об этом подробнее можно по ссылке выше почитать или есть хорошая книга по SwiftUI на английском. В интернете можно бесплатную pdf найти. В ней подробно о механизме работы SwiftUI рассказывается в сравнении с UIKit


var body: some View {
ZStack {
Color("nightOrDayColor").ignoresSafeArea()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше Resource использовать:
Color(.nightOrDay)

Link("i.lozgkina@yandex.ru", destination: mailURL)
if let email = carrier.email {
Text("E-mail")
.font(.custom("SFPro-Regular", size: 16))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно сделать обертку для кастомных шрифтов, чтобы их было удобнее добавлять и переиспользовать. Например что-то такое:

struct Fonts {
    static let sfProBoldFontName = "SFPro-Bold"
    static let sfProBold24 = Font.custom(sfProBoldFontName, size: 24)
}

.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { dismiss() }) {
Image("leftChevron")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут тоже лучше Resource использовать:
Image(.leftChevron)

Comment on lines 37 to 46
if viewModel.isLoading {
Spacer()
ProgressView()
Spacer()
} else if viewModel.errorType == .internet {
ErrorInternetView()
} else if viewModel.errorType == .server {
ErrorServerView()
} else {
ScrollView(.vertical, showsIndicators: false) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Обработка состояний дублируется в нескольких View. Предлагаю создать отдельную View и поместить в нее общий код, и передавать в эту View отличную View. Будет что-то вроде:

struct StateWrapperView<Content: View>: View {
    let isLoading: Bool
    let errorType: AppErrorType?
    let content: () -> Content

    var body: some View {
        Group {
            if isLoading {
                VStack {
                    Spacer()
                    ProgressView()
                    Spacer()
                }
            } else if errorType == .internet {
                ErrorInternetView()
            } else if errorType == .server {
                ErrorServerView()
            } else {
                content()
            }
        }
    }
}

feat(StateWrapperView) add StateWrapperView in app
@vaskhan
Copy link
Owner Author

vaskhan commented Jul 26, 2025

@NaR0RoW Здравствуй. Если моделям добавлю Sendable и сервисы вместо классов сделаю actor, этого достаточно будет?

do {
return try APIServicesContainer()
} catch {
fatalError("Failed to initialize APIServicesContainer: \(error)")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По своему опыту хотел бы отметить, что в продакшене не используют fatalError, советую заменить на assertionFailure, тут подробнее

ZStack {
Color("nightOrDayColor").ignoresSafeArea()
VStack (spacing: 16) {
VStack (alignment: .center, spacing: 16) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alignment: .center можно опустить, т.к. это дефолтное значение

}
}
.frame(width: 220, height: 38, alignment: .leading)
.frame(width: 38, height: 38, alignment: .center)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут тоже alignment: .center можно опустить

@NaR0RoW
Copy link

NaR0RoW commented Jul 26, 2025

@NaR0RoW Здравствуй. Если моделям добавлю Sendable и сервисы вместо классов сделаю actor, этого достаточно будет?

Да, достаточно

@vaskhan
Copy link
Owner Author

vaskhan commented Jul 26, 2025

@NaR0RoW Здравствуй. Если моделям добавлю Sendable и сервисы вместо классов сделаю actor, этого достаточно будет?

Да, достаточно

Это в данном случае было формальностью? Ведь все работало и без них, никаких предупреждений не было и все было безопасно на потоках

@NaR0RoW
Copy link

NaR0RoW commented Jul 26, 2025

@NaR0RoW Здравствуй. Если моделям добавлю Sendable и сервисы вместо классов сделаю actor, этого достаточно будет?

Да, достаточно

Это в данном случае было формальностью? Ведь все работало и без них, никаких предупреждений не было и все было безопасно на потоках

Это не формальность, а вложение в масштабируемость и безопасность кода. Сейчас код может работать без Sendable и actor, но:

  1. Без акторов ты теряешь защиту от data racing. Если сетевой клиент не является actor, то любой код может обращаться к нему из разных потоков. Даже если сейчас это безопасно, при любом изменении логики можно получить data race, и отловить это будет достаточно сложно
  2. Sendable - это по сути контракт на безопасность при передаче между потоками. Swift не гарантирует, что структура, не помеченная как Sendable, будет безопасна при передаче в другие изолированные контексты (Task, actor, и т.д.).
  3. Если ты используешь Sendable и actor, то ты по сути говоришь компилятору "Да, я гарантирую, что это безопасно", а дальше сам компилятор может тебе помочь проверить это

Простой пример:

class NetworkClient {
    var token: String = ""
    
    func fetch() async {
        // ...
    }
}

Если кто-то из двух Task параллельно изменяет token - мы ловим data race.
Но если это actor NetworkClient, всё ок - Swift гарантирует, что обращения к token идут последовательно.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants