Why can't MonoTouch GC kill managed objects with refcount > 1? - xamarin.ios

I think I'm getting close to understanding how Mono GC and ObjC ref counting live together.
The way it works is that when a native object has a reference count of 1, we do not prevent the managed instance from getting garbage collected. As soon as the reference count increases above 1, we prevent the managed instance from getting garbage collected.
This is because a managed object may contain user state. For managed objects which are mirroring a corresponding native object (such as the managed UIView instance) MonoTouch knows that the instance can not contain any state, so as soon as no managed code has a reference to the managed instance, the GC can collect it. If a managed instance is required at a later stage, we just create a new one.
So if I create a CustomButton that inherits UIButton, add it as subview to my View, let the managed reference slip out of scope and then run GC, this managed CustomButton still won't be eligible for collection.
Why can't it be collected? Of course it may have managed state like properties, but if there is no link to it from managed objects, who cares about this state? It may as well just disappear, why can't it?
I'm thinking of one possible reason: subscribing to CustomButton events won't keep it alive for the GC so when the object gets collected, events stop firing. This would perhaps result in unexpected behavior.
Is this correct? Are there other reasons for keeping the managed object alive even if no one links it?

Why can't it be collected? Of course it may have managed state like properties, but if there is no link to it from managed objects, who cares about this state? It may as well just disappear, why can't it?
Native code might have references to the object, which may cause the object to resurface to managed code again later.
I believe a code sample would illustrate what would happen:
class MyView : UIView {
public string ImportantSecret;
}
class AppDelegate : UIApplicationDelegate {
UIViewController vc;
public override bool FinishedLaunching (UIApplication app,
NSDictionary options)
{
var myView = new MyView ();
myView.ImportantSecret = "MonoTouchRocks";
vc = new UIViewController ();
vc.View = new UIView ();
vc.View.AddSubView (myView);
// When this method returns the only place where myView is referenced
// is from inside the *native* Subviews collection.
BeginInvokeOnMainThread (() =>
{
Console.WriteLine (((MyView) vc.Subviews [0]).ImportantSecret);
// If the MyView instance was garbage collected and recreated
// automatically at this point, ImportantSecret would be null.
});
}
}
Important: this code is just to illustrate the reason why the GC can't collect managed objects which may have state. This particular sample would actually not forget the important secret, since the Subviews array is automatically cached in managed code - but this is not generally true.

Related

NSManagedObjectContext objectWithID lifecycle method (derived property)

I have an NSManagedObject subclass with a transient property which is basically a reformatting of one of the persistent to-many relationships. I do this by observing the relationship with KVO and registering the observer in -awakeFromFetch, -awakeFromInsert, etc.
This all works fine, however if I pass the object between threads using the object's objectID and -objectWithID: technique there is no life-cycle method into which I can hook generation of the transient property. None of the life-cycle methods are triggered, in fact, since accessing the object directly using the id isn't considered a fetch, it seems.
There are ways around this, but it would be nice to use a life-cycle based technique. Am I perhaps missing something? Is there another standard method I could be using?
Thanks
Edit: Demonstration project
https://mega.co.nz/#!UsNBTZ7S!UU1qaFuc4W6Z2EYey-9AiMyfM8203Zfrm1lfpG5QITU
When you have a NSManagedObject instance on one thread with a context and then retrieve it from a different thread and different context the -awakeFromFetch or -awakeFromInsert fires.
Are you using the contexts properly so that you are retrieving a new instance?
Have you looked at the pointers in the debugger to make sure you are talking to a new instance of the NSManagedObject?
In my experience those lifecycle methods fire per context.
Ok, so to answer my own question, the issue is caused because objectWithID: always returns an object, even though the object isn't registered in the receiver managed object context. Seems in some circumstances objectRegisteredForID: is more informative. In any case, the conclusion is that the life-cycle methods do fire, but to take care with objectWithID: since it can result in an inconsistent object.
I encountered this same issue, and solved it by creating an extension on NSManagedObjectContext that goes through the normal fetch pathway, therefore triggering all of the expected lifecycle methods:
extension NSManagedObjectContext {
func fetchObject<T: NSManagedObject>(with objectID: NSManagedObjectID) -> T? {
let request = T.fetchRequest()
request.predicate = NSPredicate(format: "SELF = %#", objectID)
guard let result = try? fetch(request) else {
return nil
}
return result.first as? T
}
}

