I’m having a memory issue in my iPhone app. I'm using MonoTouch. I have hunted down the problem, by using a static instance counters. The problem has something to do with modal view controllers. When I navigate from a root-viewcontroller to a first-level-viewcontroller and back, I find that the first-level-viewcontroller is garbage-collected. But when I make the first-level-viewcontroller modal by calling PresentModalViewController, and I return by calling DismissModalViewControllerAnimated, I find that the first-level-viewcontroller is not garbage collected. Not even when I call GC.Collect().
Why not? Am I doing something wrong?
What is the best practice for ensuring release of view controllers?
partial class RootViewController : UITableViewController
{
static int instanceCount;
static int nextId;
int instanceId;
public RootViewController (IntPtr handle) : base(handle)
{
instanceCount++;
instanceId = nextId++;
Console.WriteLine(string.Format("RootViewController #{0} Count={1}", instanceId, instanceCount));
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
Title = "Root";
NavigationItem.RightBarButtonItem = new UIBarButtonItem("ModalVC", UIBarButtonItemStyle.Plain,
delegate
{
var firstlevelVc = new FirstLevelViewController();
PresentModalViewController(new UINavigationController(firstlevelVc), true);
});
NavigationItem.LeftBarButtonItem = new UIBarButtonItem("PushVC", UIBarButtonItemStyle.Plain,
delegate
{
var firstlevelVc = new FirstLevelViewController();
NavigationController.PushViewController(firstlevelVc, true);
});
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
GC.Collect();
}
~RootViewController()
{
instanceCount--;
Console.WriteLine(string.Format("RootViewController #{0} Count={1}", instanceId, instanceCount));
}
}
public partial class FirstLevelViewController : UIViewController
{
static int instanceCount;
static int nextId;
int instanceId;
public FirstLevelViewController (IntPtr handle) : base(handle)
{
Initialize ();
}
[Export("initWithCoder:")]
public FirstLevelViewController (NSCoder coder) : base(coder)
{
Initialize ();
}
public FirstLevelViewController () : base("FirstLevelViewController", null)
{
Initialize ();
}
void Initialize ()
{
instanceCount++;
instanceId = nextId++;
Console.WriteLine(string.Format("FirstLevelViewController #{0} Count={1}", instanceId, instanceCount));
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
Title = "1. level";
NavigationItem.RightBarButtonItem = new UIBarButtonItem("Dismiss modal",
UIBarButtonItemStyle.Plain,
delegate { ParentViewController.DismissModalViewControllerAnimated(true); });
}
~FirstLevelViewController()
{
instanceCount--;
Console.WriteLine(string.Format("FirstLevelViewController #{0} Count={1}", instanceId, instanceCount));
}
}
Finalizer (~method) is being called when reference counter on an object comes to 0, are you sure you do not have a reference somewhere to the UIViewController you use?
To be sure I do not have a reference and ensure GC removal, I implement IDisposable and Dispose method, call it and do the necessary cleaning there (remove all objects from inner collections and such) and I do set the original variable to null. So it looks like:
MyUIViewController cntrl = new MyUIViewController();
... do some stuff
// when done
cntrl.Dispose();
cntrl = null;
And that is all.
BTW. I believe you should not call GC.Collect by yourself that frequently, as it stops all threads and does only the cleanup, all work is at that time stopped and is quite heavy on resources.
Hope it helps.
If you want to dispose the controller manually, you can call Dispose on it.
Related
I have two coordinators for Flow A and Flow B.
They looks like this:
final class HomeCoordinator: Coordinator {
var navigationController: UINavigationController
init(navigationController: UINavigationController = UINavigationController()) {
self.navigationController = navigationController
So for each coordinator I start the flow with an UINavigationController.
Lets say that coordinator with Flow A needs to display CommonViewController, but the coordinator with Flow B would also like to show the CommonViewController.
Since the coordinator is injected in CommonViewController, it can't be both CoordinatorA or CoordinatorB. So to perform coordinator operations I've added a delegate that looks like this:
protocol CommonViewControllerDelegate: AnyObject {
func showAnotherViewController()
}
class CommonViewController: UIViewController {
weak var delegate: CommonViewControllerDelegate?
But with this approach I have duplication of code because both CoordinatorA and CorodinatorB should implement showAnotherViewController method. And I have multiple view controllers like this, sometimes the delegate doesn't work properly and it's a chaos.
How can I solve this problem? I thought about having one coordinator, but I prefer to keep them separate so I can instantiate one UINavigationController per Coordinator.
I am not Swift guy, but give me a try. If you want to share the same behaviour between classes, then you can use composition or inheritance.
Let me show an example of inheritance via C#:
public class AnotherViewController
{
public string Show()
{
return "Show AnotherViewController";
}
}
public class CoordinatorA : AnotherViewController
{
}
public class CorodinatorB : AnotherViewController
{
}
and an implementation with composition would look like this:
public class AnotherViewController
{
public string Show()
{
return "Show AnotherViewController";
}
}
public class CoordinatorA
{
private AnotherViewController _anotherViewController;
public CoordinatorA()
{
_anotherViewController = new AnotherViewController();
}
}
public class CorodinatorB
{
private AnotherViewController _anotherViewController;
public CorodinatorB()
{
_anotherViewController = new AnotherViewController();
}
}
It is worth to read also this topic when to choose composiiton or inheritance.
I have some animations that I want to trigger once the view has loaded.
Some of them rely on position values of other views on the page but at the time that OnAppearing fires, the X and Y values for these controls have not been set.
Others can just run by themselves but because they start in OnAppearing, the first few frames aren't rendered.
Adding a Task.Delay into the start of the methods solves the problem but is obviously not great.
Is there any way to create such a method or maybe a way to do it with behaviours? They need to trigger automatically, not in response to some control event like TextChanged etc.
Thanks!
You can do it from the native side, In Ios, you can override the ViewDidLoad method in custom renderer like:
public class MyPageRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//call before ViewWillAppear and only called once
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
}
}
and android, override the OnAttachedToWindow method:
public class MyPageRenderer : PageRenderer
{
public MyPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
}
}
`VisualElement` is the base class for most `Microsoft.Maui.Control`s:
public partial class NameOfThePage : ContentPage
{
public NameOfThePage()
{
InitializeComponent();
this.Loaded += NameOfThePage_Loaded;
}
private void NameOfThePage_Loaded(object sender, EventArgs e)
{
/* animations you want to trigger */
}
}
Unfortunately no OnLoad() to override.
I am trying from iOS and use it Xamarin Forms..issue is that.i don't know swift at all..
#objc func scanDocument()
{
let testViewController = ViewControllerCamer()
testViewController.delegate = self
present(scannler, animated: true)
}
In Xamarin we need to implement the protocol like following
public class MyDelegate : VNDocumentCameraViewControllerDelegate
{
public override void DidFinish(VNDocumentCameraViewController controller, VNDocumentCameraScan scan)
{
base.DidFinish(controller, scan);
//do something you want
}
public override void DidCancel(VNDocumentCameraViewController controller)
{
base.DidCancel(controller);
}
public override void DidFail(VNDocumentCameraViewController controller, NSError error)
{
base.DidFail(controller, error);
}
}
VNDocumentCameraViewController control = new VNDocumentCameraViewController();
control.Delegate = new MyDelegate();
The code is like below,but I think we can't use dispatch_once token in Swift.
I tried use lazy and failed because lazy need to be used in a member class.
'lazy' must not be used on an already-lazy global
The class is static and have no variable to use,except for static variable.
#objc public class LaunchPipeLineApi : NSObject {
private static var shared = Applaunch_AppLaunchMetrics()
#objc public static func markApplicationStartTime() {
shared.applicationStartTime = Int64(1000*Date.init().timeIntervalSince1970);
}
#objc public static func markApplicationEndTime() {
shared.applicationEndTime = Int64(1000*Date.init().timeIntervalSince1970);
}
#objc public static func markFirstFrameRenderEndTime() {
shared.firstFrameRenderEndTime = Int64(1000*Date.init().timeIntervalSince1970);
}
#objc public static func markFirstInteractiveTime() {
shared.firstInteractiveTime = Int64(1000*Date.init().timeIntervalSince1970);
}
#objc public static func startReportAppLaunchMetrics(report:(_:String,_:Data) ->()) {
//here,I want to call this report only once even the function caller do multiple calls.How can I do?
do {
report("AppLaunchMonitor",try shared.serializedData())
} catch {}
}
}
The most common use of dispatch_once was the thread-safe instantiation of some shared instance. E.g. consider:
#implementation MyObject
+ (instancetype)sharedInstance {
static MyObject *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
#end
In Swift, we now achieve the exact same behavior much more simply:
class MyObject {
static let shared = MyObject()
}
That offers the same thread-safe instantiation logic as that Objective-C pattern.
In your example, you have the static property, shared, which already enjoys this thread-safe, run-only-once, pattern. (I would make it a let rather than a var, though.)
So, you have your thread-safe instantiation logic already. So the question is whether you really need this dispatch_once sort of behavior for the report of the serializedData. Can you just put that in the init method of the Applaunch_AppLaunchMetrics class? That would be easiest, cutting the Gordian knot.
If you really want dispatch_once sort of logic for startReportAppLaunchMetrics (even though the static property shared already has that behavior), you can just have some state property to keep track of this (and then synchronize it with a lock or GCD serial queue or whatever). For example:
static let lock = NSLock()
static var hasRun = false
static func thisCanBeCalledMultipleTimesFromMultipleThreadsButRunOnlyOnce() {
lock.lock()
defer { lock.unlock() }
if !hasRun {
hasRun = true
// do something
}
}
Or
static let synchronizationQueue = DispatchQueue(label: "synchronizationQueue")
static var hasRun = false
static func thisCanBeCalledMultipleTimesFromMultipleThreadsButRunOnlyOnce() {
synchronizationQueue.async {
if !hasRun {
hasRun = true
// do something
}
}
}
So, you can do that if absolutely needed, but I cannot help but wonder that, given that your shared already enjoys thread-safe, dispatch-once, sort of behavior that all static properties enjoy, do you really need that in this report of the serializedData, too?
I have a simple UITableView and I am loading the data by implementing UITableViewSource. I also have a label on the same page. I would like to display the selected row from tableview on label.
This is my MainViewController:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
List<string> lstTableSource = new List<string> ();
for (int intCounter=0; intCounter<5; intCounter++)
{
lstTableSource.Add ("Row "+intCounter);
}
tblTest.Source = new TableSource (lstTableSource.ToArray());
}
public void Test(string strSelected)
{
lblTest.Text = strSelected;
}
This is my row selection method in TableSource.cs
public override void RowSelected (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
MainViewControllerObj.Test (tableItems[indexPath.Row]);
}
When i tap on the row system is throwing NullReferenceException on
lblTest.Text = strSelected;
Program works fine if I print the value of the variable strSelected on a Console.