Se você usou o SwiftUI, provavelmente prestou atenção a palavras-chave como @ObservedObject, @EnvironmentObject, @FetchRequest e assim por diante. Wrappers de propriedades (doravante referidos como "wrappers de propriedades") são um novo recurso do Swift 5.1. Este artigo o ajudará a entender de onde vêm todas as construções do @, como usá-las no SwiftUI e em seus projetos.

Traduzido por: Evgeny Zavozhansky, desenvolvedor da FunCorp.
Nota: Quando a tradução foi preparada, parte do código-fonte do artigo original havia perdido sua relevância devido a alterações no idioma; portanto, alguns exemplos de código foram intencionalmente substituídos.
Os invólucros de propriedades foram introduzidos nos fóruns da Swift em março de 2019, alguns meses antes do anúncio da SwiftUI. Em sua proposta original, Douglas Gregor, membro da equipe Swift Core, descreveu esse construto (então chamado de delegados de propriedades) como "uma generalização acessível ao usuário da funcionalidade atualmente fornecida por um construto de linguagem como o lazy
, por exemplo".
Se uma propriedade for declarada com a palavra-chave lazy
, isso significa que será inicializada na primeira vez que for acessada. Por exemplo, a inicialização da propriedade adiada pode ser implementada usando uma propriedade privada, acessada por meio de uma propriedade computada. Mas o uso da palavra-chave lazy
torna isso muito mais fácil.
struct Structure {
SE-0258: O Wrapper de propriedades explica perfeitamente o design e a implementação dos wrappers de propriedades. Portanto, em vez de tentar melhorar a descrição na documentação oficial, considere alguns exemplos que podem ser implementados usando wrappers de propriedades:
- restrição de valores de propriedade;
- conversão de valores ao alterar propriedades;
- mudança da semântica da igualdade e comparação de propriedades;
- registro de acesso à propriedade.
Limitar valores de propriedade
SE-0258: O Wrapper de propriedades fornece vários exemplos práticos, incluindo @Clamping
, @Copying
, @Atomic
, @ThreadSpecific
, @Box
, @UserDefault
. Considere o wrapper @Clamping
, que permite limitar o valor máximo ou mínimo de uma propriedade.
@propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(initialValue value: Value, _ range: ClosedRange<Value>) { precondition(range.contains(value)) self.value = value self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } }
@Clamping
pode ser usado, por exemplo, para simular a acidez de uma solução, cujo valor pode levar de 0 a 14.
struct Solution { @Clamping(0...14) var pH: Double = 7.0 } let carbonicAcid = Solution(pH: 4.68)
Tentar definir o valor do pH além da faixa de (0...14)
fará com que a propriedade obtenha o valor mais próximo do intervalo mínimo ou máximo.
let superDuperAcid = Solution(pH: -1) superDuperAcid.pH
Os invólucros de propriedades podem ser usados para implementar outros invólucros de propriedades. Por exemplo, o wrapper @UnitInterval
limita o valor de uma propriedade ao intervalo (0...1)
usando @Clamping(0...1)
:
@propertyWrapper struct UnitInterval<Value: FloatingPoint> { @Clamping(0...1) var wrappedValue: Value = .zero init(initialValue value: Value) { self.wrappedValue = value } }
Idéias semelhantes
@Positive
/ @NonNegative
indica que o valor pode ser um número positivo ou negativo.@NonZero
indica que o valor da propriedade não pode ser 0.@Validated
ou @Whitelisted
/ @Blacklisted
restringe o valor de uma propriedade a determinados valores.
Convertendo valores ao alterar propriedades
A validação dos valores do campo de texto é uma dor de cabeça constante para desenvolvedores de aplicativos. Há muitas coisas a serem rastreadas: desde banalidades, como codificação, até tentativas maliciosas de inserir um código através de um campo de texto. Considere usar um wrapper de propriedade para remover os espaços que um usuário inseriu no início e no final de uma linha.
import Foundation let url = URL(string: " https:
Foundation
oferece o método trimmingCharacters(in:)
, com o qual você pode remover espaços no início e no final de uma linha. Você pode chamar esse método sempre que precisar garantir a correção da entrada, mas isso não é muito conveniente. Você pode usar o wrapper de propriedade para isso.
import Foundation @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { return value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(initialValue: String) { self.wrappedValue = initialValue } }
struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "…") quine.title
Idéias semelhantes
@Transformed
aplica a conversão ICU à string de @Transformed
.@Rounded
/ @Truncated
arredonda ou trunca um valor de sequência.
Alterar semântica da igualdade e comparação de propriedades
No Swift, duas cadeias são iguais se forem canonicamente equivalentes , ou seja, contém os mesmos caracteres. Mas suponha que queremos que as propriedades da string sejam iguais, sem distinção entre maiúsculas e minúsculas, que elas contêm.
@CaseInsensitive
implementa um wrapper para propriedades do tipo String
ou SubString
.
import Foundation @propertyWrapper struct CaseInsensitive<Value: StringProtocol> { var wrappedValue: Value } extension CaseInsensitive: Comparable { private func compare(_ other: CaseInsensitive) -> ComparisonResult { wrappedValue.caseInsensitiveCompare(other.wrappedValue) } static func == (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool { lhs.compare(rhs) == .orderedSame } static func < (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool { lhs.compare(rhs) == .orderedAscending } static func > (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool { lhs.compare(rhs) == .orderedDescending } }
let hello: String = "hello" let HELLO: String = "HELLO" hello == HELLO
Idéias semelhantes
@Approximate
para uma comparação aproximada das propriedades do tipo Double ou Float.@Ranked
propriedades cujos valores estão em ordem (por exemplo, a classificação das cartas de jogar).
Registro de acesso à propriedade
@Versioned
permitirá que você intercepte os valores atribuídos e lembre-se de quando eles foram definidos.
import Foundation @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { defer { timestampedValues.append((Date(), value)) } value = newValue } } init(initialValue value: Value) { self.wrappedValue = value } }
A classe ExpenseReport
permite salvar registros de data e hora dos estados de processamento do relatório de despesas.
class ExpenseReport { enum State { case submitted, received, approved, denied } @Versioned var state: State = .submitted }
Mas o exemplo acima demonstra uma limitação séria na implementação atual dos wrappers de propriedades, que se segue da restrição Swift: as propriedades não podem lançar exceções. Se quisermos adicionar uma restrição a @Versioned
para impedir que o valor seja alterado para .approved
depois que o valor .denied
, a melhor opção é fatalError()
, que não é adequado para aplicativos reais.
class ExpenseReport { @Versioned var state: State = .submitted { willSet { if newValue == .approved, $state.timestampedValues.map { $0.1 }.contains(.denied) { fatalError("") } } } } var tripExpenses = ExpenseReport() tripExpenses.state = .denied tripExpenses.state = .approved
Idéias semelhantes
@Audited
para @Audited
acesso à propriedade.@UserDefault
para encapsular o mecanismo para ler e salvar dados em UserDefaults
.
Limitações
Propriedades não podem lançar exceções
Como já mencionado, os wrappers de propriedades podem usar apenas alguns métodos para processar valores inválidos:
- ignore-os;
- finalize o aplicativo usando fatalError ().
Propriedades agrupadas não podem ser marcadas com o atributo `typealias`
O exemplo @UnitInterval
acima, cuja propriedade é limitada pelo intervalo (0...1)
, não pode ser declarado como
typealias UnitInterval = Clamping(0...1)
Restrição ao uso de uma composição de vários wrappers de propriedades
A composição de wrappers de propriedades não é uma operação comutativa: a ordem da declaração afetará o comportamento. Considere um exemplo em que a propriedade slug, que é o URL de uma postagem de blog, é normalizada. Nesse caso, o resultado da normalização variará dependendo de quando os espaços forem substituídos por hífens, antes ou depois da remoção dos espaços. Portanto, no momento, uma composição de vários wrappers de propriedades não é suportada.
@propertyWrapper struct Dasherized { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.replacingOccurrences(of: " ", with: "-") } } init(initialValue: String) { self.wrappedValue = initialValue } } struct Post { … @Dasherized @Trimmed var slug: String
No entanto, essa limitação pode ser contornada usando wrappers de propriedades aninhados.
@propertyWrapper struct TrimmedAndDasherized { @Dasherized private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(initialValue: String) { self.wrappedValue = initialValue } } struct Post { … @TrimmedAndDasherized var slug: String }
Outras restrições de wrapper de propriedade
- Não pode ser usado dentro do protocolo.
- Uma instância de propriedade do wrapper não pode ser declarada em
enum
. - Uma propriedade quebrada declarada dentro de uma classe não pode ser substituída por outra propriedade.
- Uma propriedade
@NSCopying
não pode ser lazy
, @NSCopying
, @NSManaged
, weak
ou unowned
. - Uma propriedade agrupada deve ser a única dentro de sua definição (ou seja,
@Lazy var (x, y) = /* ... */
). - Uma propriedade
getter
não pode ter getter
e setter
definidos. - Os tipos da propriedade
wrappedValue
e a variável wrappedValue
em init(wrappedValue:)
devem ter o mesmo nível de acesso que o tipo de wrapper da propriedade. - A propriedade type de
projectedValue
deve ter o mesmo nível de acesso que o tipo de wrapper da propriedade. init()
deve ter o mesmo nível de acesso que o tipo de wrapper de propriedade.
Vamos resumir. Os wrappers de propriedades no Swift fornecem aos autores da biblioteca acesso ao comportamento de alto nível anteriormente reservado para funções de idioma. Seu potencial para melhorar a legibilidade e reduzir a complexidade do código é enorme, e apenas examinamos superficialmente os recursos dessa ferramenta.
Você usa wrappers de propriedade em seus projetos? Escreva nos comentários!