How to dispose of a ViewModel in Durandal after Logout

I might be on the wrong track here, but here goes:
In my PhoneGap Durandal app, I have a Profile View/VM which only returns data the first time it is hit - after that it checks a bool called initialised and wont hit the DB again the 2nd time. This works fine.
However after Logout, I need to invalidate the cache. I could use a message to tell the Profile VM to clear the variable (ie. invalidate the cache) but I thought perhaps there is a higher-level way of doing this in Durandal - e.g. On Logout, I tell dispose of all ViewModels in memory (there may be other Singleton objects with session specific info in them).
Advice please...
This is more of a javascript question and this is just my understanding of how javascript works.
Javascript will automatically dispose of objects that are no longer referenced through a mechanism called Garbage Collection.
Here is a good article on how Garbage Collection works. Basically it will dispose of objects that are no longer referenced in your program.
There is another method in javascript that allows you to remove objects. The delete method:
delete someobj;
Which too my knowledge is pretty much equal to someobj = undefined;
Hope this helps.
***Edit
Durandal follows the screen activator pattern for it's viewmodels. So apart of the viewmodel lifecycle it will call an activate, candeactivate, and deactivate method.
You could do your disposing in the deactivate method.
(Durandal 2.0) You could always hook into the composition life-cycle callback methods on your view-model. There are four: activate(), attached(), deactivate(), and detached(). They are called automatically by Durandal on your view-model, if they exist. In my projects, if I need a view to invalidate its cache, I hook into the deactivate() method and put the cleanup logic there. Similarly, I use the detached() method to unbind events and destroy UI widgets.
Simple example:
define(['modules/myDataService'],
function(dataservice) {
var cache;
function activate() {
return dataservice.getData().done(function(response) {
cache = response;
});
}
function deactivate() {
cache = null;
}
return {
activate: activate,
deactivate: deactivate
};
});
Source documentation: http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks/

Does every monotouch UIKit variable need to be at class scope?

In this Xamarin forum post, Xamarin admin Clancey says:
But as a general rule, anything that has that type of interaction say
a UIButton, you really need to keep a reference to it.
For example:
//This can will blow up
public override ViewDidLoad()
{
var myButton = new UIButton();
myButton.Tapped += delegate{
//do something
};
View.AddSubview(myButton);
}
This is because myButton goes out of scope and is GC'ed, but the underlying iOS button handle is still alive, and if ObjectiveC calls back to Mono with that button handle, the app crashes. So you need to make myButton a class level variable, so it stays alive.
My question is: what about other kinds of UIKit objects, like UIImage? Suppose I do this:
var image = new UIImage(sFilename);
ctlImageView = new UIImageView(image);
this.View.Add(ctlImageView);
ctlImageView is at class scope, but image isn't. Can this cause the same kind of memory leak, and does image also need to be at class scope? Or are UIView objects special in this regard?
You don't need to keep a reference to the UIImage because the UIImageView will hold one for you and clean it up when it is disposed.

Is this a bug in MonoTouch GC?

