---
title: "Programmare in SwiftUI: ObjectBinding e BindableObject"
url: https://www.sofapps.it/it/blog/swiftui-programming-objectbinding/
date: 2020-01-10
author: Costantino Pistagna
language: it
translated_from: en
tags: [SwiftUI, Tutorial]
---

# Programmare in SwiftUI: ObjectBinding e BindableObject

> @State è adatto ai value type posseduti da una sola View. Per una class condivisa tra più view SwiftUI offre @ObjectBinding insieme al protocol BindableObject, che pubblica i cambiamenti tramite un PassthroughSubject di Combine. Un'unica istanza fa da source of truth e ogni View la usa per riferimento.

In un [articolo precedente](https://www.sofapps.it/it/blog/swiftui-programming-states/) abbiamo visto che in **SwiftUI** è possibile associare uno o più stati a una View tramite il modifier **@State**; quest'ultimo deve essere usato con ***value type*** allocati all'interno della View che li dichiara. In questo modo la nostra applicazione è in grado di capire autonomamente quando aggiornare l'interfaccia utente in seguito ai cambiamenti della variabile marcata con @State che, ricordiamo, viene identificata come **source of truth**.

Ma cosa succede se la nostra **source of truth** è un ***oggetto complesso***, progettato per esempio come una class? In questo caso @State fallisce il suo scopo, perché è pensato per lavorare esclusivamente con le property locali della nostra View.

SwiftUI mette a disposizione un concetto più adatto a questo caso: **@ObjectBinding**. Si può usare in tutti i casi in cui la property viene passata per riferimento, come accade con le class.

```swift

struct MyCustomObject : View {
    @ObjectBinding var myObject
    var body: some View {
        Text(myObject.someTextProperty)
    }
}

```

## Il protocol BindableObject

Le property con un modifier **@ObjectBinding** si riferiscono quindi a oggetti di tipo class. Perché una class sia compatibile con il modifier @ObjectBinding, deve adottare il protocol **BindableObject**.

Nella sua variante più semplice il protocol **BindableObject** è molto lineare e richiede soltanto che la nostra class implementi una property chiamata didChange, che notificherà gli oggetti View iscritti in modo che possano reagire correttamente ai cambiamenti dell'interfaccia visiva.

```swift

class MyObject : BindableObject {
    let didChange = PassthroughSubject<Void, Never>()
    
    var someTextProperty:String = "" {
        didSet {
            didChange.send( () )
        }
    }
    
    ...
}

```

## Il framework Combine e PassthroughSubject

Perché funzioni, il codice precedente richiede di includere il supporto per il framework Combine all'inizio del nostro file swift.

```swift

import Combine

```

Il **framework Combine** è uno strumento molto potente e piuttosto complesso, che vedremo in un prossimo articolo. Per ora ci basta sapere che, tra le tante altre cose, fornisce una class chiamata **PassthroughSubject** (la nostra property didChange è un'istanza di questa class). Ha il compito di notificare (Publishing) le parti interessate (Subscribers) che si è verificato un evento specifico.

**PassthroughSubject**, come suggerisce il nome, si comporta in modo molto simile alle notifiche tradizionali messe a disposizione dal framework Foundation di Swift con NSNotificationCenter: l'oggetto specificato come parametro (nel nostro caso Void, dato che non vogliamo passare alcun parametro) viene passato così com'è, senza modifiche, al destinatario insieme a un ***evento di notifica*** che fa scattare una nuova ricostruzione della view. 

Se si vuole specificare un errore insieme alla notifica, lo si può passare come secondo parametro. Nel nostro esempio questa eventualità non ci interessa, ed è per questo che passiamo Never come secondo parametro nell'invocazione di PassthroughSubject.

```swift

let didChange = PassthroughSubject<Void, Never>()

```

Una volta creata un'istanza di tipo **PassthroughSubject**, la possiamo usare per pubblicare il nostro evento ogni volta che vogliamo *notificare* alle View iscritte i cambiamenti della nostra **source of truth**.

Per soddisfare quest'ultimo requisito usiamo il costrutto didSet messo a disposizione da Swift, che viene invocato ogni volta che cambia il valore di una property:

```swift

var someTextProperty:String = "" {
    didSet {
        didChange.send( () )
    }
}

```

In questo modo, quando modifichiamo *someTextProperty*, viene invocato il **costrutto didSet**. Al suo interno chiamiamo il metodo send() dell'istanza di PassthroughSubject. Notate come specifichiamo un parametro di tipo Void passando al metodo una coppia di parentesi ().

Così tutti gli oggetti iscritti ai cambiamenti (quelli che hanno dichiarato la loro property come **@ObjectBinding**) sapranno che è arrivato il momento di aggiornare il loro body.

![Fig.1: Semplice schema che illustra i riferimenti con ObjectBinding.](https://www.sofapps.it/blog/swiftui-programming-objectbinding/objectbindingsimpleflowstateen.jpg)

Come potete vedere dallo schema qui sopra, il nostro modello è caratterizzato da una **singola istanza** in memoria, tramite una class conforme al **protocol BindableObject**. I nostri oggetti View contengono un *riferimento* all'oggetto originale.

In questo modo si evita un problema tipico, la *duplicazione* della source of truth: le modifiche al modello saranno uniche e porteranno all'aggiornamento immediato di tutte le view che fanno riferimento alla nostra class.

![Fig.2: Semplice schema che illustra source of truth e ObjectBinding.](https://www.sofapps.it/blog/swiftui-programming-objectbinding/sourceoftruthandobjectbinding.jpg)

## Un esempio

Proviamo a mettere in pratica i concetti precedenti con un esempio. Nell'articolo precedente avevamo configurato la nostra CustomTableViewCell per usare una variabile passata per valore, contenente una struct chiamata Person.

```swift

struct CustomTableViewCell : View {
    var model: Person
    
    var body: some View {
    ...

```

Questo approccio non permetteva alla CustomTableViewCell di essere indipendente rispetto agli aggiornamenti del proprio contenuto: la variabile, infatti, veniva copiata durante la fase di init della CustomTableViewCell.

Ogni modifica al suo contenuto era gestita tramite una variabile @State, posseduta da ContentView, che rigenerava la cella con il nuovo contenuto a ogni cambiamento.

![Fig.3: Riferimento a State e a Object binding a confronto.](https://www.sofapps.it/blog/swiftui-programming-objectbinding/statevsobjectbindingreference.jpg)

Con il concetto di **@ObjectBinding** possiamo rendere unico il *modello Person*, passando alla CustomTableViewCell solo un riferimento, come mostrato nel grafico qui sopra a destra.

La prima cosa da fare è modificare il nostro modello Person in modo che usi una class che *adotta* il protocol **BindableObject** al posto di una **struct**.

```swift

class Person : BindableObject {
    let didChange = PassthroughSubject<Void, Never>()
    
    var photoName:String { didSet { update() } }
    var title:String { didSet { update() } }
    var subtitle:String { didSet { update() } }
    
    init(photoName: String, title: String, subtitle: String) {
        self.photoName = photoName
        self.title = title
        self.subtitle = subtitle
    }
    
    func update() {
        didChange.send(())
    }
}

```

Il nostro nuovo modello *adotta* il protocol **BindableObject** implementando la property **didChange** tramite un'*istanza* di **PassthroughSubject**.

Ogni property interessata da possibili cambiamenti dell'interfaccia visiva (photoName, title e subtitle) è stata modificata in modo da reagire tramite il costrutto didSet. In questo modo, ogni volta che aggiorniamo una delle variabili, viene attivata la funzione update(), che a sua volta invia una notifica a tutti gli iscritti tramite l'istanza di PassthroughSubject.

Infine modifichiamo la CustomTableViewCell per sfruttare il nuovo modello per riferimento. Per farlo basta anteporre il modifier @ObjectBinding di cui abbiamo parlato.

```swift

struct CustomTableCell : View {
    @ObjectBinding var model: Person
    
    var body: some View {
        return HStack {
            Image(model.photoName)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .clipShape(Circle())
                .frame(width: 60, height: 60, alignment: .center)
            VStack(alignment: .leading) {
                Text(model.title).font(.title)
                Text(model.subtitle).font(.subheadline).fontWeight(.thin)
            }
            Spacer()
        }
    }
}

```

In questo modo la CustomTableViewCell non ha una copia del modello Person, ma lavora con un riferimento all'area di memoria in cui è conservato il modello originale. Vediamo dove e come creare il modello da passare per riferimento.

Di questo requisito si occuperà ContentView, creando il modello vero e proprio in memoria e passandolo per riferimento alla CustomTableViewCell.

```swift

struct ContentView : View {
    var dataSource:[Person] = [
        Person(photoName: "photoThumb", title: "Mario Rossi", subtitle: "Amministratore"),
        Person(photoName: "photoThumb", title: "Giuseppe Bianchi", subtitle: "Consigliere"),
        Person(photoName: "photoThumb", title: "Luca Verdi", subtitle: "Funzionario"),
    ]
    var body: some View {
        List {
            CustomTableCell(model: dataSource[0])
            CustomTableCell(model: dataSource[1])
            CustomTableCell(model: dataSource[2])
        }
    }
}

```

## List e TableView
Questa nuova versione di ContentView usa un nuovo oggetto chiamato List, che raggruppa gli elementi più o meno come il corrispettivo VStack. La differenza sta nella visualizzazione: il risultato sarà molto simile a una UITableView del framework UIKit.

Quella che vediamo qui sopra è la forma di dichiarazione più semplice di List. Nel prossimo articolo vedremo come rendere dinamico il suo contenuto, in base al modello dichiarato.

--A presto!
