Since the shared singleton instance will always be around, can we safely use [unowned self] in all closures within that singleton class?
Sure, it's safe. But that's not a good reason.
Whether you use weak references or strong references should be based on the memory management characteristics in the function you are writing. For example, if a closure is referred to strongly by the object, then the closure should capture a weak reference to the object; and that is safe since nobody else has a reference to the closure, so it only can execute while the main object is alive, etc. If there is no retain cycle, and the closure is given to a separate API so that it is not tied to the lifetime of the main object, then the closure should have a strong reference to the main object. This reasoning applies equally for singletons and non-singletons alike.
Yes the singleton holds a strong reference to itself, and can't be disposed.
Base on that is safe to say that you can safely create weak or unowned references to it.
From Apple documents:
The class lazily creates its sole instance the first time it is
requested and thereafter ensures that no other instance can be
created. A singleton class also prevents callers from copying,
retaining, or releasing the instance.
An easy way to test it is to test from the main class.
Create a new class (let's call "first class"), which initializes the singleton with some values and is disposed after finishing a unique job.
After that in the main class create another class (let's call "second class") which retrieves the singleton instance and reads its values.
Between the first (disposed) class and the second (newly created) class there are no references to the singleton.
Now read the values and if values still there it proves that the singleton has kept alive by its own reference.
Related
Update
To clarify, the access of the object during its deinitialization is not being done in its deinit method explicitly. The object in question has listers that get added to it (closures) and these closures are all executed within the deinit method. It is within these closures that accesses of the object is being performed with unowned references. And it is the replacement of those unowned references with unowned(unsafe) references that results in EXC_BAD_ACCESS' from no longer occuring.
It is these unowned(unsafe) references that I'm referring to when asking if they're safe to use if always executed during the object in question's deinit.
Original
I wrote a lot of code predicated on being able to clean up unowned references in the deinitializers of their unowned object. Lo and behold, that is not a feature of unowned references. But apparently it is of unowned(unsafe) references, at least that is the way it appears to be working right now — what once caused a crash accessing an unowned reference during its object's deinitialization, now is no longer crashing and is working as expected.
If guaranteed that all unowned references will not be accessed after deinitialization of their object, would it be safe to use it?
For more details, the aforementioned cleaning up entails removing the object from a set where the hashability is based off its contents' object identities. So if it's a plain unowned reference, when the set attempts to access its hash, it will crash if that procedure is being performed while the object is already deinitializing.
The reason the objects aren't removed from the set before they are deinitialized is because this code is a component of library that enables the addition of nodes to a directed acyclic graph. As a feature, I decided that I would not require consumers of the library to have to remove the nodes when they're done with them, they can simply add them to the graph, then when they're done, release their object (the node) as they would anyways, and because the library adds listeners onto the nodes to remove them from the graph in their deinitializers, it was anticipated that it wouldn't be a problem — that the graph would be able to be cleaned up transparently. Obviously it's a little more complicated now that it's apparent that unowned(safe) references can't be accessed while the object they're referencing is deinitializing.
If unowned(unsafe) works in the way it appears to, it would be a solution to this problem.
The only difference between unowned(safe) and unowend(unsafe) is that the save variant is implemented using proxy objects and it will reliably crash your app when you access it illegally.
The unsafe variant on the other hand is just a plain C-Style pointer which will sometimes "just work" (if by coincidence the memory has not been reused anyway) and sometimes will strangely crash or just report unpredicable results.
unowned is the same as unowned(safe)
Nevertheless, during deinit you may access all the propertys of your object, see The Documentation
And also:
I am not sure exactly what you have implemented but it looks like you are trying to duplicate the mechanism with tables Swift uses internally for keeping track of deallocations of weak references.
If guaranteed that all unowned references will not be accessed after
deinitialization of their object, would it be safe to use it?
Yes it would be safe. If you have this guarantee I think it would also be simpler to turn all your variables to implicitly unwrapped weak variables.
So if it's a plain unowned reference, when the set attempts to access
its hash, it will crash if that procedure is being performed while the
object is already deinitializing.
Obviously it's a little more complicated now that it's apparent that
unowned(safe) references can't be accessed while the object they're
referencing is deinitializing.
I do not think this is the reason for the crash, the memory is freed after deinitialization, during deinitialization you still have access to the instance to perform any manual cleanup you need, I would suggest to replace the complicated solution that keeps track of deallocated references, and simply rely on Swift to set to nil objects that are deallocated using weak references. If you do not want to refactor you code to handle optionals when make them explicitly unwrapped.
However if during deinitialization you access the object from an other reference(outside deinit) it will fail, this is to ensure consistency. See here that access an instance that is deinitialized will cause an app to crash.
Since ARC doesn't apply to struct and enum, then how are they deallocated from the memory? I have to get stuck when it asked in the interviews and try to find the correct answer but can't find much info on it googling. I know swift is smart at handling value types. But how?
The memory management of objects (instances of classes) is relatively difficult, because objects can outlive a function call, the life of other objects, or even the life of the threads that allocated them. They're independent entities on the heap, that need book keeping to make sure they're freed once they're not needed (once they're no longer referenced from any other threads/objects, they're unreachable, thus can't possible be needed, so are safe to delete).
On the other hand, structs and enums just have their instances stored inline:
If they're declared as a global variable, they're stored in the program text.
If they're declared as a local variable, they're allocated on the stack (or in registers, but never mind that).
If they're allocated as a property of another object, they're just
stored directly inline within that object.
They're only ever deleted
by virtue of their containing context being deallocated, such as when
a function returns, or when an object is deallocated.
If one creates a shared pointer to an object using std::make_shared, and use a weak pointer to it as an observer. When the reference count of the shared pointer hits zero, the object is not deallocated because the weak pointer keeps it alive. (If I am not mistaken here.) Suppose that after a call of member function lock() on that weak pointer, and it turns out that it has expired. Now the programmer wants to call reset() to trigger destruction of the object, because the object is quite large.
The question is: is reset an atomic operation? If the answer is NO, my next question is that why the standard doesn't requires it being atomic.
The object is only deallocated after each weak_ptr that references the object is reset.
You don't modify single weak_ptr from multiple threads, so reset of a single weak_ptr don't need to be atomic.
C++20 introduces a helper class std::atomic, that guarantees, quote
The partial template specialization of std::atomic for std::weak_ptr allows users to manipulate weak_ptr objects atomically.
If multiple threads of execution access the same std::weak_ptr object
without synchronization and any of those accesses uses a non-const
member function of weak_ptr then a data race will occur unless all
such access is performed through an instance of
std::atomic>.
If one is not using C++20, check this SO answer by Chris Jester-Young for workaround.
I'm developing a small weak datastructures framework.
A collection of weakely wrapped object has a remove() method.
Inside the method i will delete an object if present and eventually purge the wrappers containing nil references.
The problem raise in the case the weakCollection.remove(object) is called inside a object.deinit() (may happen indirectly).
In this case (since i need to make a copy for comparison reasons) i will have a SIGABORT due to trying to reference an object that is being deallocated and this is forbidden in swift.
Cannot form weak reference to instance (0x608000199710) of class
XXX. It is possible that this object was
over-released, or is in the process of deallocation.
Normally i'd just document it, throw an error or a warning and skip. But i'd like to be safe about this and only remove an object when is not in "releasing" state.
One way would be reading the referenceCount, but in swift is not a good idea. Most other reflection/meta techniques i can think of are too expensive.
Plus: Another thing i'd really appreciate to know is if there is any notification/kno/observer i can connect to in order to be notified when object is being released.
I have a ViewModel class with a method like this:
func getUserSettings() {
UserSettingsManager.getInfo { (result, error) in
if error == nil {
self.userData = result
}
}
}
This class viewModel is instantiated and then viewModel.getUserSettings() is called. Such method is calling a static method UserSettings.getInfo which is passed an #escaping closure to be called as completion. That closure is capturing viewModel (it's using self within it's body).
What consequences does calling a static method have in terms of memory? How would that UserSettings class that is not instantiated be "deallocated"?
Could a strong reference cycle happen in this particular scenario? If so, how should self be captured: weak or strong?
What consequences does calling a static method have in terms of
memory? How would that UserSettings class that is not instantiated be
"deallocated"?
In the context of your question, the fact that the function is static doesn't have any special memory implications. Static methods have just as much potential to create reference cycles as non-static ones.
As you said, if there is no instance of UserSettingsManager, no instance will be deallocated. This fact alone does not eliminate the potential for reference cycles.
Could a strong reference cycle happen in this particular scenario? If so, how should self be captured: weak or strong?
Depending on what happens within getInfo, this could create a reference cycle. While, it seems unlikely, it's impossible to say for sure with the snippet you've posted.
For clarification, I should mention that you're currently capturing self strongly, which is default. This means the closure increments the strong reference count of the instance of self so that it can successfully interact with that instance when the closure is eventually invoked. To override this behavior and avoid a reference cycle, you'd use [weak self].
Finally, to visualize your current approach, you could think of it in the following manner:
UserSettingsManager → closure → self
That's a nice clean chain! A reference cycle would only occur if one of those links gained a reference back to another.