I am working on a SwiftUI project, where I use the MVVM-architecture.
When changing a View-model object property from the SwiftUI view, it causes a memory conflict crash in the view-model object.
The error is of the type: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
In steps, here is what happens:
View-model property is changed from view
View-model property changes model property
Model property notifies about changes
View-model receives change notification
View-model access model object
Crash occur due to memory conflict
Relevant code snippets are seen below. Xcode project is a standard SwiftUI project.
The error will happen, after first clicking the add button, and then the modify button.
If the "update" code is moved into the "receiveValue" closure, the error will not occur. Likewise, the error will not occur, if the View-model class is made non-generic.
To my best knowledge, the code is all-right, so I suspect it is a compiler problem. But I am not sure.
import Foundation
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var item: ViewModel<Model> = ViewModel<Model>()
var body: some View {
VStack {
Button("Add", action: { item.add(model:Model()) })
Button("Modify", action: { item.selected.toggle() })
}
}
}
protocol ModelType {
var objectDidChange: ObservableObjectPublisher { get }
var selected: Bool { get set }
}
class Model: ModelType {
let objectDidChange = ObservableObjectPublisher()
var selected = false {
didSet {
objectDidChange.send()
}
}
}
class ViewModel<Model:ModelType>: ObservableObject {
var selected = false {
didSet {
model.selected = selected
}
}
func add(model: Model) {
self.model = model
cancellable = model.objectDidChange.sink(receiveValue: { _ in
self.update()
})
}
private var model: Model! = nil
private var cancellable: AnyCancellable? = nil
func update() {
// Crash log: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
print("update \(model.selected)")
}
}
Short version: require AnyObject for ModelType.
Long version:
You're trying to read from self.model while you're in the middle of setting self.model. When you say "If the "update" code is moved into the "receiveValue" closure, the error will not occur," this isn't quite correct. I expect what you mean is you wrote this:
cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(model.selected)")
})
And that worked, but that's completely different code. model in this case is the local variable, not the property self.model. You'll get the same crash if you write it this way:
cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(self.model.selected)")
})
The path that gets you here is:
ViewModel.selected.didSet
WRITE to Model.selected <---
Model.selected.didSet
(observer-closure)
ViewModel.update
READ from ViewModel.model <---
This is a read and write to the same value, and that violates exclusive access. Note that the "value" in question is "the entire ViewModel value," not ViewModel.selected. You can show this by changing the update function to:
print("update \(model!)")
You'll get the same crash.
So why does this work when you take out the generic? Because this particularly strict version of exclusivity only applies to value types (like structs). It doesn't apply to classes. So when this is concrete, Swift knows viewModel is a class, and that's ok. (The why behind this difference a bit complex, so I suggest reading the proposal that explains it.)
When you make this generic, Swift has to be very cautious. It doesn't know that Model is a class, so it applies stricter rules. You can fix this by promising that it's a class:
protocol ModelType: AnyObject { ... }
Related
Explanation
I am still in the process of learning to utilize SwiftUI patterns in the most optimal way. But most SwiftUI MVVM implementation examples I find are very simplistic. They usually have one database class and then 1-2 viewmodels that take data from there and then you have views.
In my app, I have a SQLite DB, Firebase and different areas of content. So I have a few separate model-vm-view paths. In the Android equivalent of my app, I used a pattern like this:
View - ViewModel - Repository - Database
This way I can separate DB logic like all SQL queries in the repository classes and have the VM handle only view related logic. So the whole thing looks something like this:
In Android this works fine, because I just pass through the LiveData object to the view. But when trying this pattern in SwiftUI, I kind of hit a wall:
It doesn't work / I don't know how to correctly connect the Published objects of both
The idea of "chaining" or nesting ObservableObjects seems to be frowned upon:
This article about Nested Observable Objects in SwiftUI:
I’ve seen this pattern described as “nested observable objects”, and it’s a subtle quirk of SwiftUI and how the Combine ObservableObject protocol works that can be surprising. You can work around this, and get your view updating with some tweaks to the top level object, but I’m not sure that I’d suggest this as a good practice. When you hit this pattern, it’s a good time to step back and look at the bigger picture.
So it seems like one is being pushed towards using the simpler pattern of:
View - ViewModel - Database Repository
Without the repository in-between. But this seems annoying to me, it would make my viewmodel classes bloated and would mix UI/business code with SQL queries.
My Code
So this is a simplified version of my code to demonstrate the problem:
Repository:
class SA_Repository: ObservableObject {
#Published var selfAffirmations: [SelfAffirmation]?
private var dbQueue: DatabaseQueue?
init() {
do {
dbQueue = Database.sharedInstance.dbQueue
fetchSelfAffirmations()
// Etc. other SQL code
} catch {
print(error.localizedDescription)
}
}
private func fetchSelfAffirmations() {
let saObservation = ValueObservation.tracking { db in
try SelfAffirmation.fetchAll(db)
}
if let unwrappedDbQueue = dbQueue {
let _ = saObservation.start(
in: unwrappedDbQueue,
scheduling: .immediate,
onError: {error in print(error.localizedDescription)},
onChange: {selfAffirmations in
print("change in SA table noticed")
self.selfAffirmations = selfAffirmations
})
}
}
public func updateSA() {...}
public func insertSA() {...}
// Etc.
}
ViewModel:
class SA_ViewModel: ObservableObject {
#ObservedObject private var saRepository = SA_Repository()
#Published var selfAffirmations: [SelfAffirmation] = []
init() {
selfAffirmations = saRepository.selfAffirmations ?? []
}
public func updateSA() {...}
public func insertSA() {...}
// + all the Firebase stuff later on
}
View:
struct SA_View: View {
#ObservedObject var saViewModel = SA_ViewModel()
var body: some View {
NavigationView {
List(saViewModel.selfAffirmations, id: \.id) { selfAffirmation in
SA_ListitemView(content: selfAffirmation.content,
editedValueCallback: { newString in
saViewModel.updateSA(id: selfAffirmation.id, newContent: newString)
})
}
}
}
}
Attempts
Obviously the way I did it here is wrong, because it clones the data from repo to vm once with selfAffirmations = saRepository.selfAffirmations ?? [] but then it never updates when I edit the entries from the view, only on app restart.
I tried $selfAffirmations = saRepository.$selfAffirmations to just transfer the binding. But the repo one is an optional, so I'd need to make the vm selfAffirmations an optional too, which would then mean handling unnecessary logic in the view code. And not sure if it would even work at all.
I tried to do it manually with Combine but this way seemed to not be recommended and fragile. Plus it also didn't work:
selfAffirmations = saRepository.selfAffirmations ?? []
cancellable = saRepository.$selfAffirmations.sink(
receiveValue: { [weak self] repoSelfAffirmations in
self?.selfAffirmations = repoSelfAffirmations ?? []
}
)
Question
Overall I would just need some way to pass through the data from the repo to the view, but have the vm be in the middle as a separator. I read about the PassthroughSubject in Combine, which sounds like it would be fitting, but I'm not sure if I am just misunderstanding some concepts here.
Now I am not sure if my architecture concepts are wrong/unfitting, or if I just don't understand enough about Combine publishers yet to make this work.
Any advice would be appreciated.
After getting some input from the comments, I figured out a clean way.
The problem for me was understanding how to make a property of a class publish its values. Because the comments suggested that property wrappers like #ObservedObject was a frontend/SwiftUI only thing, making me assume that everything related was limited to that too, like #Published.
So I was looking for something like selfAffirmations.makePublisher {...}, something that would make my property a subscribable value emitter. I found that arrays naturally come with a .publisher property, but this one seems to only emit the values once and never again.
Eventually I figured out that #Published can be used without #ObservableObject and still work properly! It turns any property into a published property.
So now my setup looks like this:
Repository (using GRDB.swift btw):
class SA_Repository {
private var dbQueue: DatabaseQueue?
#Published var selfAffirmations: [SelfAffirmation]?
// Set of cancellables so they live as long as needed and get deinitialiazed with the class end
var subscriptions = Array<DatabaseCancellable>()
init() {
dbQueue = Database.sharedInstance.dbQueue
fetchSelfAffirmations()
}
private func fetchSelfAffirmations() {
// DB code....
}
}
And viewmodel:
class SA_ViewModel: ObservableObject {
private var saRepository = SA_Repository()
#Published var selfAffirmations: [SelfAffirmation] = []
// Set of cancellables to keep them running
var subscriptions = Set<AnyCancellable>()
init() {
saRepository.$selfAffirmations
.sink{ [weak self] repoSelfAffirmations in
self?.selfAffirmations = repoSelfAffirmations ?? []
}
.store(in: &subscriptions)
}
}
I have an ObservableObject declared on my main view (ContentView.swift).
final class DataModel: ObservableObject {
#AppStorage("stuff") public var notes: [NoteItem] = []
}
Then I declare it in the main entry of the app as (removed extra code not needed for this example):
#main struct The_NoteApp: App {
private let dataModel = DataModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
}
}
In the ContentView.swift, I can use it on the different views I declared there:
struct NoteView: View {
#EnvironmentObject private var data: DataModel
// more code follows...
}
Now, I have a collection of global functions saved on FileFunctions.swift, which essentially are functions that interact with files on disk. One of them is to load those files and their content into my app.
Now, I'm trying to use #EnvironmentObject private var data: DataModel in those functions so at loading time, I can populate the data model with the actual data from the files. And when I declare that either as a global declaration in FileFunctions.swift or inside each function separately, I get two behaviors.
With the first one I get an error:
Global 'var' declaration requires an initializer expression or an explicitly stated getter`,
and
Property wrappers are not yet supported in top-level code
I tried to initialize it in any way, but it goes nowhere. With the second one, adding them to each function, Xcode craps on me with a segfault. Even if I remove the private and try to declare it in different ways, I get nowhere.
I tried the solution in Access environment variable inside global function - SwiftUI + CoreData, but the more I move things around the worse it gets.
So, how would I access this ObservableObject, and how would I be able to modify it within global functions?
Below is an example of a global function and how it's being called.
In FileFunctions.swift I have:
func loadFiles() {
var text: String = ""
var title: String = ""
var date: Date
do {
let directoryURL = try resolveURL(for: "savedDirectory")
if directoryURL.startAccessingSecurityScopedResource() {
let contents = try FileManager.default.contentsOfDirectory(at: directoryURL,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles])
for file in contents {
text = readFile(filename: file.path)
date = getModifiedDate(filename: file.absoluteURL)
title = text.components(separatedBy: NSCharacterSet.newlines).first!
// I need to save this info to the DataModel here
}
directoryURL.stopAccessingSecurityScopedResource()
} else {
Alert(title: Text("Couldn't load notes"),
message: Text("Make sure the directory where the notes are stored is accessible."),
dismissButton: .default(Text("OK")))
}
} catch let error as ResolveError {
print("Resolve error:", error)
return
} catch {
print(error)
return
}
}
And I call this function from here:
#main struct The_NoteApp: App {
private let dataModel = DataModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
.onAppear {
loadFiles()
}
}
}
You could change the signature of the global functions to allow receiving the model:
func loadFiles(dataModel: DataModel) { ... }
This way, you have access to the model instance within the function, what's left to do is to pass it at the call site:
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
.onAppear {
loadFiles(dataModel: self.dataModel)
}
You can do the same if the global functions calls originate from the views.
I would do something like this :
final class DataModel: ObservableObject {
public static let shared = DataModel()
#AppStorage("stuff") public var notes: [NoteItem] = []
}
#main struct The_NoteApp: App {
private let dataModel = DataModel.shared
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
}
}
now in your viewModel you can access it like this
class AnyClass {
init (){
print(DataModel.shared.notes)
}
// or
func printNotes(){
print(DataModel.shared.notes)
}
}
As discussed in the comments, here a basic approach which makes some changes to the structure by defining dedicated "components" which have a certain role and which are decoupled as far as necessary.
I usually define a namespace for a "feature" where I put every "component" which is related to it. This offers a couple of advantages which you might recognise soon later:
enum FilesInfo {}
Using a "DataModel" or a "ViewModel" to separate your "Data" from the View
makes sense. A ViewModel - as opposed to DataModel - just obeys the rules from the MVVM pattern. A ViewModel should expose a "binding". I call this "ViewState", which completely describes what the view should render:
extension FilesInfo {
enum ViewState {
struct FileInfo {
var date: Date
var title: String
}
case undefined
case idle([FileInfo])
init() { self = .undefined } // note that!
}
}
Why ViewState is an enum?
Because you might want to represent also a loading state when your load function is asynchronous (almost always the case!) and an error state later. As you can see, you start with a state that's "undefined". You can name it also "zero" or "start", or however you like. It just means: "no data loaded yet".
A view model basically looks like this:
extension FilesInfo {
final class ViewModel: ObservableObject {
#Published private(set) var viewState: ViewState = .init()
...
}
}
Note, that there is a default initialiser for ViewState.
It also may have public functions where you can send "events" to it, which may originate in the view, or elsewhere:
extension FilesInfo.ViewModel {
// gets the view model started.
func load() -> Void {
...
}
// func someAction(with parameter: Param) -> Void
}
Here, the View Model implements load() - possibly in a similar fashion you implemented your loadFiles.
Almost always, a ViewModel operates (like an Actor) on an internal "State", which is not always the same as the ViewState. But your ViewState is a function of the State:
extension FilesInfo.ViewModel {
private struct State {
...
}
private func view(_ state: State) -> ViewState {
//should be a pure function (only depend on state variable)
// Here, you likely just transform the FilesInfo to
// something which is more appropriate to get rendered.
// You call this function whenever the internal state
// changes, and assign the result to the published
// property.
}
}
Now you can define your FileInfosView:
extension FilesInfo {
struct ContentView: View {
let state: ViewState
let action: () -> Void // an "event" function
let requireData: () -> Void // a "require data" event
var body: some View {
...
.onAppear {
if case .undefined = state {
requireData()
}
}
}
}
}
When you look more closely on the ContentView, it has no knowledge from a ViewModel, neither from loadFiles. It only knows about the "ViewState" and it just renders this. It also has no knowledge when the view model is ready, or provides data. But it knows when it should render data but has none and then calls requireData().
Note, it does not take a ViewModel as parameter. Those kind of setups are better done in some dedicated parent view:
extension FilesInfo {
struct CoordinatorView: View {
#ObservedObject viewModel: ViewModel
var body: some View {
ContentView(
state: viewModel.viewState,
action: {},
requireData: viewModel.load
)
}
}
}
Your "coordinator view" deals with separating ViewModel from your specific content view. This is not strictly necessary, but it increases decoupling and you can reuse your ContentView elsewhere with a different ViewModel.
Your CoordinatorView may also be responsible for creating the ViewModel and creating target views for navigation. This depends on what convention you establish.
IMHO, it may make sense, to restrict the access to environment variables to views with a certain role, because this creates a dependency from the view to the environment. We should avoid such coupling.
Also, I would consider mutating environment variables from within Views a "smell". Environment variables should be kind of a configuration which you setup in a certain place in your app (also called "CompositionRoot"). You may end up with an uncontrollable net of variables if you allow that everyone can change any environment variable at any time. When you have "ViewModels" in your environment, these of course get not "mutated" when they change their state - these are classes - for a reason.
Basically, that's it for a very basic but functional MVVM pattern.
I have a class, a “clock face” with regular updates; it should display an array of metrics that change over time.
Because I’d like the clock to also be displayed in a widget, I’ve found that I had to put the class into a framework (perhaps there’s another way, but I’m too far down the road now). This appears to have caused a problem with SwiftUI and observable objects.
In my View I have:
#ObservedObject var clockFace: myClock
In the clock face I have:
class myClock: ObservableObject, Identifiable {
var id: Int
#Publish public var metric:[metricObject] = []
....
// at some point the array is mutated and the display updates
}
I don’t know if Identifiable is needed but it’s doesn’t make any difference to the outcome. The public is demanded by the compiler, but it’s always been like that anyway.
With these lines I get a runtime error as the app starts:
objc[31175] no class for metaclass
So I took off the #Published and changed to a manual update:
public var metric:[metricObject] = [] {
didSet {
self.objectWillChange.send()`
}
}
And now I get a display and by setting a breakpoint I can see the send() is being called at regular intervals. But the display won’t update unless I add/remove from the array. I’m guessing the computed variables (which make up the bulk of the metricObject change isn’t being seen by SwiftUI. I’ve subsequently tried adding a “dummy” Int to the myClock class and setting that to a random value to trying to trigger a manual refresh via a send() on it’s didSet with no luck.
So how can I force a periodic redraw of the display?
What is MetricObject and can you make it a struct so you get Equatable for free?
When I do this with an Int it works:
class PeriodicUpdater: ObservableObject {
#Published var time = 0
var subscriptions = Set<AnyCancellable>()
init() {
Timer
.publish(every: 1, on: .main, in: .default)
.autoconnect()
.sink(receiveValue: { _ in
self.time = self.time + 1
})
.store(in: &subscriptions)
}
}
struct ContentView: View {
#ObservedObject var updater = PeriodicUpdater()
var body: some View {
Text("\(self.updater.time)")
}
}
So it's taken a while but I've finally got it working. The problem seemed to be two-fold.
I had a class defined in my framework which controls the SwiftUI file. This class is sub-classed in both the main app and the widget.
Firstly I couldn't use #Published in the main class within the framework. That seemed to cause the error:
objc[31175] no class for metaclass
So I used #JoshHomman's idea of an iVar that's periodically updated but that didn't quite work for me. With my SwiftUI file, I had:
struct FRMWRKShape: Shape {
func drawShape(in rect: CGRect) -> Path {
// draw and return a shape
}
}
struct ContentView: View {
#ObservedObject var updater = PeriodicUpdater()
var body: some View {
FRMWRKShape()
//....
FRMWRKShape() //slightly different parameters are passed in
}
}
The ContentView was executed every second as I wanted, however the FRMWRKShape code was called but not executed(?!) - except on first starting up - so the view doesn't update. When I changed to something far less D.R.Y. such as:
struct ContentView: View {
#ObservedObject var updater = PeriodicUpdater()
var body: some View {
Path { path in
// same code as was in FRMWRKShape()
}
//....
Path { path in
// same code as was in FRMWRKShape()
// but slightly different parameters
}
}
}
Magically, the View was updated as I wanted it to be. I don't know if this is expected behaviour, perhaps someone can say whether I should file a Radar....
I'm implementing an Observer design pattern on a Struct model object. The idea is that I will pass my model down a chain of UIViewController and as each controller modifies it, previous controllers will also be updated with changes to the object.
I'm aware this problem could be solved by using a class instead of struct and modifying the object directly through reference, however I'm trying to learn more about using structs.
struct ModelObject {
var data: Int = 0 {
didSet {
self.notify()
}
}
private var observers = [ModelObserver]()
mutating func attachObserver(_ observer: ModelObserver){
self.observers.append(observer)
}
private func notify(){
for observer in observers {
observer.modelUpdated(self)
}
}
}
protocol ModelObserver {
var observerID: Int { get }
func modelUpdated(_ model: ModelObject)
}
class MyViewController : UIViewController, ModelObserver {
var observerID: Int = 1
var model = ModelObject()
override func viewDidLoad() {
self.model.attachObserver(self)
self.model.data = 777
}
func modelUpdated(_ model: ModelObject) {
print("received updated model")
self.model = model //<-- problem code
}
}
Simply put, my model object notifies any observer when data changes by calling notify().
My problem right now is memory access: when data gets set to 777, self.model becomes exclusively accessed, and when it calls notify which calls modelUpdated and eventually self.model = model, we get an error:
Simultaneous accesses to 0x7fd8ee401168, but modification requires exclusive access.
How can I solve this memory access issue?
If you're observing "a thing," then that "thing" has an identity. It's a particular thing that you're observing. You can't observe the number 4. It's a value; it has no identity. Every 4 is the same as every other 4. Structs are values. They have no identity. You should not try to observe them any more than you'd try to observe an Int (Int is in fact a struct in Swift).
Every time you pass a struct to a function, a copy of that struct is made. So when you say self.model = model, you're saying "make a copy of model, and assign it to this property." But you're still in an exclusive access block because every time you modify a struct, that also makes a copy.
If you mean to observe ModelObject, then ModelObject should be a reference type, a class. Then you can talk about "this particular ModelObject" rather than "a ModelObject that contains these values, and is indistinguishable from any other ModelObject which contains the same values."
I am trying to leverage SwiftUI and Combine to store user defaults for my application. Looking at suggestions in a few other posts, I have updated my code as you see below. However, I am now getting the error of "Referencing instance method 'send()' on 'Subject' requires the types 'Setup' and 'Void' be equivalent". It has been suggested that I change "Setup" to void in the PassthroughSubject, however this then gives a hard crash in the app at startup - " Fatal error: No observable object of type Setup.Type found."
I am at a bit of loss... any pointers would be welcomed.
============== DataStoreClass ============
import SwiftUI
import Foundation
import Combine
class Setup: ObservableObject {
private var notificationSubscription: AnyCancellable?
let objectWillChange = PassthroughSubject<Setup,Never>()
#UserDefault(key: "keyValueBool", defaultValue: false)
var somevalueBool: Bool {
didSet{
objectWillChange.send() // <====== Referencing instance method 'send()' on 'Subject' requires the types 'Setup' and 'Void' be equivalent
}
}
init() {
notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
self.objectWillChange.send()
}
}
}
============= property wrapper ===========
import Foundation
#propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
UserDefaults(suiteName: "group.com.my.app")!.value(forKey: key) as? T ?? defaultValue
} set {
UserDefaults(suiteName: "group.com.my.app")!.set(newValue, forKey: key)
}
}
}
The error comes from the fact that you have declared your Output type as Setup, but you are calling objectWillChange with Void.
So you have to pass self to objectWillChange:
self.objectWillChange.send(self)
Important thing to notice is that you should call objectWillChange not in didSet but in willSet:
var somevalueBool: Bool {
willSet{
objectWillChange.send(self
}
}
You never set somevalueBool, so this bit of code will not get called anyway.
Your setup should look roughly like this:
class Setup: ObservableObject {
private var notificationSubscription: AnyCancellable?
public let objectWillChange = PassthroughSubject<Setup,Never>()
#UserDefault(key: "keyValueBool", defaultValue: false)
var somevalueBool: Bool
init() {
notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
self.objectWillChange.send(self)
}
}
}
The send method requires you to pass the input type of the subject, or a failure completion. So your send lines should pass the Setup;
objectWillChange.send(self)
That said, in most SwiftUI code, PassthroughSubject is <Void, Never> (such that send() does not require a parameter). It's not clear what the source of the crash you're describing is; we would need to see the code that's involved in the crash to debug that. I haven't reproduced it so far.
SwiftUI doesn't use a PassthroughSubject, it uses an ObservableObjectPublisher. I am pretty sure that that is an alias for PassthroughSubject<Void, Never> but I'm not sure. The ObservableObject protocol defines a correct objectWillChange for you so the best thing you can do is to remove your definition.
The publisher is objectWillChange and as its name suggests it should be sent in willSet and not didSet, I don't suppose that it matters much but Apple changed from didSet to willSet and my guess is that they had a good reason.