Protocols and @ObservedObject properties
The @ObservedObject
property wrapper can only be applied to properties that are classes. If you attempt to create a property that has a protocol as a type and you want this property to be wrapped in a @ObservedObject
property wrapper you’ll get a compilation error message when you try to compile.
The protocol & view:
protocol Searchable {
var searchResults: [String] { get }
func search()
}
class LocalSearch : Searchable, ObservableObject {
@Published private(set) var searchResults: [String] = []
func search() {
searchResults = ["Hello", "World"]
}
}
The view referencing a class as an @ObservedObject
property
struct ProtocolObservableObjectView: View {
@ObservedObject var service: LocalSearch
var body: some View {
VStack {
Text("ProtocolObservableObjectView").font(.largeTitle)
Button(action: { self.service.search() }) {
Text("Search")
}
if (self.service.searchResults.count > 0) {
Text("Search Results")
// https://www.hackingwithswift.com/articles/210/how-to-fix-slow-list-updates-in-swiftui
List(service.searchResults, id: \.self) {
Text("\($0)")
}
.id(UUID()) // crashes if this is missing
}
}
}
}
The view uses the property wrapper to define the service as a class. If we wanted to define the service as a protocol instead, by changing
@ObservedObject var service: LocalSearch
to
@ObservedObject var service: Searchable
you would get the following error:
Property type ‘Searchable’ does not match that of the ‘wrappedValue’ property of its wrapper type ‘ObservedObject’
The reason for this is that the ObservableObject
protocol can only be annotated to class properties, not struct
or protocol
types. If you made LocalSearch
a struct
instead, you’d get the following error during compilation
Non-class type ‘SwiftSection.LocalSearch’ cannot conform to class protocol ‘ObservableObject’
Solution
To specify a protocol as a type that is to be wrapped in an @ObservedObject
property wrapper, you can modify the view to use a generic type that is constrained to the protocol.
- The protocol should adopt
ObservableObject
- Make the view specialized by a generic type that adopts your protocol.
- The type of the object that the property wrapper wraps should be the generic type
- Add an
init
method that sets the wrapped property to the input parameter that is typed as the generic type
protocol Searchable: ObservableObject {
var searchResults: [String] { get }
func search()
}
class LocalSearch : Searchable {
@Published private(set) var searchResults: [String] = []
func search() {
searchResults = ["LocalSearch"]
}
}
class RemoteSearch : Searchable {
@Published private(set) var searchResults: [String] = []
func search() {
searchResults = ["RemoteSearch"]
}
}
struct ProtocolObservableObjectView<Service: Searchable>: View {
@ObservedObject var service: Service
init(service: Service) {
self.service = service
}
var body: some View {
VStack {
Text("ProtocolObservableObjectView").font(.largeTitle)
Button(action: { self.service.search() }) {
Text("Search")
}
if (self.service.searchResults.count > 0) {
Text("Search Results")
// https://www.hackingwithswift.com/articles/210/how-to-fix-slow-list-updates-in-swiftui
List(service.searchResults, id: \.self) {
Text("\($0)")
}
.id(UUID()) // crashes if this is missing
}
}
}
}
The caller can now pass to the view adopters of Searchable
, either LocalSearch
or RemoteSearch
.