Combine und SwiftUI

Swift wurde auf der WWDC 2014 Developers Conference vorgestellt. Apple empfiehlt die Verwendung der MVC-Architektur. Wie die Praxis gezeigt hat, reicht die übliche MVC-Architektur für komplexe Anwendungen nicht aus. Auf der WWDC 2019 werden Entwicklern SwiftUI und Combine gezeigt, was das Leben eines iOS-Entwicklers verbessern soll. Apple empfiehlt jetzt die Verwendung der MVVM-Architektur. Und jetzt ist es an der Zeit, diese Technologien zu verstehen.

Combine

Die Hauptidee ist, dass Publisher Daten an einen oder mehrere Abonnenten senden. Die Daten werden über Operatoren an Abonnenten gesendet. Schauen wir uns die in dieser Technologie verwendeten Begriffe genauer an.

Publishers

Publisher senden Werte an einen oder mehrere Abonnenten. Sie entsprechen dem Publisher-Protokoll und deklarieren den Ausgabetyp und alle von ihnen verursachten Fehler.

Subscribers

Abonnenten abonnieren eine bestimmte Instanz des Herausgebers und erhalten Daten, bis das Abonnement gekündigt wird. Sie entsprechen dem Abonnentenprotokoll.

Operators

Operatoren werden benötigt, um Daten zu ändern. Filtern Sie beispielsweise Null heraus, formatieren Sie Daten usw.

Lasst uns beginnen

Ich zeige Ihnen ein Beispiel mit reaktivem Code. Beginnen wir mit einem Modell, das wir über die API ausfüllen.

import Foundation
class TenantModel: NSObject, Codable {
var name: String?
var code: String?
var dateFormat: String?
var timeFormat: String?
var advanceDaysCount: Int?
var historyDaysCount: Int?
var features: [String]?
var timeSlots: [TimeSlot]?
var timezone: String?
var country: String?
var displayUserNamesInBooking: Bool?
var allowMultiSlotBookings: Bool?
var allowMultipleBookingsPerUser: Bool?
var provideNoteFieldInBookings: Bool?
var noteFieldInBookingsIsRequired: Bool?
var noteFieldInBookingsHelpText: String?
var accessRestrictionStart: String?
var accessRestrictionEnd: String?
var bookingRestrictionStart: String?
var bookingRestrictionEnd: String?
}
class TimeSlot: NSObject, Codable {
let id: Int?
let startTime: String?
let endTime: String?
}

Network requests

Ich werde URLSession für Anfragen verwenden

func perform<T: Decodable>(_ request: URLRequest) -> AnyPublisher<HTTPResponse<T>, Error> {
return URLSession.shared.dataTaskPublisher(for: request)
.tryMap { data, response -> HTTPResponse<T> in
guard let httpResponse = response as? HTTPURLResponse,
200...299 ~= httpResponse.statusCode else {
throw APIError.responseError(((response as? HTTPURLResponse)?.statusCode ?? 500,
String(data: data, encoding: .utf8) ?? ""))
}
let data = try JSONDecoder().decode(T.self, from: data)
return HTTPResponse(value: data, response: response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
view raw perform.swift hosted with ❤ by GitHub
func getTenants(_ userID: String) -> AnyPublisher<[TenantModel], Error>? {
guard let url = Endpoint.userTenants(userID).absoluteURL else { return nil }
var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.get.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(Token.access ?? "")", forHTTPHeaderField: "Authorization")
return HTTPClient.perform(request)
.map(\.value)
.eraseToAnyPublisher()
}

Beachten Sie den Rückgabetyp: AnyPublisher & amp; lt; [TenantModel], Fehler & amp; gt; ist unser Verlag. Wir abonnieren es und erhalten Daten, die bereits im ViewModel enthalten sind

import Foundation
import Combine
class TenantViewModel: ObservableObject {
@Published var tenants: [TenantModel] = []
private var cancellableSet: Set<AnyCancellable> = []
init() {}
func getTenants() {
UserAPI.getTenants(User.idWithToken)?
.sink(
receiveCompletion: { [weak self] completion in
if case let .failure(error) = completion {
switch error {
case let apiError as APIError:
HTTPClient.apiErrorRefreshToken(apiError) {
self?.getTenants()
}
default:
break
}
}
},
receiveValue: { [weak self] tenant in
self?.tenants = tenant
})
.store(in: &self.cancellableSet)
}
}

.sink ist ein Teilnehmer, der Daten empfängt. self.tenants = Tenants - Weisen Sie einer mit @Publisher gekennzeichneten Variablen Werte zu. Diese Änderungen erzwingen die Aktualisierung der Ansicht

import SwiftUI
struct TenantPicker: View {
@StateObject var viewModel: TenantViewModel = TenantViewModel()
@Binding var selectedTenant: TenantModel?
@Binding var showTenantPicker: Bool
var label: String
var title: String
var body: some View {
HStack {
Button(action: {
viewModel.getTenants()
showTenantPicker.toggle()
}) { HStack(alignment: .center, spacing: 2) {
Text(label)
.foregroundColor(Color(.label))
Spacer()
Text(selectedTenant?.name ?? "Select tenant")
.foregroundColor(Color(.secondaryLabel))
}
}.sheet(isPresented: $showTenantPicker, content: {
NavigationView {
ScrollView {
CustomList {
ForEach(Array(viewModel.tenants.enumerated()), id: \.element) { tenantIndex, tenant in
CustomListItem {
Button {
selectedTenant = tenant
CoreDataManager.shared.saveTenant(tenant)
showTenantPicker = false
} label: {
HStack {
Text("\(tenant.name ?? "")")
.foregroundColor(Color(.label))
Spacer()
}
}
}
if viewModel.tenants.count > 1 &&
viewModel.tenants.count - 1 > tenantIndex {
Divider()
}
}
}.cornerRadius(8)
}
.padding()
.fillBy(Color(.systemGray6))
.navigationBarTitle(title, displayMode: .inline)
.navigationBarItems(leading: Button(action: {
showTenantPicker = false
}, label: {
HStack(spacing: 8) {
Image(systemName: "chevron.left")
Text("Back")
}
}))
}
})
}
}
}

Fügen Sie den @ StateObject-Wrapper hinzu, um alle Änderungen am ViewModel zu erfassen. Hier für jedes (Array (viewModel.tenants ...)) gehen wir die Liste durch, diese Liste wird sich beim Empfang von Daten über das Netzwerk neu erstellen.

Folgendes müssen wir erhalten:

swiftui

 

Mehr davon

Get in touch

In Kontakt kommen

Frankfurt am Main, Germany (Sales)

60354

Eckenheimer Schulstraße, 20

+38 (098) 630-49-85

info@a5.ua

Kharkiv, Ukraina

61023

Trinklera Strasse, 9

+38 (050) 908-31-07

info@a5.ua

Burgas, Bulgaria (Development)

8008

бул. „Транспортна“ 15, Northern Industrial Zone

+359 877 350129

info@a5.ua