28
votes

Quelle est la différence entre ObservedObject et StateObject dans SwiftUI

Si j'ai un ObservableObject dans SwiftUI, je peux y faire référence en tant que @ObservedObject :

class ViewModel: ObservableObject {
    @Published var someText = "Hello World!"
}

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        Text(viewModel.someText)
    }
}

Ou en tant que @StateObject :

class ViewModel: ObservableObject {
    @Published var someText = "Hello World!"
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        Text(viewModel.someText)
    }
}

Mais quelle est la différence réelle entre les deux? Y a-t-il des situations où l'un est meilleur que l'autre, ou ce sont deux choses complètement différentes?


6 Réponses :


34
votes

@ObservéObjet

Lorsqu'une vue crée sa propre instance @ObservedObject , elle est recréée chaque fois qu'une vue est supprimée et redessinée:

struct ContentView: View {
  @StateObject var viewModel = ViewModel()
}

Au contraire, une variable @State conservera sa valeur lorsqu'une vue est redessinée.

@StateObject

Un @StateObject est une combinaison de @ObservedObject et @State - l'instance du ViewModel sera conservée et réutilisée même après qu'une vue est supprimée et redessinée:

struct ContentView: View {
  @ObservedObject var viewModel = ViewModel()
}

Performance

Bien qu'un @ObservedObject puisse avoir un impact sur les performances si la vue est forcée de recréer souvent un objet lourd, cela n'a pas beaucoup d'importance lorsque @ObservedObject n'est pas complexe.

Quand utiliser @ObservedObject

Il peut sembler qu'il n'y a aucune raison d'utiliser un @ObserverObject , alors quand doit-il être utilisé?

Vous devez utiliser @StateObject pour toutes les propriétés observables que vous initialisez dans la vue qui l'utilise. Si l'instance ObservableObject est créée en externe et transmise à la vue qui l'utilise, marquez votre propriété avec @ObservedObject.

Notez qu'il y a trop de cas d'utilisation possibles et qu'il peut parfois être souhaitable de recréer une propriété observable dans votre vue. Dans ce cas, il est préférable d'utiliser un @ObservedObject .

Liens utiles:


4 commentaires

Il n'est pas clair pour moi s'il existe un cas d'utilisation d'ObservedObject, pourquoi n'est-il pas obsolète? Le cas d'utilisation de @DavidPasztor montre qu'ils peuvent être équivalents, mais quand ObservedObject serait-il le premier choix?


@RyanHeitner Tout est dans les liens utiles que j'ai fournis. Mais comme il est vrai que tout le monde ne les lira pas tous, j'ai ajouté le résumé à ma réponse.


Est-il possible qu'une vue ait les deux?


@AbhijitSarkar Oui, ces wrappers de propriété sont appliqués aux classes ObservableObject, pas à la vue elle-même. Vous pouvez avoir à la fois les propriétés StateObject et ObservedObject dans votre vue.



9
votes

Même si la réponse de pawello2222 a bien expliqué les différences lorsque la vue elle-même crée son modèle de vue, il est important de noter les différences lorsque le modèle de vue est injecté dans la vue.

Lorsque vous injectez le modèle de vue dans la vue, tant que le modèle de vue est un type de référence, il n'y a aucune différence entre @ObservedObject et @StateObject , car l'objet qui a injecté le modèle de vue dans votre vue doit contenir une référence pour voir le modèle ainsi, le modèle de vue n'est pas détruit lorsque la vue enfant est redessinée.

class ViewModel: ObservableObject {}

struct ParentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        ChildView(viewModel: viewModel) // You inject the view model into the child view
    }
}

// Even if `ChildView` is discarded/redrawn, `ViewModel` is kept in memory, since `ParentView` still holds a reference to it - `ViewModel` is only released and hence destroyed when `ParentView` is destroyed/redrawn.
struct ChildView: View {
    @ObservedObject var viewModel: ViewModel
}


0 commentaires

11
votes

La documentation Apple a expliqué pourquoi l'initialisation avec ObservedObject n'est pas sûre .

SwiftUI peut créer ou recréer une vue à tout moment, il est donc important que l'initialisation d'une vue avec un ensemble donné d'entrées aboutisse toujours à la même vue. Par conséquent, il n'est pas sûr de créer un objet observé dans une vue.

La solution est StateObject .

Dans le même temps, la documentation nous a montré comment créer des modèles de données dans une vue (ou une application / scène) lorsqu'elle peut conserver la vérité et la transmettre à une autre vue.

struct LibraryView: View {
    @StateObject var book = Book() // Hold on to the 1 truth
    var body: some View {
        BookView(book: book) // Pass it to another view
    }
}

struct BookView: View {
    @ObservedObject var book: Book // From external source
}


2 commentaires

Auriez-vous besoin d'ObservedObject puisqu'un type de référence est passé dans BookView? J'essaie de savoir quand vous auriez besoin d'ObservedObject.


Oh, je pense que j'ai compris. Sans ObservedObject, vous n'obtiendrez aucune modification poussée vers BookView qui se produirait pour réserver si le parent (ou d'autres sources) apportent des modifications au livre StateObject.



1
votes

Voici un exemple. Chaque fois que vous cliquez sur le bouton d' refresh , CountViewObserved force StateObjectClass à être détruit / recréé, afin que vous puissiez voir 0 , ce qui n'est pas attendu.

import SwiftUI
import Combine

class StateObjectClass:ObservableObject{
    let type:String
    let id:Int
    @Published var count = 0
    init(type:String){
        self.type = type
        self.id = Int.random(in: 0...1000)
        print("type:\(type) id:\(id) init")
    }
    deinit {
        print("type:\(type) id:\(id) deinit")
    }
}

struct CountViewState:View{
    @StateObject var state = StateObjectClass(type:"StateObject")
    var body: some View{
        VStack{
            Text("@StateObject count :\(state.count)")
            Button("+1"){
                state.count += 1
            }
        }
    }
}

struct CountViewObserved:View{
    @ObservedObject var state = StateObjectClass(type:"Observed")
    var body: some View{
        VStack{
            Text("@Observed count :\(state.count)")
            Button("+1"){
                state.count += 1
            }
        }
    }
}

struct ContentView: View {
    @State var count = 0
    var body: some View {
        VStack{
            Text("refresh CounterView count :\(count)")
            Button("refresh"){
                count += 1
            }

            CountViewState()
                .padding()

            CountViewObserved()
                .padding()

        }
    }
}


0 commentaires

0
votes

@StateObject est un état d'une vue donnée, donc son instance est conservée par SwiftUI à travers body mises à jour du body . Il n'est cependant pas conservé lors de l'exécution en Aperçu.

@ObservedObject en revanche, n'est qu'un objet observé par une View donnée, et n'est donc pas retenu par SwiftUI (il doit être conservé en dehors de la View ).

En d'autres termes - il semble que SwiftUI conserve une référence strong de @StateObject et unowned référence sans @ObservedObject de @ObservedObject .

Source conservée vs source non conservée, source de comportement des aperçus , vers 8h30 environ.


0 commentaires

0
votes

La différence entre disons:

@ObservedObject var livre: BookModel et @StateObject var livre: BookModel

@ObservedObject ne possède PAS le "livre" d'instance, il est de votre responsabilité de gérer le cycle de vie de l'instance.

Mais lorsque vous voulez lier le cycle de vie de votre «livre» observableObject à votre vue comme dans @State, vous pouvez utiliser @StateObject. Dans ce cas, SwiftUI possédera l'observableObject et la création et la destruction seront liées au cycle de vie de la vue SwiftUI maintiendra l'objet en vie pendant tout le cycle de vie de la vue Ceci est idéal pour les ressources coûteuses, vous n'avez pas besoin de jouer avec onDisappear plus pour libérer des ressources

Cette clarification est tirée de l'essentiel des données WWDC2020 dans SwiftUI:


0 commentaires