Created
July 1, 2020 13:39
-
-
Save dtrofimov/1d84abb80314fa81aa50ea7ee5c9788a to your computer and use it in GitHub Desktop.
A replacement for ObservableObject & ObservedObject, allowing the view model protocol usage as a concrete type
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ObservableModel.swift | |
// Rivora | |
// | |
// Created by Dmitrii Trofimov on 26.03.2020. | |
// Copyright © 2020 Dmitrii Trofimov. All rights reserved. | |
// | |
import Foundation | |
import Combine | |
import SwiftUI | |
/** | |
A protocol with requirements equal to `ObservableObject`, but without `associatedtype` requirements, | |
which allows to use it as a concrete type, as well as any inherited protocol (e. g. some view model protocol). | |
Example: | |
``` | |
protocol DemoViewModel: ObservableModel { | |
var description: String { get } | |
} | |
class DemoViewModelImpl: DemoViewModel, ObservableObject { | |
@Published var description: String = "Some description" | |
} | |
``` | |
*/ | |
public protocol ObservableModel: AnyObject { | |
var objectWillChange: ObservableObjectPublisher { get } | |
} | |
/** | |
A property wrapper functionally equivalent to `ObservedObject`, but not explicitly requiring any protocol conformance. | |
Instead, it checks conformance to `ObservableModel` in runtime at initialization moment. | |
This allows to use a protocol (some view model protocol inherited from `ObservableModel`) as a concrete type to store an actual model | |
and to uncouple it from a concrete view model implementation. | |
Example: | |
``` | |
struct DemoView: View { | |
@ObservedModel var model: DemoViewModel | |
var body: some View { | |
Text(model.description) | |
} | |
} | |
``` | |
*/ | |
@propertyWrapper | |
public struct ObservedModel<Wrapped>: DynamicProperty { | |
public let wrappedValue: Wrapped | |
private class Observer: ObservableObject { | |
@Published var value: () = () | |
} | |
@ObservedObject private var observer: Observer | |
private let disposable: AnyCancellable | |
public init(wrappedValue: Wrapped) { | |
let observer = Observer() | |
self.observer = observer | |
self.wrappedValue = wrappedValue | |
guard let observable = wrappedValue as? ObservableModel else { | |
fatalError("ObservedModel should be used with ObservableModel conforming types only") | |
} | |
self.disposable = observable.objectWillChange.sink { value in | |
observer.value = () | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment