How to create a generic persistent array of ObservableObjects in Swift - swift

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
}
}

Related

How to pass variable between multiple classes and views in Swift?

I have one View and two classes (Cls1 and Cls2).
Object of Class1 initialised in view like:
#ObservedObject var cls1 = Cls1()
And I have no problems to get variables of clsObj declaring them inside class as:
#Published var myVarFromCls1 = false
However, I have Cls2, it was initialised inside Cls1:
var cls2 = Cls2()
and I need to get changes in variables of cls2 inside my view.
I've tried to redeclare cls2 initialization to:
#Published var cls2 = Cls2()
But I can't get its variable in view using:
#ObservedObject var cls1 = Cls1()
$cls1.cls2.myVarFromCls2
declared in Class2:
#Published var myVarFromCls2 = false
How to do it right way?
At the end of Cls1's initializer, add:
cancellable = cls2.objectWillChange.forwardedThroughObjectWillChange(of: self)
}
private var cancellable: AnyCancellable!
import Combine
public extension Publisher<Void, Never> {
/// Forward output through an `ObservableObject`'s `objectWillChange`.
func forwardedThroughObjectWillChange<Object: ObservableObject>(of object: Object) -> AnyCancellable
where Object.ObjectWillChangePublisher == ObservableObjectPublisher {
sink { [unowned object] in object.objectWillChange.send() }
}
}
You can use the same idea for e.g. "[some ObservableObject]" situations.
import Combine
import SwiftUI
#propertyWrapper
public final class ObservableObjects<Objects: Sequence>: ObservableObject
where
Objects.Element: ObservableObject,
Objects.Element.ObjectWillChangePublisher == ObservableObjectPublisher
{
public init(wrappedValue: Objects) {
self.wrappedValue = wrappedValue
assignCancellable()
}
#Published public var wrappedValue: Objects {
didSet { assignCancellable() }
}
private var cancellable: AnyCancellable!
}
// MARK: - private
private extension ObservableObjects {
func assignCancellable() {
cancellable = Publishers.MergeMany(wrappedValue.map(\.objectWillChange))
.forwardedThroughObjectWillChange(of: self)
}
}
// MARK: -
#propertyWrapper
public struct ObservedObjects<Objects: Sequence>: DynamicProperty
where
Objects.Element: ObservableObject,
Objects.Element.ObjectWillChangePublisher == ObservableObjectPublisher
{
public init(wrappedValue: Objects) {
_objects = .init(
wrappedValue: .init(wrappedValue: wrappedValue)
)
}
public var wrappedValue: Objects {
get { objects.wrappedValue }
nonmutating set { objects.wrappedValue = newValue }
}
public var projectedValue: Binding<Objects> { $objects.wrappedValue }
#ObservedObject private var objects: ObservableObjects<Objects>
}
#propertyWrapper
public struct StateObjects<Objects: Sequence>: DynamicProperty
where
Objects.Element: ObservableObject,
Objects.Element.ObjectWillChangePublisher == ObservableObjectPublisher
{
public init(wrappedValue: Objects) {
_objects = .init(
wrappedValue: .init(wrappedValue: wrappedValue)
)
}
public var wrappedValue: Objects {
get { objects.wrappedValue }
nonmutating set { objects.wrappedValue = newValue }
}
public var projectedValue: Binding<Objects> { $objects.wrappedValue }
#StateObject private var objects: ObservableObjects<Objects>
}

How to avoid a retain cycle on my custom dependency injection tool?

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

Is it common to have only one ViewModel to manage all CoreData entities - Core Data + MVVM + SwiftUI

