Composable Architecture - Ein neuer Blick auf die Anwendungsarchitektur

Die ausgewogene Architektur der mobilen Anwendung verlÀngert die Lebensdauer des Projekts und der Entwickler.



Geschichte



Treffen Sie Alex. Er muss eine Anwendung entwickeln, um eine Einkaufsliste zu erstellen. Alex ist ein erfahrener Entwickler und bildet zunÀchst die Anforderungen an das Produkt:



  1. Die Möglichkeit, das Produkt auf andere Plattformen (watchOS, macOS, tvOS) zu portieren
  2. Vollautomatische Anwendungsregression
  3. IOS 13+ UnterstĂŒtzung


Alex hat kĂŒrzlich das pointfree.com- Projekt kennengelernt , bei dem Brandon und Stephen ihre Einblicke in die moderne Anwendungsarchitektur teilten. So erfuhr Alex von Composable Architecutre.



Zusammensetzbare Architektur



Nach Durchsicht der Dokumentation zur zusammensetzbaren Architektur stellte Alex fest, dass es sich um eine unidirektionale Architektur handelte, die den Entwurfsanforderungen entsprach. Aus der BroschĂŒre folgte:



  1. Aufteilung des Projekts in Module;
  2. Datengesteuerte BenutzeroberflÀche - Die Konfiguration der Schnittstelle wird durch ihren Status bestimmt.
  3. Die gesamte Modullogik wird durch Unit-Tests abgedeckt.
  4. Schnappschuss-Test von Schnittstellen;
  5. UnterstĂŒtzt iOS 13+, macOS, tvOS und watchOS;
  6. SwiftUI- und UIKit-UnterstĂŒtzung.


Bevor wir uns mit dem Studium der Architektur befassen, werfen wir einen Blick auf ein Objekt wie einen intelligenten Regenschirm.



Bild alt

Wie beschreibt man das System, nach dem der Regenschirm angeordnet ist?



Das Schirmsystem besteht aus vier Komponenten:



. : .



. .



. .



. 10 .



composable architecture . .





? , .



UI — [];



Action — ;



State — [];



Environment — [ ];



Reducer — , [] ;



Effect — , action reducer.



( 1)







.



. , .



struct ShoppingListState {
    var products: [Product] = []
}

enum ShoppingListAction {
    case addProduct
}


reducer :



let shoppingListReducer = Reducer { state, action, env in
    switch action {
    case .addProduct:
        state.products.insert(Product(), at: 0)
        return .none
    }
}


:



struct Product {
    var id = UUID()
    var name = ""
    var isInBox = false
}

enum ProductAction {
    case toggleStatus
    case updateName(String)
}

let productReducer = Reducer { state, action, env in
    switch action {
    case .toggleStatus:
        state.isInBox.toggle()
        return .none
    case .updateName(let newName):
        state.name = newName
        return .none
    }
}


, reducer , , . reducer .



UI .



UI





iOS 13+ Composable Architecture SwiftUI, .



, Store:



typealias ShoppingListStore = Store<ShoppingListState, ShoppingListAction>

let store = ShoppingListStore(
    initialState: ShoppingListState(products: []),
    reducer: shoppingListReducer,
    environment: ShoppingListEnviroment()
)


Store viewModel MVVM — .



let view = ShoppingListView(store: store)

struct ShoppingListView: View {
    let store: ShoppingListStore

    var body: some View {
        Text("Hello, World!")
    }
}


Composable Architecture SwiftUI. , store ObservedObject, WithViewStore:



var body: some View {
    WithViewStore(store) { viewStore in
        NavigationView {
            Text("\(viewStore.products.count)")
            .navigationTitle("Shopping list")
            .navigationBarItems(
                trailing: Button("Add item") {
                    viewStore.send(.addProduct)
                }
            )
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}


Add item, . send(Action) .



, , :



struct ProductView: View {
    let store: ProductStore

    var body: some View {
        WithViewStore(store) { viewStore in
            HStack {
                Button(action: { viewStore.send(.toggleStatus) }) {
                    Image(
                        systemName: viewStore.isInBox
                            ? "checkmark.square"
                            : "square"
                    )
                }
                .buttonStyle(PlainButtonStyle())
                TextField(
                    "New item",
                    text: viewStore.binding(
                        get: \.name,
                        send: ProductAction.updateName
                    )
                )
            }
            .foregroundColor(viewStore.isInBox ? .gray : nil)
        }
    }
}




. ? .



enum ShoppingListAction {
        //       
    case productAction(Int, ProductAction)
    case addProduct
}

//      
// ..   ,   
let shoppingListReducer: Reducer<ShoppingListState, ShoppingListAction, ShoppingListEnviroment> = .combine(
        //  ,     
    productReducer.forEach(
                // Key path
        state: ShoppingListState.products,
                // Case path
        action: /ShoppingListAction.productAction,
        environment: { _ in ProductEnviroment() }
    ),
    Reducer { state, action, env in
        switch action {
        case .addProduct:
            state.products.insert(Product(), at: 0)
            return .none
                //      productReducer
        case .productAction:
            return .none
        }
    }
)




. .



UI :



var body: some View {
    WithViewStore(store) { viewStore in
        NavigationView {
            List {
        //   
                ForEachStore(
            //  store
                    store.scope(
                        state: \.products,
                        action: ShoppingListAction.productAction
                    ),
            //  
                    content: ProductView.init
                )
            }
            .navigationTitle("Shopping list")
            .navigationBarItems(
                trailing: Button("Add item") {
                    viewStore.send(.addProduct)
                }
            )
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}


150 , .





2 — (in progress)



Teil 3 - Erweiterung der FunktionalitĂ€t, HinzufĂŒgen und Entfernen von Produkten (in Bearbeitung)



Teil 4 - Listen-Caching hinzufĂŒgen und in den Store gehen (in Bearbeitung)



Quellen



Produktliste Teil 1: github.com



Ansatz Autorenportal: pointfree.com



Quellen fĂŒr zusammensetzbare Architekturen: https://github.com/pointfreeco/swift-composable-architecture




All Articles