Note: I've created a simple project—you can see how switching types between UIButton and CustomButton in storyboard changes GC behavior.
I'm trying to get my head wrapped around MonoTouch garbage collector.
The issue is similar to the one fixed in MT 4.0, however with inherited types.
To illustrate it, consider two view controllers, parent and child.
Child's view contains a single UIButton that writes to console on tap.
Controller's Dispose method throws an exception so it's hard to miss.
Here goes child view controller:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
sayHiButton.TouchUpInside += (sender, e) =>
SayHi();
}
}
void SayHi()
{
Console.WriteLine("Hi");
}
protected override void Dispose (bool disposing)
{
throw new Exception("Hey! I've just been collected.");
base.Dispose (disposing);
}
Parent view controller just presents child controller and sets a timer to dismiss it and run GC:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var child = (ChildViewController)Storyboard.InstantiateViewController("ChildViewController");
NSTimer.CreateScheduledTimer(2, () => {
DismissViewController(false, null);
GC.Collect();
});
PresentViewController(child, false, null);
}
If you run this code, it predictably crashes inside ChildViewController.Dispose() called from its finalizer because child controller has been garbage collected. Cool.
Now open the storyboard and change button type to CustomButton. MonoDevelop will generate a simple UIButton subclass:
[Register ("CustomButton")]
public partial class CustomButton : UIButton
{
public CoolButton (IntPtr handle) : base (handle)
{
}
void ReleaseDesignerOutlets()
{
}
}
Somehow changing the button type to CustomButton is enough to trick garbage collector into thinking child controller is not yet eligible for collection.
How is that so?
This is an unfortunate side-effect of MonoTouch (who is garbage collected) having to live in a reference counted world (ObjectiveC).
There are a few pieces of information required to be able to understand what's going on:
For every managed object (derived from NSObject), there is a corresponding native object.
For custom managed classes (derived from framework classes such as UIButton or UIView), the managed object must stay alive until the native object is freed [1]. The way it works is that when a native object has a reference count of 1, we do not prevent the managed instance from getting garbage collected. As soon as the reference count increases above 1, we prevent the managed instance from getting garbage collected.
What happens in your case is a cycle, which crosses the MonoTouch/ObjectiveC bridge and due to the above rules, the GC can't determine that the cycle can be collected.
This is what happens:
Your ChildViewController has a sayHiButton. The native ChildViewController will retain this button, so its reference count will be 2 (one reference held by the managed CustomButton instance + one reference held by the native ChildViewController).
The TouchUpInside event handler has a reference to the ChildViewController instance.
Now you see that the CustomButton instance will not be freed, because its reference count is 2. And the ChildViewController instance will not be freed because the CustomButton's event handler has a reference to it.
There are a couple of ways to break the cycle to fix this:
Detach the event handler when you no longer need it.
Dispose the ChildViewController when you no longer need it.
[1] This is because a managed object may contain user state. For managed objects which are mirroring a corresponding native object (such as the managed UIView instance) MonoTouch knows that the instance can not contain any state, so as soon as no managed code has a reference to the managed instance, the GC can collect it. If a managed instance is required at a later stage, we just create a new one.

Application Object Won't Share

I'm having issues with my Application Object. I am currently using a Service to simulate incoming data from an electronic game board. This data is represented as a 2D boolean array. Every five seconds the Service uses a method of the Application Object to update the array (setDetectionMap()). This array is being read by a Thread in my main Activity using another method (getDetectionMap()). After some debugging I am almost positive that the main Activity is not seeing the changes. Here is the code for my Application Object:
public class ChessApplication extends Application{
private static ChessApplication singleton;
private boolean[][] detectionMap;
public static ChessApplication getInstance(){
return singleton;
}
#Override
public void onCreate() {
super.onCreate();
singleton=this;
detectionMap=new boolean[8][8];
}
public boolean[][] getDetectionMap(){
return detectionMap;
}
public void setDetectionMap(boolean[][] newMap){
detectionMap=newMap;
Log.d("Chess Application","Board Changed");
}
}
I've checked my Manifest, I've rewritten my object declaration a dozen times, I've added LogCat tags to make sure that the code is executing when I think it should be, and I've even implemented the supposedly redundant Singleton code. Any ideas what could be causing this? Incidentally can anyone tell me how to view variable states as the activity is running? Thanks in advance.
Is your Activity calling getDetectionMap() to get the new map after the update occurs?
Because otherwise, it's holding onto a reference to the old boolean[][] array, wheras setDetectionMap(...) isn't actually updating the current data structure, it's just updating the "detectionMap" variable to point to a different one. As such, your main activity won't be aware of the swapout until the next time it calls getDetectionMap.
Easy fix: in setDetectionMap, manually copy values from newMap into detectionMap. Or, update the Activity's reference so it's looking at the right map.
One other observation entirely unrelated to the original question: It's quite unusual to override Application during Android development, and is usually considered a "code smell" unless you have a really good reason for doing so. In this case I imagine it's so that you can communicate between your service and Activity, but you create a middle-man where one isn't entirely necessary. Here's a useful SO thread on how to communicate directly between the two :)

Resources