Arquitectura componible: una nueva mirada a la arquitectura de aplicaciones

La arquitectura equilibrada de la aplicación móvil prolonga la vida del proyecto y los desarrolladores.



Historia



Conoce a Alex. Necesita desarrollar una aplicación para hacer una lista de compras. Alex es un desarrollador experimentado y, en primer lugar, forma los requisitos del producto:



  1. La capacidad de portar el producto a otras plataformas (watchOS, macOS, tvOS)
  2. Regresión de aplicaciones completamente automatizada
  3. Soporte IOS 13+


Alex se familiarizó recientemente con el proyecto pointfree.com , donde Brandon y Stephen compartieron sus conocimientos sobre la arquitectura de aplicaciones moderna. Así fue como Alex se enteró de Composable Architecutre.



Arquitectura componible



Después de revisar la documentación de Composable Architecture, Alex determinó que se trataba de una arquitectura unidireccional que coincidía con los requisitos de diseño. Del folleto siguió:



  1. Dividir el proyecto en módulos;
  2. IU basada en datos: la configuración de la interfaz está determinada por su estado;
  3. Toda la lógica del módulo está cubierta por pruebas unitarias;
  4. Prueba de instantáneas de interfaces;
  5. Admite iOS 13+, macOS, tvOS y watchOS;
  6. Soporte SwiftUI y UIKit.


Antes de sumergirnos en el estudio de la arquitectura, echemos un vistazo a un objeto como un paraguas inteligente.



imagen alt

¿Cómo describir el sistema mediante el cual se organiza el paraguas?



El sistema de paraguas tiene cuatro componentes:



. : .



. .



. .



. 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)



Parte 3: ampliar la funcionalidad, agregar eliminación y clasificación de productos (en curso)



Parte 4: agregue el almacenamiento en caché de la lista y vaya a la tienda (en progreso)



Fuentes



Lista de productos, parte 1: github.com



Aproximación al portal de autores: pointfree.com



Fuentes de arquitectura componible: https://github.com/pointfreeco/swift-composable-architecture




All Articles