So I am currently working on my app and have come across a problem where I have to access a published variable in another observable class.
Here is some code on what I am trying to do
class PeriodViewModel: ObservableObject {
#Published var value = 1
}
class DataViewModel: ObservableObject {
#ObservedObject var periodViewModel = PeriodViewModel()
periodViewModel.value = 1
}
How would I be able to access the updated variable from periodViewModel in dataViewModel? Thanks.
[Note]: Ignore all the variables I am just showing the the flow of using different mangers.
Here is an example of function that I am using for my FirebaseDatabaseManage. You can see a clouser is passing into the function parameter. When your firebase insert function response after async you need to call your clouser which I named as completion.
class FirebaseDatabaseManager {
public func insertRecipe(with recipe: RecipeModel, completion: #escaping (Bool, String) -> Void) {
SwiftSpinner.show("Loading...")
let userID = UserDefaultManager.shared.userId
database.child(FireBaseTable.recipes.rawValue).child(userID).childByAutoId().setValue(recipe.convertToDictionary!, withCompletionBlock: { error, ref in
SwiftSpinner.hide()
guard error == nil else {
completion(false, "failed to write to database")
return
}
completion(true, ref.key ?? "no key found")
})
}
}
Now look at my ViewModel class in which I am calling my FirebaseManager method. On calling of completion I am updating my #Publisher Which you can use to update your UI.
class RecipeViewModel: ObservableObject {
#Publisher var id = 0
func createRecipe() {
FirebaseDatabaseManager.shared.insertRecipe(with: self.recipeModel) { status, id in
self.id = id
}
}
}
Hope this help your in understand your concepts.
Related
I have been coding in swift for a short time now and wish to create my first, properly complete application. My application starts with a UITabController (after the logging in part which I have implemented) will come with a "profile" page, where the user can update information about themselves (username etc).
I have therefore created a User class which holds this information and will in the future, communicate with a server to update the users information.
I only want one User class object to be instantiated throughout the application (yet still accessible everywhere) as only one user can be logged in on the phone, what is considered the best practice to do so? It may also be worth noting that the log in section will remember a user is logged in so they won't have to re-log in (using user defaults Boolean for isLoggedIn)
I was thinking about making the User class as a singleton, or somehow making the class instance global (although I am pretty sure making it global isn't great).
Or is there a way to make the instance accessible for every view controller placed in a UITabController class if I create the User class in the tab controller class? What do you recommend?
Thanks all!
This is how I use a single object for user data that both is available in any view controller I want but also allows for the saving of data. This utilizes Realm for swift for saving data. To call the data you just create the variable let user = User.getCurrentUser()
class User: Object, Decodable {
#objc dynamic var firstName: String? = ""
#objc dynamic var lastName: String? = ""
#objc dynamic var email: String = ""
#objc dynamic var signedIn: Bool = false
override static func primaryKey() -> String? {
return "email"
}
private enum CodingKeys: String, CodingKey {
case email
case firstName
case lastName
}
}
extension User {
func updateUser(block: (User) -> ()) {
let realm = UserRealm.create()
try? realm.write {
block(self)
}
static func getCurrentUser() -> User? {
let realm = UserRealm.create()
return realm.objects(User.self).filter("signedIn == %#", true).first
}
}
fileprivate let currentSchema: UInt64 = 104
struct UserRealm {
static func create() -> Realm {
do {
return try Realm(configuration: config)
} catch {
print(error)
fatalError("Creating User Realm Failed")
}
}
static var config: Realm.Configuration {
let url = Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("Users.realm")
return Realm.Configuration(fileURL: url,
schemaVersion: currentSchema, migrationBlock: { migration, oldSchema in
print("Old Schema version =", oldSchema)
print("Current schema version =", currentSchema)
print("")
if oldSchema < currentSchema {
}
}, shouldCompactOnLaunch: { (totalBytes, usedBytes) -> Bool in
let oneHundredMB = 100 * 1024 * 1024
return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
})
}
}
For a Store/Factory/ViewModel pattern using Combine and SwiftUI, I'd like a Store protocol-conforming class to expose a publisher for when specified model object(s) change internal properties. Any subscribed ViewModels can then trigger objectWillChange to display the changes.
(This is necessary because changes are ignored inside a model object that is passed by reference, so #Published/ObservableObject won't auto-fire for Factory-passed Store-owned models. It works to call objectWillChange in the Store and the VM, but that leaves out any passively listening VMs.)
That's a delegate pattern, right, extending #Published/ObservableObject to passed-by-reference objects? Combing through combine blogs, books, and docs hasn't triggered an idea to what's probably a pretty standard thing.
Crudely Working Attempt
I thought PassthroughSubject<Any,Never> would be useful if I exposed a VM's objectWillChange externally, but PassthroughSubject.send() will fire for every object within the model object. Wasteful maybe (although the ViewModel only fires its objectWillChange once).
Attaching a limiter (e.g., throttle, removeDuplicates) on Ext+VM republishChanges(of myStore: Store) didn't seem to limit the .sink calls, nor do I see an obvious way to reset the demand between the PassthroughSubject and the VM's sink... or understand how to attach a Subscriber to a PassthroughSubject that complies with the Protcols. Any suggestions?
Store-Side
struct Library {
var books: // some dictionary
}
class LocalLibraryStore: LibraryStore {
private(set) var library: Library {
didSet { publish() }
}
var changed = PassthroughSubject<Any,Never>()
func removeBook() {}
}
protocol LibraryStore: Store {
var changed: PassthroughSubject<Any,Never> { get }
var library: Library { get }
}
protocol Store {
var changed: PassthroughSubject<Any,Never> { get }
}
extension Store {
func publish() {
changed.send(1)
print("This will fire once.")
}
}
VM-Side
class BadgeVM: VM {
init(store: LibraryStore) {
self.specificStore = store
republishChanges(of: jokesStore)
}
var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
internal var subscriptions = Set<AnyCancellable>()
#Published private var specificStore: LibraryStore
var totalBooks: Int { specificStore.library.books.keys.count }
}
protocol VM: ObservableObject {
var subscriptions: Set<AnyCancellable> { get set }
var objectWillChange: ObservableObjectPublisher { get set }
}
extension VM {
internal func republishChanges(of myStore: Store) {
myStore.changed
// .throttle() doesn't silence as hoped
.sink { [unowned self] _ in
print("Executed for each object inside the Store's published object.")
self.objectWillChange.send()
}
.store(in: &subscriptions)
}
}
class OtherVM: VM {
init(store: LibraryStore) {
self.specificStore = store
republishChanges(of: store)
}
var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
internal var subscriptions = Set<AnyCancellable>()
#Published private var specificStore: LibraryStore
var isBookVeryExpensive: Bool { ... }
func bookMysteriouslyDisappears() {
specificStore.removeBook()
}
}
Thanks #NewDev for pointing out subclassing as a smarter route.
If you want to nest ObservableObjects or have an ObservableObject re-publish changes in objects within an object passed to it, this approach works with less code than in my question.
In searching to simplify further with a property wrapper (to get at parent objectWillChange and simplify this further), I noticed a similar approach in this thread: https://stackoverflow.com/a/58406402/11420986. This only differs in using a variadic parameter.
Define VM and Store/Repo Classes
import Foundation
import Combine
class Repo: ObservableObject {
func publish() {
objectWillChange.send()
}
}
class VM: ObservableObject {
private var repoSubscriptions = Set<AnyCancellable>()
init(subscribe repos: Repo...) {
repos.forEach { repo in
repo.objectWillChange
.receive(on: DispatchQueue.main) // Optional
.sink(receiveValue: { [weak self] _ in
self?.objectWillChange.send()
})
.store(in: &repoSubscriptions)
}
}
}
Example Implementation
Repo: add didSet { publish() } to model objects
VM: The super.init() accepts any number of repos to republish
import Foundation
class UserDirectoriesRepo: Repo, DirectoriesRepository {
init(persistence: Persistence) {
self.userDirs = persistence.loadDirectories()
self.persistence = persistence
super.init()
restoreBookmarksAccess()
}
private var userDirs: UserDirectories {
didSet { publish() }
}
var someExposedSliceOfTheModel: [RootDirectory] {
userDirs.rootDirectories.filter { $0.restoredURL != nil }
}
...
}
import Foundation
class FileStructureVM: VM {
init(directoriesRepo: DirectoriesRepository) {
self.repo = directoriesRepo
super.init(subscribe: directoriesRepo)
}
#Published // No longer necessary
private var repo: DirectoriesRepository
var rootDirectories: [RootDirectory] {
repo.rootDirectories.sorted ...
}
...
}
It seems that what you want is a type that notifies when its internal properties change. That sounds an awful lot like what ObservableObject does.
So, make your Store protocol inherit from ObservableObject:
protocol Store: ObservableObject {}
Then a type conforming to Store could decide what properties it wants to notify on, for example, with #Published:
class StringStore: Store {
#Published var text: String = ""
}
Second, you want your view models to automatically fire off their objectWillChange publishers when their store notifies them.
The automatic part can be done with a base class - not with a protocol - because it needs to store the subscription. You can keep the protocol requirement, if you need to:
protocol VM {
associatedtype S: Store
var store: S { get }
}
class BaseVM<S: Store>: ObservableObject, VM {
var c : AnyCancellable? = nil
let store: S
init(store: S) {
self.store = store
c = self.store.objectWillChange.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
}
class MainVM: BaseVM<StringStore> {
// ...
}
Here's an example of how this could be used:
let stringStore = StringStore();
let mainVm = MainVM(store: stringStore)
// this is conceptually what #ObservedObject does
let c = mainVm.objectWillChange.sink {
print("change!") // this will fire after next line
}
stringStore.text = "new text"
I am attempting to use the Realm library to persist data within my application. However, I keep running into the same error code: "Realm accessed from incorrect thread". I attempted to resolve this issue by creating a Realm-specific Dispatch Queue, and wrapping all of my Realm calls in it.
Here is what my "RealmManager" class looks like right now:
import Foundation
import RealmSwift
class RealmManager {
fileprivate static let Instance : RealmManager = RealmManager()
fileprivate var _realmDB : Realm!
fileprivate var _realmQueue : DispatchQueue!
class func RealmQueue() -> DispatchQueue {
return Instance._realmQueue
}
class func Setup() {
Instance._realmQueue = DispatchQueue(label: "realm")
Instance._realmQueue.async {
do {
Instance._realmDB = try Realm()
} catch {
print("Error connecting to Realm DB")
}
}
}
class func saveObjectArray(_ objects: [Object]) {
Instance._realmQueue.async {
do {
try Instance._realmDB.write {
for obj in objects {
Instance._realmDB.add(obj, update: .all)
}
}
} catch {
print("Error Saving Objects")
}
}
}
class func fetch(_ type: Int) -> [Object] {
if let realm = Instance._realmDB {
let results = realm.objects(Squeak.self).filter("type = \(type)")
var returnArray : [Object] = []
for r in results {
returnArray.append(r)
}
return returnArray
}
return []
}
I am calling Setup() inside of didFinishLaunchingWithOptions to instantiate the Realm queue and Realm Db instance.
I am getting the error code inside of saveObjectArray at:
try Instance._realmDB.write { }
This seems to simply be a matter of my misunderstanding of the threading requirements of Realm. I would appreciate any insight into the matter, or a direction to go in from here.
This issue is that you fetch your Realm data on a different thread than you save it.
To fix the error, the code within fetch will also need to run on the Realm thread that you have created.
I think this article does a good job of explaining multi-threading in Realm and particularly recommend paying attention to the three rules outlined in the article.
I have the following code:
class Note: NSObject {
}
struct Global {
static var notes: Array<Note> = [] {
didSet {
print("hi")
}
}
}
This prints "hi" if I add or remove an item from the array or if I do
Global.notes = []
Is there a way to print("hi") every time when one of the Note objects in the array is modified?
Thanks for your answers
Without changing the class to a struct, I have two basic ways to handle this.
This is the object you asked about
class Note: NSObject {
}
struct Global {
static var notes: Array<Note> = [] {
didSet {
print("hi")
}
}
}
Wrap Notes in a wrapper that is a struct to get the struct behavior.
extension Note {
struct Wrapper { let note: Note }
}
extension Global {
static var wrappedNotes = [Note.Wrapper]() {
didSet {
print("hi")
}
}
}
Global.wrappedNotes.append(Note.Wrapper(note: Note()))
Global.wrappedNotes[0] = Note.Wrapper(note: Note())
Global.wrappedNotes.remove(at: 0)
The other way is to create a note manager to wrap access to the array.
class NoteManager {
subscript(index: Int) -> Note {
get {
return values[index]
}
set {
defer { onUpdate() }
values[index] = newValue
}
}
func append(_ newNote: Note) {
defer { onUpdate() }
values.append(newNote)
}
func remove(at index: Int) -> Note {
defer { onUpdate() }
return values.remove(at: index)
}
private func onUpdate() {
print("hi")
}
private var values = [Note]()
}
extension Global {
static var managedNotes = NoteManager()
}
Global.managedNotes.append(Note())
Global.managedNotes[0] = Note()
Global.managedNotes.remove(at: 0)
As per #staticVoidMan comment , If you make your model , a struct, rather than a class, then the property observer didSet will work for your Note model's own properties as well.
import Foundation
struct Note {
var name: String
}
struct Global {
static var notes: Array<Note> = [] {
didSet {
print("hi")
}
}
}
Global.notes.append(Note(name: "Shubham"))
Global.notes.append(Note(name: "Bakshi"))
Global.notes[0].name = "Boxy"
This will print the following on the console :
hi
hi
hi
Swift Array is a struct, and structs are value-type which means they change completely when elements are added/removed/replaced. Hence when you add/remove/replace a Note, the didSet property observer gets called as the array has been set again.
However, as per you question:
Is there a way to print("hi") every time when one of the Note objects in the array is modified?
By this I am assuming that you want to do something when an element within this array is accessed and an internal property is modified.
This would have been fine if you were dealing with only value-type objects, i.e. had your Note object also been a struct, then changing anything inside one Note would have caused the array to change as well.
But your Note object is a class, i.e. reference-type, and stays as the same object even if it's internal elements change. Hence your array doesn't need to update and didSet does not get called.
Read: Value and Reference Types
KVO Solution:
Now... Since your Note is subclassing NSObject, you can use the KVO concept
As per the following working example, we observe only one property of the Note class.
If you want to observe more properties then you will need to observe those many more keypaths.
Example:
class Note: NSObject {
#objc dynamic var content = ""
init(_ content: String) {
self.content = content
}
}
class NoteList {
var notes: [Note] = [] {
didSet {
print("note list updated")
//register & save observers for each note
self.noteMessageKVOs = notes.map { (note) -> NSKeyValueObservation in
return note.observe(\Note.content, options: [.new, .old]) { (note, value) in
print("note updated: \(value.oldValue) changed to \(value.newValue)")
}
}
}
}
//array of observers
var noteMessageKVOs = [NSKeyValueObservation]()
}
let list = NoteList()
list.notes.append(Note("A")) //note list updated
list.notes.append(Note("B")) //note list updated
list.notes[0].content = "X" //note updated: A changed to X
list.notes[1].content = "Y" //note updated: B changed to Y
Notes:
NSObject is required for KVO
#objc dynamic is required to make a property observable
\Note.message is a keypath
noteMessageKVOs are required to keep the observers alive
Usecase
I have a superclass (FirebaseObject) with subclasses for most data items in my Firebase (ex: RecipeItem, User). I made a function in the superclass that automatically updates the data that is in the subclass, now I am trying to make a function with closures that get called when the object is updated.
Code
class FirebaseObject {
private var closures: [((FirebaseObject) -> Void)] = []
public func didChange(completion: #escaping (((FirebaseObject) -> Void))) {
// Save closures for future updates to object
closures.append(completion)
// Activate closure with the current object
completion(self)
}
//...
}
This calls the closure with the initial object and saves it for later updates. In my Firebase observer I can now activate all the closures after the data is updated by calling:
self.closures.forEach { $0(self) }
To add these closures that listen for object changes I need to do:
let recipeObject = RecipeItem(data)
recipeObject.didChange { newFirebaseObject in
// Need to set Type even though recipeObject was already RecipeItem
// this will never fail
if let newRecipeObject = newFirebaseObject as? RecipeItem {
// Do something with newRecipeObject
}
}
Question
Is there a way to have the completion handler return the type of the subclass so I don't have to do as? Subclass even though it won't ever fail? I tried to do this with generic type but I can't figure it out and I am not sure if this is the correct solution.
I would like to keep most code in the FirebaseObject class so I don't need to add a lot of code when creating a new subclass.
Edit
Based on this article I tried to add the type when creating a subclass:
class RecipeItem: FirebaseObject<RecipeItem> {
//...
}
class FirebaseObject<ItemType> {
private var handlers: [((ItemType) -> Void)] = []
public func didChange(completion: #escaping (((ItemType) -> Void))) {
//...
This compiles but it crashes as soon as RecipeItem is initialised. I also tried
class RecipeItem: FirebaseObject<RecipeItem.Type> {
//...
}
But this gives an interesting compiler error when I try to access RecipeItem data in didChange closure:
Instance member 'title' cannot be used on type 'RecipeItem'
Ok, so I've been working on this for a day and I have found a way to do it using the method in this answer for the didChange and initObserver functions and taking inspiration from this way of saving data in extensions.
First off, all the functions that need to use the type of the subclass are moved to a protocol.
protocol FirebaseObjectType {}
extension FirebaseObjectType where Self: FirebaseObject {
private func initObserver(at ref: DatabaseReference) {
//...
}
mutating func didChange(completion: #escaping (((Self) -> Void))) {
if observer == nil {
// init Firebase observer here so there will be no Firebase
// observer running when you don't check for changes of the
// object, and so the Firebase call uses the type of whatever
// FirebaseObject this function is called on eg:
// RecipeItem.didChange returns RecipeItem
// and NOT:
// RecipeItem.didChange returns FirebaseObject
initObserver(at: ref)
}
if closureWrapper == nil {
// init closureWrapper here instead of in init() so it uses
// the class this function is called on instead of FirebaseObject
closureWrapper = ClosureWrapper<Self>()
}
// Save closure for future updates to object
closures.append(completion)
// Activate closure with current object
completion(self)
}
}
To save the closures I now use a wrapper class so I can do type checking on that. In FirebaseObject:
class ClosureWrapper<T> {
var array: [((T) -> Void)]
init() {
array = []
}
}
fileprivate var closureWrapper: AnyObject?
Now I can get the closures with the right type in FirebaseObjectType protocol:
private var closures: [((Self) -> Void)] {
get {
let closureWrapper = self.closureWrapper as? ClosureWrapper<Self>
return closureWrapper?.array ?? []
}
set {
if let closureWrapper = closureWrapper as? ClosureWrapper<Self> {
closureWrapper.array = newValue
}
}
}
I can now use didChange on a FirebaseObject subclass without checking its type every time.
var recipe = RecipeItem(data)
recipe.didChange { newRecipe in
// Do something with newRecipe
}