Is it common to have only one ViewModel to manage all CoreData entities?
For instance, in the following example, I have three Core Data entities, Car, CarService and ServiceRecord where Car has many carServices and each CarService has many serviceRecords. Everything is working fine but I feel like my CarViewModel file is growing and growing and I'm not sure if this is really a good MVVM practice.
As you can see in the following example I'm using CarViewModel to fetch data from Core Data and passing it around SwiftUI views. Again, everything is working fine but I feel like I'm missing something.
Can someone please share how you usually structure your code when using MVVM + CoreData + SwiftUI. Do you handle everything from one ViewModel as shown below or do you usually have a ViewModel for each entity? If a viewModel per each entity is the best option, what method do you use to pass viewModels around SwiftUI views?
CoreDataManager
class CoreDataManager{
static let instance = CoreDataManager()
lazy var context: NSManagedObjectContext = {
return container.viewContext
}()
lazy var container: NSPersistentContainer = {
return setupContainer()
}()
func setupContainer()->NSPersistentContainer{
// code to setup container...
return container
}
func save(){
do{
try context.save()
}catch let error{
print("Error saving Core Data. \(error.localizedDescription)")
}
}
}
CarViewModel
class CarViewModel: ObservableObject{
let manager: CoreDataManager
#Published var cars: [Car] = []
#Published var carServices: [CarService] = []
#Published var serviceRecords: [ServiceRecord] = []
init(coreDataManager: CoreDataManager = .instance){
self.manager = coreDataManager
// getCars() etc.
}
// CREATIONS
func addCar(name:String){}
func addService(name:String, cost: Double){}
func createRecord(name:String, cost: Double){}
// DELETES
func deleteCar(){}
func deleteCarService(){}
func deleteServiceRecord(){}
// UPDATES
func updateCar(){}
func updateService(){}
// GETS
func getCars(){}
func getServices(){}
func getRecords(){}
func save(){
self.manager.save()
}
}
SwiftUI Views
CarsView
struct CarsView: View {
#StateObject var carViewModel = CarViewModel()
var body: some View {
NavigationView{
VStack{
List {
ForEach(carViewModel.cars) { car in
}
}
}
}
}
}
ServicesView
struct ServicesView: View {
#ObservedObject var carViewModel:CarViewModel
var body: some View {
NavigationView{
VStack{
List {
ForEach(carViewModel.carServices) { service in
}
}
}
}
}
}
RecordsView
struct RecordsView: View {
#ObservedObject var carViewModel: CarViewModel
var body: some View {
NavigationView{
VStack{
List {
ForEach(carViewModel.serviceRecords) { record in
}
}
}
}
}
}
Personally I would create a service file which holds all the functions to a given model. I would then only expose the functions in my viewModel that my viewController needs.
For example:
CarService.swift
class CarService {
func addCar(name:String) {}
func addService(name:String, cost: Double) {}
func createRecord(name:String, cost: Double) {}
// DELETES
func deleteCar() {}
func deleteCarService() {}
func deleteServiceRecord() {}
// UPDATES
func updateCar() {}
func updateService() {}
// GETS
func getCars() {}
func getServices() {}
func getRecords() {}
}
CarViewModel.swift
protocol CarViewModelType {
func addCar(name: String)
func deleteCar()
}
class CarViewModel: CarViewModelType {
var carService: CarService
init(service: CarService) {
self.carService = service
}
func addCar(name: String) {
self.carService.addCar(name: name)
}
func deleteCar() {
self.carService.deleteCar()
}
}
CarViewController.swift
class CarViewController: UIViewController {
var viewModel: CarViewModelType!
}
This is of course just one way to go about it, I believe there is no 'right' way to structure your code.
Hope it helps

#ObservedObject not updating after successful network call

