StateObject vs ObservedObject

Apr 11 2023
StateObject resolve problemas inesperados ao usar ObservedObject em determinadas situações. Este artigo apresentará as semelhanças, diferenças, princípios e precauções entre os dois.

StateObject resolve problemas inesperados ao usar ObservedObject em determinadas situações. Este artigo apresentará as semelhanças, diferenças, princípios e precauções entre os dois.

Conclusão

Ambos StateObject e ObservedObject são wrappers de propriedade usados ​​para assinar objetos observáveis ​​(tipos de referência que estão em conformidade com o protocolo ObservableObject). Quando o objeto observável inscrito envia dados por meio do Publisher integrado (usando @Published ou chamando diretamente seu método objectWillChange.send), StateObject e ObservedObject conduzirão suas exibições associadas para atualização.

ObservedObject só mantém o relacionamento de assinatura durante o tempo de vida da visão, enquanto StateObject, além de manter o relacionamento de assinatura, também mantém uma forte referência ao objeto observável.

Baseado no mecanismo ARC (Automatic Reference Counting) do Swift, o StateObject garante que o tempo de vida do objeto observável não seja menor que o tempo de vida da visão, garantindo assim a estabilidade dos dados durante a vida útil da visão.

No entanto, como ObservedObject mantém apenas o relacionamento de assinatura, se o tempo de vida do objeto observável inscrito for menor que o tempo de vida da exibição, a exibição exibirá vários comportamentos incontroláveis.

Alguns podem se perguntar: a instância correspondente a testObject no código abaixo tem um tempo de vida menor do que a exibição?

struct DemoView: View {
    @ObservedObject var testObject = TestObject()
    var body: some View {
        Text(testObject.name)
    }
}

Princípio

ARCO

Swift usa Automatic Reference Counting (ARC) para rastrear e gerenciar o uso de memória de instâncias de tipos de referência. Enquanto existir uma referência forte a uma instância de classe, o ARC não liberará a memória usada por essa instância. Em outras palavras, quando a referência forte a uma instância for 0, a instância será destruída pelo Swift e a memória que ela ocupa será recuperada.

StateObject garante que o tempo de vida da instância do objeto observável não seja menor que o da exibição, mantendo uma referência forte ao objeto observável.

Assinatura e Cancelável

No Combine, ao usar sink ou assign para assinar um Publisher, é necessário manter a relação de assinatura para que a assinatura funcione normalmente. A relação de assinatura é agrupada no tipo AnyCancellable e os desenvolvedores podem cancelar manualmente a assinatura chamando o método cancel de AnyCancellable.

var cancellable: AnyCancellable?
init() {
    cancellable = NotificationCenter.default.publisher(for: .AVAssetContainsFragmentsDidChange)
        .sink { print($0) }
}

var cancellable = Set<AnyCancellable>()
init() {
    NotificationCenter.default.publisher(for: .AVAssetContainsFragmentsDidChange)
        .sink { print($0) }
        .store(in: &cancellable)
}

Ambos ObservedObject e StateObject armazenam o relacionamento de assinatura entre a exibição e o objeto observável. Durante a existência da visualização, eles não cancelarão ativamente esta assinatura. No entanto, ObservedObject não pode garantir se o objeto observável cancelará a assinatura antecipadamente devido a ser destruído.

Descrição, exemplo e visualização

SwiftUI é uma estrutura declarativa em que os desenvolvedores usam código para declarar (descrever) a apresentação de interface do usuário desejada. Abaixo está um exemplo de declaração de view (descrição):

struct DemoView: View {
    @StateObject var store = Store()
    var body: some View {
        Text("Hello \\\\(store.username)")
    }
}

  • Crie uma instância de DemoView
  • Faça algum trabalho de preparação relacionado à exibição (por exemplo, injeção de dependência)
  • Avalie a propriedade do corpo da instância
  • Renderizar a visualização

A vida útil de uma exibição começa quando ela é carregada na hierarquia de exibição e termina quando é removida da hierarquia de exibição.

Durante a vida útil de uma exibição, seu valor mudará constantemente com base na fonte da verdade (várias fontes de dependência). O SwiftUI também criará constantemente novas instâncias da área que descrevem a exibição durante sua vida útil por vários motivos, a fim de garantir valores calculados precisos.

Como as instâncias são criadas repetidamente, os desenvolvedores devem usar identificadores específicos (@State, @StateObject etc.)

Quando uma exibição é carregada na hierarquia de exibição, o SwiftUI hospedará os estados que precisam ser vinculados (@State, @StateObject, onReceive etc.) em seu pool de dados gerenciado com base na instância usada no momento. Independentemente de quantas vezes a instância for recriada posteriormente, o SwiftUI usará apenas os estados criados durante a primeira criação. Isso significa que a tarefa de vincular estados a visualizações é executada apenas uma vez.

Leia o artigo SwiftUI View Lifecycle Study para saber mais sobre a relação entre visualizações e instâncias.

Wrappers de propriedade

Os wrappers de propriedade do Swift adicionam uma camada de separação entre o código que gerencia o armazenamento de propriedade e o código que define a propriedade. Por um lado, é conveniente para os desenvolvedores encapsular alguma lógica comum e aplicá-la a um determinado dado. Por outro lado, se os desenvolvedores não entenderem totalmente o propósito de um wrapper de propriedade, isso pode levar à inconsistência entre o que é percebido e o que realmente está acontecendo (mal-entendido).

Em muitos casos, precisamos entender os nomes dos wrappers de propriedade do SwiftUI de uma perspectiva de exibição, como:

  • ObservedObject (visualização se inscreve em um objeto observável)
  • StateObject (inscreve-se em um objeto observável e mantém uma forte referência a ele)
  • Estado (mantém um valor)

Leia o artigo “Evitando cálculos redundantes em visualizações SwiftUI” para obter mais detalhes sobre a implementação de DynamicProperty.

A razão para a ocorrência de fenômenos estranhos em ObservedObject

Se um código semelhante @ObservedObject var testObject = TestObject()for usado, fenômenos estranhos podem ocorrer às vezes.

O artigo “Investigating @StateObject” mostra um trecho de código que desencadeia fenômenos estranhos devido ao uso incorreto de ObservedObject.

Isso ocorre porque uma vez que o SwiftUI cria uma nova instância e a usa durante o tempo de vida da exibição (em alguns casos, criar uma nova instância não significa necessariamente usá-la), a instância TestObject originalmente criada será liberada (porque não há referência forte), e o relacionamento de assinatura mantido em ObservedObject também se tornará inválido.

Algumas exibições, talvez por causa de sua posição alta na árvore de exibição (como a exibição raiz), ou por causa de sua vida útil curta, ou porque são menos afetadas por outros estados, tendem a garantir que o SwiftUI crie apenas uma instância durante sua vida útil . Esta é também a razão pela qual @ObservedObject var testObject = TestObject()nem sempre falha.

Notas

  • Evite criar códigos como@ObservedObject var testObject = TestObject()
  • Evite criar códigos como@StateObject var testObject:TestObject
  • Use métodos construtores leves para tipos de referência usados ​​em exibições leves

Resumo

StateObject e ObservedObject são wrappers de propriedade que usamos com frequência e cada um deles tem seus próprios pontos fortes. Compreender suas conotações não apenas ajuda na seleção de cenários de aplicativos apropriados, mas também ajuda a dominar o mecanismo de longevidade das visualizações do SwiftUI.

Espero que este artigo tenha sido útil para você. Você também pode se comunicar comigo através do Twitter , do canal Discord ou do painel de comentários do meu blog.