I have created a custom propertyWrapper to inject my dependencies in code so when testing the code, I can pass a mock in place using the WritableKeyPath link to the object in memory.
This is how I would use it in production code. It is very convenient as I don't need to pass the object inside an initializer.
#Injected(\.child) var child
And this is how I would use it in my unit tests to pass the mock in place of the WritableKeyPath.
let parentMock = ParentMock()
InjectedDependency[\.parent] = parentMock
The thing is that in some part of the code where I am trying to use it, there seems to be ghost objects that are being created when the Child class would need to have access to the Parent class in a cycle. When I am making a look up and play with it in the Playground, I could have noticed that there are two objects created when linked to each others, and only one deallocation for each of them instead of two when setting the variable to nil.
How can I update my #propertyWrapper or what could be improved on this solution to make it work as expected? How come two objects are created instead of them making a references to the objects in memory?
So the use in code of this custom dependency injection tool is set below.
I have implemented the classic way with a weak var parent: Parent? to deallocate the object in memory with no issue to showcase what I was expected.
protocol ParentProtocol {}
class Parent: ParentProtocol {
//var child: Child?
#Injected(\.child) var child
init() { print("🔔 Allocating Parent in memory") }
deinit { print ("♻️ Deallocating Parent from memory") }
}
protocol ChildProtocol {}
class Child: ChildProtocol {
//weak var parent: Parent?
#Injected(\.parent) var parent
init() { print("🔔 Allocating Child in memory") }
deinit { print("♻️ Deallocating Child from memory") }
}
var mary: Parent? = Parent()
var tom: Child? = Child()
mary?.child = tom!
tom?.parent = mary!
// When settings the Parent and Child to nil,
// both are expected to be deallocating.
mary = .none
tom = .none
This is the response in the log when using the custom dependency injection solution.
🔔 Allocating Parent in memory
🔔 Allocating Child in memory
🔔 Allocating Child in memory // Does not appear when using the weak reference.
♻️ Deallocating Child from memory
🔔 Allocating Parent in memory // Does not appear when using the weak reference.
♻️ Deallocating Parent from memory
This is the implementation of my custom PropertyWrapper to handle the dependency injection following the keys of the Parent and the Child for the example of use.
// The key protocol for the #propertyWrapper initialization.
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value { get set }
}
// The main dependency injection custom tool.
#propertyWrapper
struct Injected<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T>
var wrappedValue: T {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T>) {
self.keyPath = keyPath
}
}
// The custom tool to use in unit tests to implement the mock
// within the associated WritableKeyPath.
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
// The Parent and Child keys to access the object in memory.
extension InjectedDependency {
var parent: ParentProtocol {
get { Self[ParentKey.self] }
set { Self[ParentKey.self] = newValue }
}
var child: ChildProtocol {
get { Self[ChildKey.self] }
set { Self[ChildKey.self] = newValue }
}
}
// The instantiation of the value linked to the key.
struct ParentKey: InjectedKeyProtocol {
static var currentValue: ParentProtocol = Parent()
}
struct ChildKey: InjectedKeyProtocol {
static var currentValue: ChildProtocol = Child()
}
Many changes, so just compare - in general we need to think about reference counting, ie. who keeps references... and so it works only for reference-types.
Tested with Xcode 13.3 / iOS 15.4
protocol ParentProtocol: AnyObject {}
class Parent: ParentProtocol {
//var child: Child?
#Injected(\.child) var child
init() { print("🔔 Allocating Parent in memory") }
deinit { print ("♻️ Deallocating Parent from memory") }
}
protocol ChildProtocol: AnyObject {}
class Child: ChildProtocol {
//weak var parent: Parent?
#Injected(\.parent) var parent
init() { print("🔔 Allocating Child in memory") }
deinit { print("♻️ Deallocating Child from memory") }
}
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value? { get set }
}
// The main dependency injection custom tool.
#propertyWrapper
struct Injected<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T?>
var wrappedValue: T? {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T?>) {
self.keyPath = keyPath
}
}
// The custom tool to use in unit tests to implement the mock
// within the associated WritableKeyPath.
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value? where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T?>) -> T? {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
// The Parent and Child keys to access the object in memory.
extension InjectedDependency {
var parent: ParentProtocol? {
get { Self[ParentKey.self] }
set { Self[ParentKey.self] = newValue }
}
var child: ChildProtocol? {
get { Self[ChildKey.self] }
set { Self[ChildKey.self] = newValue }
}
}
// The instantiation of the value linked to the key.
struct ParentKey: InjectedKeyProtocol {
static weak var currentValue: ParentProtocol?
}
struct ChildKey: InjectedKeyProtocol {
static weak var currentValue: ChildProtocol?
}
Output for test code:
Complete test module in project is here
Related
I have a class that implements an array of ObservableObjects that will save itself to persistent storage whenever one of the objects changes. Here is a very simplified version of the working code:
class MyObject : ObservableObject {
#Published var name: String = ""
// Lots of other #Published variables
}
class MyArray {
#Published var objects: [MyObject] = []
#Published private var objectWillChange: Void = ()
private var objectChanged: AnyCancellable?
init() {
loadFromStorage()
for object in objects {
object.objectWillChange.assign(to: &$objectWillChange)
}
objectChanged = $objectWillChange.dropFirst().sink() {
self.saveToStorage()
}
}
func loadFromStorage() {
// Loads objects from persistent storage
}
func saveToStorage() {
// Saves objects to persistent storage
}
}
This all works fine. Now I would like to create a generic version of this code, but I can't get it to compile.
Here is what I have so far
class MyPersistentArray: PersistentArray<MyObject> {
}
class PersistentArray<Object: ObservableObject> {
#Published var objects: [Object] = []
#Published private var objectWillChange: Void = ()
private var objectChanged: AnyCancellable?
init() {
loadFromStorage()
for object in objects {
object.objectWillChange.assign(to: &$objectWillChange) // DOES NOT COMPILE
}
objectChanged = $objectWillChange.dropFirst().sink() {
self.saveToStorage()
}
}
func loadFromStorage() {
// Loads objects from persistent storage
}
func saveToStorage() {
// Saves objects to persistent storage
}
}
In Xcode 14.2, the call to object.objectWillChange gives the following compiler error:
Cannot convert parent type 'Published<Void>' to expected type 'Published<Object.ObjectWillChangePublisher.Output>'
How can I fix this compiler error?
I will answer my own question. I figured out that I had to constrain the ObservableObject using a where clause to limit the type of ObjetWillChangePublisher.Output to Void, like this:
protocol PersistentObject: ObservableObject where ObjectWillChangePublisher.Output == Void {
}
class MyObject : PersistentObject {
#Published var name: String = ""
// Lots of other #Published variables
}
class MyPersistentArray: PersistentArray<MyObject> {
}
class PersistentArray<Object: PersistentObject> {
#Published var objects: [Object] = []
#Published private var objectWillChange: Void = ()
private var objectChanged: AnyCancellable?
init() {
loadFromStorage()
for object in objects {
object.objectWillChange.assign(to: &$objectWillChange)
}
objectChanged = $objectWillChange.dropFirst().sink() { _ in
self.saveToStorage()
}
}
func loadFromStorage() {
// Loads objects from persistent storage
}
func saveToStorage() {
// Saves objects to persistent storage
}
}
I'm trying to extend a protocol so that a certain few impls of the protocol have a view associated with them. However, because a SwiftUI View is a protocol, this is proving to be a challenge.
import SwiftUI
protocol ParentProtocol {
var anyProperty: String { get }
}
protocol ChildProtocol : ParentProtocol {
associatedtype V
var someView: V { get }
}
class ChildImpl : ChildProtocol {
var someView : some View {
Text("Hello World")
}
var anyProperty: String = ""
}
class ChildMgr {
var child: ParentProtocol = ChildImpl()
func getView() -> some View {
guard let child = child as? ChildProtocol else { return EmptyView() }
return child.someView
}
}
Its not clear to me where to constrain the ChildProtocol's associated type to a View (or Text for that matter).
At the guard let child = ... I get the following compiler error:
Protocol 'ChildProtocol' can only be used as a generic constraint because it has Self or associated type requirements
and when returning the chid's view I get:
Member 'someView' cannot be used on value of protocol type 'ChildProtocol'; use a generic constraint instead
I think the answer may be in this thread: https://developer.apple.com/forums/thread/7350
but frankly its confusing on how to apply it to this situation.
Don't use runtime checks. Use constrained extensions.
I also don't see a reason for you to be using classes.
protocol ChildProtocol: ParentProtocol {
associatedtype View: SwiftUI.View
var someView: View { get }
}
final class ChildImpl: ChildProtocol {
var someView: some View {
Text("Hello World")
}
var anyProperty: String = ""
}
final class ChildMgr<Child: ParentProtocol> {
var child: Child
init(child: Child) {
self.child = child
}
}
extension ChildMgr where Child: ChildProtocol {
func getView() -> some View {
child.someView
}
}
extension ChildMgr {
func getView() -> some View {
EmptyView()
}
}
extension ChildMgr where Child == ChildImpl {
convenience init() {
self.init(child: .init())
}
}
I'm using an approach similar to the one described on mockacoding - Dependency Injection in SwiftUI where my main ViewModel has the responsibility to create child viewModels.
In the code below I am not including the Factory, as it's very similar to the contents of the post above: it creates the ParentViewModel, passes to it dependencies and closures that construct the child view models.
struct Book { ... } // It's a struct, not a class
struct ParentView: View {
#StateObject var viewModel: ParentViewModel
var body: some View {
VStack {
if viewModel.book.bookmarked {
BookmarkedView(viewModel: viewModel.makeBookMarkedViewModel())
} else {
RegularView(viewModel: viewModel.makeBookMarkedViewModel())
}
}
}
}
class ParentViewModel: ObservableObject {
#Published var book: Book
// THIS HERE - This is how I am passing the #Published to #Binding
// Problem is I don't know if this is correct.
//
// Before, I was not using #Binding at all. All where #Published
// and I just pass the reference. But doing that would cause for
// the UI to NEVER update. That's why I changed it to use #Binding
private var boundBook: Binding<Book> {
Binding(get: { self.book }, set: { self.book = $0 })
}
// The Factory object passes down these closures
private let createBookmarkedVM: (_ book: Binding<Book>) -> BookmarkedViewModel
private let createRegularVM: (_ book: Binding<Book>) -> RegularViewModel
init(...) {...}
func makeBookmarkedViewModel() {
createBookmarkedVM(boundBook)
}
}
class BookmarkedView: View {
#StateObject var viewModel: BookmarkedViewModel
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
Text(book.title) // <---- THIS IS THE PROBLEM. Not being updated
Button("Remove bookmark") {
viewModel.removeBookmark()
}
}
.onReceive(timer) { _ in
print("adding letter") // <-- this gets called
withAnimation {
viewModel.addLetterToBookTitle()
}
}
}
}
class BookmarkedViewModel: ObservableObject {
#Binding var book: Book
// ... some other dependencies passed by the Factory object
init(...) { ... }
public func removeBookmark() {
// I know a class would be better than a struct, bear with me
book = Book(title: book.title, bookmarked: false)
}
/// Adds an "a" to the title
public func addLetterToBookTitle() {
book = Book(title: book.title + "a", bookmarked: book.bookmarked)
print("letter added") // <-- this gets called as well
}
}
From the code above, let's take a look at BookmarkedView. If I click the button and viewModel.removeBookmark() gets called, the struct is re-assigned and ParentView now renders RegularView.
This tells me that I successfully bound #Published book: Book from ParentViewModel to #Binding book: Book from BookmarkedViewModel, through its boundBook computed property. This felt like the most weird thing I had to make.
However, the problem is that even though addLetterToBookTitle() is also re-assigning the book with a new title, and it should update the Text(book.title), it's not happening. The same title is being displayed.
I can guarantee that the book title has change (because of some other components of the app I'm omitting for simplicity), but the title's visual is not being updated.
This is the first time I'm trying out these pattern of having a view model build child view models, so I appreciate I may be missing something fundamental. What am I missing?
EDIT:
I made an MVP example here: https://github.com/christopher-francisco/TestMVVM/tree/main/MVVMTest.xcodeproj
I'm looking for whether:
My take at child viewmodels is fundamentally wrong and I should start from scratch, or
I have misunderstood #Binding and #Published attributes, or
Anything really
Like I said initially #Binding does not work in a class you have to use .sink to see the changes to an ObservableObject.
See below...
class MainViewModel: ObservableObject {
#Published var timer = YourTimer()
let store: Store
let nManager: NotificationManager
let wManager: WatchConnectionManager
private let makeNotYetStartedViewModelClosure: (_ parentVM: MainViewModel) -> NotYetStartedViewModel
private let makeStartedViewModelClosure: (_ parentVM: MainViewModel) -> StartedViewModel
init(
store: Store,
nManager: NotificationManager,
wManager: WatchConnectionManager,
makeNotYetStartedViewModel: #escaping (_ patentVM: MainViewModel) -> NotYetStartedViewModel,
makeStartedViewModel: #escaping (_ patentVM: MainViewModel) -> StartedViewModel
) {
self.store = store
self.nManager = nManager
self.wManager = wManager
self.makeNotYetStartedViewModelClosure = makeNotYetStartedViewModel
self.makeStartedViewModelClosure = makeStartedViewModel
}
}
// MARK: - child View Models
extension MainViewModel {
func makeNotYetStartedViewModel() -> NotYetStartedViewModel {
self.makeNotYetStartedViewModelClosure(self)
}
func makeStartedViewModel() -> StartedViewModel {
self.makeStartedViewModelClosure(self)
}
}
class NotYetStartedViewModel: ObservableObject {
var parentVM: MainViewModel
var timer: YourTimer{
get{
parentVM.timer
}
set{
parentVM.timer = newValue
}
}
var cancellable: AnyCancellable? = nil
init(parentVM: MainViewModel) {
self.parentVM = parentVM
//Subscribe to the parent
cancellable = parentVM.objectWillChange.sink(receiveValue: { [self] _ in
//Trigger reload
objectWillChange.send()
})
}
func start() {
// I'll make this into a class later on
timer = YourTimer(remainingSeconds: timer.remainingSeconds, started: true)
}
}
class StartedViewModel: ObservableObject {
var parentVM: MainViewModel
var timer: YourTimer{
get{
parentVM.timer
}
set{
parentVM.timer = newValue
}
}
var cancellable: AnyCancellable? = nil
init(parentVM: MainViewModel) {
self.parentVM = parentVM
cancellable = parentVM.objectWillChange.sink(receiveValue: { [self] _ in
//trigger reload
objectWillChange.send()
})
}
func tick() {
// I'll make this into a class later on
timer = YourTimer(remainingSeconds: timer.remainingSeconds - 1, started: timer.started)
}
func cancel() {
timer = YourTimer()
}
}
But this is an overcomplicated setup, stick to class or struct. Also, maintain a single source of truth. That is basically the center of how SwiftUI works everything should be getting its value from a single source.
From within a property wrapper in Swift, can you someone refer back to the instance of the class or struck that owns the property being wrapped? Using self doesn't obviously work, nor does super.
I tried to pass in self to the property wrapper's init() but that doesn't work either because self on Configuration is not yet defined when #propertywrapper is evaluated.
My use case is in a class for managing a large number of settings or configurations. If any property is changed, I just want to notify interested parties that something changed. They don't really need to know which value just, so use something like KVO or a Publisher for each property isn't really necessary.
A property wrapper looks ideal, but I can't figure out how to pass in some sort of reference to the owning instance that the wrapper can call back to.
References:
SE-0258
enum PropertyIdentifier {
case backgroundColor
case textColor
}
#propertyWrapper
struct Recorded<T> {
let identifier:PropertyIdentifier
var _value: T
init(_ identifier:PropertyIdentifier, defaultValue: T) {
self.identifier = identifier
self._value = defaultValue
}
var value: T {
get { _value }
set {
_value = newValue
// How to callback to Configuration.propertyWasSet()?
//
// [self/super/...].propertyWasSet(identifier)
}
}
}
struct Configuration {
#Recorded(.backgroundColor, defaultValue:NSColor.white)
var backgroundColor:NSColor
#Recorded(.textColor, defaultValue:NSColor.black)
var textColor:NSColor
func propertyWasSet(_ identifier:PropertyIdentifier) {
// Do something...
}
}
The answer is no, it's not possible with the current specification.
I wanted to do something similar. The best I could come up with was to use reflection in a function at the end of init(...). At least this way you can annotate your types and only add a single function call in init().
fileprivate protocol BindableObjectPropertySettable {
var didSet: () -> Void { get set }
}
#propertyDelegate
class BindableObjectProperty<T>: BindableObjectPropertySettable {
var value: T {
didSet {
self.didSet()
}
}
var didSet: () -> Void = { }
init(initialValue: T) {
self.value = initialValue
}
}
extension BindableObject {
// Call this at the end of init() after calling super
func bindProperties(_ didSet: #escaping () -> Void) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? BindableObjectPropertySettable {
child.didSet = didSet
}
}
}
}
You cannot do this out of the box currently.
However, the proposal you refer to discusses this as a future direction in the latest version:
https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
For now, you would be able to use a projectedValue to assign self to.
You could then use that to trigger some action after setting the wrappedValue.
As an example:
import Foundation
#propertyWrapper
class Wrapper {
let name : String
var value = 0
weak var owner : Owner?
init(_ name: String) {
self.name = name
}
var wrappedValue : Int {
get { value }
set {
value = 0
owner?.wrapperDidSet(name: name)
}
}
var projectedValue : Wrapper {
self
}
}
class Owner {
#Wrapper("a") var a : Int
#Wrapper("b") var b : Int
init() {
$a.owner = self
$b.owner = self
}
func wrapperDidSet(name: String) {
print("WrapperDidSet(\(name))")
}
}
var owner = Owner()
owner.a = 4 // Prints: WrapperDidSet(a)
My experiments based on : https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
protocol Observer: AnyObject {
func observableValueDidChange<T>(newValue: T)
}
#propertyWrapper
public struct Observable<T: Equatable> {
public var stored: T
weak var observer: Observer?
init(wrappedValue: T, observer: Observer?) {
self.stored = wrappedValue
}
public var wrappedValue: T {
get { return stored }
set {
if newValue != stored {
observer?.observableValueDidChange(newValue: newValue)
}
stored = newValue
}
}
}
class testClass: Observer {
#Observable(observer: nil) var some: Int = 2
func observableValueDidChange<T>(newValue: T) {
print("lol")
}
init(){
_some.observer = self
}
}
let a = testClass()
a.some = 4
a.some = 6
The answer is yes! See this answer
Example code for calling ObservableObject publisher with a UserDefaults wrapper:
import Combine
import Foundation
class LocalSettings: ObservableObject {
static var shared = LocalSettings()
#Setting(key: "TabSelection")
var tabSelection: Int = 0
}
#propertyWrapper
struct Setting<T> {
private let key: String
private let defaultValue: T
init(wrappedValue value: T, key: String) {
self.key = key
self.defaultValue = value
}
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
public static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, T>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Setting<T>>
) -> T {
get {
return object[keyPath: storageKeyPath].wrappedValue
}
set {
(object.objectWillChange as? ObservableObjectPublisher)?.send()
UserDefaults.standard.set(newValue, forKey: object[keyPath: storageKeyPath].key)
}
}
}
This is related to this question. Using typealias I can create a class that has a "children" var but with a specific type, which is what I'm looking for. But when I do that I can no longer test to see if an instance follows that protocol. I have also tried 'if let' and that doesn't work. Is there any way to do this?
protocol Parent {
typealias Child
var children: [Child] { get set }
}
protocol Child {
typealias Parent
var parent: Parent { get set }
}
class Foo: Parent {
var children = [Bar]()
init(){}
}
class Bar: Child {
var parent = Foo()
}
let testVar = Foo()
let cVar = Bar()
testVar.children.append(cVar)
cVar.parent = testVar
//I get the error here saying protocol is limited to generic constraints
if testVar is Parent {
}
Unfortunately there is no way to solve this statically (at least for now). So a more dynamic approach would be to have a "super" protocol which has no self associated type requirements. This allows you to cast between these "super" protocols:
protocol _Parent {
var anyChildren: [Any] { get set }
}
protocol Parent: _Parent {
typealias Child
var children: [Child] { get set }
}
extension Parent {
// requirement of _Parent
var anyChildren: [Any] {
get {
return children.map{ $0 as Any }
}
set {
// use `as!` and `map` if you are sure that all elements are of type Child
// or if you want a crash if not
children = newValue.flatMap{ $0 as? Child }
}
}
}
protocol _Child {
var anyParent: Any { get set }
}
protocol Child: _Child {
typealias Parent
var parent: Parent { get set }
}
extension Child {
// requirement of _Child
var anyParent: Any {
get {
return parent
}
set {
parent = newValue as! Parent
}
}
}
Now you can use their properties to manipulate the data:
class Foo: Parent {
var children = [Foo]()
}
let anything: Any = Foo()
// use `if let` and `as?` instead of `is` in order to have a more static type
if let aDynamicParent = anything as? _Parent {
aDynamicParent.anyChildren.map{ $0 as! Foo } // cast the array back to its original type
aDynamicParent.children = [Foo(), Foo(), Foo()]
}
You don't need Child and Parent typealiases if Parent contains array of objects conforming Child protocol and Child contains Parent protocol property.
protocol Parent {
var children: [Child] { get set }
}
protocol Child {
var parent: Parent { get nonmutating set }
}
class Foo: Parent {
var children: [Child]
init(children: [Child]) {
self.children = children
self.children.forEach { $0.parent = self }
}
}
class Bar: Child {
var parent: Parent
init(parent: Parent) {
self.parent = parent
self.parent.children.append(self)
}
}
let testVar = Foo(children: [Bar]())
let cVar = Bar(parent: testVar)
let parent = testVar as Parent
let children = parent.children