I've hit a brick wall in my widget extension. I'm using AlamoFire and ObjectMapper to match the networking we have in the main app. I can tell that my AlamoFire network call is getting triggered and that I'm getting results back, and in the correct, expected format. However, saving the response of that network call to a #Published var doesn't seem to be working. My view and models/structs are below:
struct WidgetEntryView: View {
var entry: ResourceCategoryEntry
#ObservedObject var viewModel = WidgetResourcesView(widgetSize: .medium)
var body: some View {
if UserDefaults.forAppGroup.object(forKey: "sessionToken") as? String == nil {
PleaseLogIn()
} else if viewModel.mediumResources.count < 1 {
ErrorScreen()
} else {
MediumResourcesView(resources: viewModel.mediumResources)
}
}
}
class WidgetResourcesView: ObservableObject {
#Published var resourceGroups: [WidgetResouceGroup] = [WidgetResouceGroup]()
var widgetSize: WidgetSize = .small
var selectedCategory: String?
init(widgetSize: WidgetSize) {
self.widgetSize = widgetSize
self.selectedCategory = UserDefaults.forAppGroup.string(forKey: ResourceCategoryEntry.userDefaultKey)
getResources()
}
func getResources() {
WidgetNetworkService.getResources(widgetSize: self.widgetSize.rawValue, selectedCategory: self.selectedCategory) { resourceGroups in
DispatchQueue.main.async {
self.resourceGroups = resourceGroups
}
} failure: { _ in
print("Error Received")
}
}
var mediumResources: [WidgetResource] {
var resources = [WidgetResource]()
if let featuredResourceGroup = resourceGroups.featuredResourceGroup {
for resource in featuredResourceGroup.resources { resources.append(resource) }
}
if let nonFeaturedResourceGroup = resourceGroups.nonFeaturedResourceGroup {
for resource in nonFeaturedResourceGroup.resources { resources.append(resource) }
}
return resources
}
}
class WidgetResouceGroup: NSObject, Mappable, Identifiable {
var id = UUID()
var widgetCategory: WidgetCategory = .featured
var resources = [WidgetResource]()
required init?(map: Map) {}
func mapping(map: Map) {
id <- map["section"]
widgetCategory <- map["section"]
resources <- map["resources"]
}
}
typealias WidgetResourceGroupCollection = [WidgetResouceGroup]
extension WidgetResourceGroupCollection {
var featuredResourceGroup: WidgetResouceGroup? {
return first(where: {$0.widgetCategory == .featured})
}
var nonFeaturedResourceGroup: WidgetResouceGroup? {
return first(where: {$0.widgetCategory != .featured})
}
}
class WidgetResource: NSObject, Mappable, Identifiable {
enum ResourceType: String {
case text = "text"
case audio = "audio"
case video = "video"
}
var id = 0
var title = ""
var imageInfo: WidgetImageInfo?
var resourceType: ResourceType = .text
required init?(map: Map) {}
func mapping(map: Map) {
id <- map["object_id"]
title <- map["title"]
imageInfo <- map["image_info"]
resourceType <- map["content_type"]
}
}
You can use the objectWillChange - Property in your observable object to specifiy when the observable object should be refreshed.
Apple Dev Doku
Example by Paul Hudson
WidgetEntryView instantiates WidgetResourcesView using the ObservedObject wrapper. This causes a new instance of WidgetResourcesView to be instantiated again on every refresh. Try switching that to StateObject, and the original object will be kept in memory between view updates. I believe this is the only change needed, but I’m away so can’t test it!

Manage single variable for two different types of data

I have two UI of listing, the cells are the same in both listing. For both UI, the different Codable array is managed. i.e.
FirstViewController contains FirstDataModel & SecondViewController contains SecondDataModel.
So in FirstViewController, in cellForRow method I have called the code :
func setPastCell(cell:WeekCell, data_dic:FirstDataModel) {
cell.lblGoalA.text = predictScore1
cell.lblGoalB.text = predictScore2
}
Arrays:
struct FirstDataModel: Codable {
var team1_name:String?
var team2_name:String?
var status:Int?
var image:String?
var score:Int?
}
struct SecondDataModel: Codable {
var team1_name:String?
var team2_name:String?
var status:Int?
var image:String?
var count:Int?
var balance:Int?
}
I want to make common function for both FirstDataModel & SecondDataModel. So how can I manage it using generic function?
How can I pass FirstDataModel or SecondDataModel in setPastCell(cell: WeekCell, data_dic: FirstDataModel) ?
Thanks in advance!
You can inherit the two DataModel from a Protocol, and use Protocol properties in setup.
Example:
protocol ModelProtocol {
var descriptionOne: String { get }
var descriptionTwo: String { get }
}
struct FirstDataModel: ModelProtocol {}
func setPastCell(cell:WeekCell, data_dic: ModelProtocol) {
cell.lblGoalA.text = descriptionOne
cell.lblGoalB.text = descriptionTwo
}
Craete a protocol with common properties
protocol DataModel {
var team1_name:String? { get }
var team2_name:String? { get }
var status:Int? { get }
var image:String? { get }
}
Confirm to the protocol in your structs and add its properties
struct FirstDataModel: DataModel, Codable {
//Protocol properties
var team1_name:String?
var team2_name:String?
var status:Int?
var image:String?
//additional properties
var score:Int?
}
struct SecondDataModel: DataModel, Codable {
//Protocol properties
var team1_name:String?
var team2_name:String?
var status:Int?
var image:String?
//additional properties
var count:Int?
var balance:Int?
}
As both structs confirm to the DataModel protocol, you can use it in the function parameter.
func setPastCell<T: DataModel>(cell:WeekCell, data_dic:T) {
if let firstModel = data_dic as? FirstDataModel {
print(firstModel.team1_name)
print(firstModel.team2_name)
print(firstModel.status)
print(firstModel.image)
print(firstModel.score)
} else if let secondModel = data_dic as? SecondDataModel {
print(secondModel.team1_name)
print(secondModel.team2_name)
print(secondModel.status)
print(secondModel.image)
print(secondModel.count)
print(secondModel.balance)
}
}