Bug 27559 - UITableView.DequeueReusableCell(NSString) crashes on iOS 8.3 beta 2
Summary: UITableView.DequeueReusableCell(NSString) crashes on iOS 8.3 beta 2
Status: RESOLVED ANSWERED
Alias: None
Product: iOS
Classification: Xamarin
Component: XI runtime ()
Version: XI 8.6.0
Hardware: PC Mac OS
: Normal normal
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2015-03-02 12:01 UTC by Alexey Bogdanov
Modified: 2015-03-03 09:42 UTC (History)
3 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Sample project that crashes. (6.44 KB, application/zip)
2015-03-03 02:28 UTC, Alexey Bogdanov
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description Alexey Bogdanov 2015-03-02 12:01:59 UTC
I have a custom UITableViewSource. In its constructor I register reusable cell identifier:

tableView.RegisterClassForCellReuse(typeof(MyCustomCell), new NSString("MyCustomCell"));

Then I use this cell in GetCell() override:
var cell = (MyCustomCell)tableView.DequeueReusableCell(new NSString("MyCustomCell"));

Everything works fine on iOS versions prior 8.3.

On iOS 8.3 beta 2 DequeuReusableCell crashes with "Exception has been thrown by the target of an invocation."

Stack trace:
0	
at at System.Reflection.MonoCMethod.InternalInvoke(System.Object obj, System.Object[] parameters) [0x00000] in <filename unknown>:0
1	
at System.Reflection.MonoCMethod.DoInvoke(System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
2	
at System.Reflection.MonoCMethod.Invoke(BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
3	
at System.Reflection.ConstructorInfo.Invoke(System.Object[] parameters) [0x00000] in <filename unknown>:0
4	
at ObjCRuntime.Runtime.ConstructNSObject[NSObject](IntPtr ptr, System.Type type, MissingCtorResolution missingCtorResolution) [0x00000] in <filename unknown>:0
5	
at ObjCRuntime.Runtime.ConstructNSObject(IntPtr ptr, IntPtr klass, MissingCtorResolution missingCtorResolution) [0x00000] in <filename unknown>:0
6	
at ObjCRuntime.Runtime.GetNSObject(IntPtr ptr, MissingCtorResolution missingCtorResolution, Boolean evenInFinalizerQueue) [0x00000] in <filename unknown>:0
7	
at ObjCRuntime.Runtime.TryGetOrConstructNSObjectWrapped(IntPtr ptr) [0x00000] in <filename unknown>:0
8	
at ObjCRuntime.Runtime.try_get_or_construct_nsobject(IntPtr obj) [0x00000] in <filename unknown>:0
9	
at(wrapper native-to-managed) ObjCRuntime.Runtime:try_get_or_construct_nsobject (intptr)
10	
at(wrapper managed-to-native) ObjCRuntime.Messaging:IntPtr_objc_msgSend_IntPtr (intptr,intptr,intptr)
11	
at UIKit.UITableView.DequeueReusableCell(Foundation.NSString identifier) [0x00000] in <filename unknown>:0
Comment 1 Alexey Bogdanov 2015-03-02 12:06:04 UTC
Exception type is System.Reflection.TargetInvocationException.
Comment 2 Sebastien Pouliot 2015-03-02 15:43:36 UTC
It looks like the type (of your custom cell) was not found, was it [Register] it ?

It's possible that iOS 8.3 does a call (back into managed code) that did not exists previously. That means that the missing `[Register]`, that was not required before, becomes _really_ required.

The `InnerException` of the `TargetInvocationException` would give us some clue about what's going on (in case I'm wrong about the above). Otherwise we'll need to test case to reproduce this.
Comment 3 Alexey Bogdanov 2015-03-03 02:27:56 UTC
It looks like the problem is setting layout margins of the custom cell:

public override UIEdgeInsets LayoutMargins
{
    get { return UIEdgeInsets.Zero; }
}

If I remove this code for 8.3, everything works as expected.
Comment 4 Alexey Bogdanov 2015-03-03 02:28:32 UTC
Created attachment 10124 [details]
Sample project that crashes.
Comment 5 Sebastien Pouliot 2015-03-03 08:51:09 UTC
The inner exception shows a NRE. That happens because `TextLabel` is null when `Initialize` is being called. e.g.

        private void Initialize()
        {
//            TextLabel.Text = "Custom cell";
        }

^ solves the crash.

My guess is that it's an iOS 8.3 bug that only happens when `LayoutMargins` is called. E.g. different code path taken, missing the TextLabel initialization (or because the the cell is created earlier to allow calling `LayoutMargins` before creating it's subviews).
Comment 6 Sebastien Pouliot 2015-03-03 09:18:15 UTC
LayoutMargins is called very often. The first times the TextLabel is `null` (on iOS 8.3) then it's later initialized.

        private void Initialize()
        {
//            TextLabel.Text = "Custom cell";
        }

        public override UIEdgeInsets LayoutMargins
        {
            get { 
				Console.WriteLine ("CustomCell.LayoutMargins get {0}", TextLabel);
				return UIEdgeInsets.Zero; 
			}
        }

2015-03-03 08:57:51.038 DequeueCrash[1022:326051] TableSource.ctor
2015-03-03 08:57:51.072 DequeueCrash[1022:326051] CustomCell.ctor(IntPtr)
2015-03-03 08:57:51.076 DequeueCrash[1022:326051] CustomCell.LayoutMargins get 
2015-03-03 08:57:51.077 DequeueCrash[1022:326051] CustomCell.LayoutMargins get 
2015-03-03 08:57:51.091 DequeueCrash[1022:326051] CustomCell.LayoutMargins get <UITableViewLabel: 0x176f6390; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x176f6670>>
2015-03-03 08:57:51.093 DequeueCrash[1022:326051] CustomCell.LayoutMargins get <UITableViewLabel: 0x176f6390; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x176f6670>>
2015-03-03 08:57:51.094 DequeueCrash[1022:326051] CustomCell.LayoutMargins get <UITableViewLabel: 0x176f6390; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x176f6670>>
2015-03-03 08:57:51.096 DequeueCrash[1022:326051] CustomCell.LayoutMargins get <UITableViewLabel: 0x176f6390; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x176f6670>>
...

However iOS high-level semantics remains identical, e.g.

        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
		var cell = _tableView.DequeueReusableCell(CellIdentifier, indexPath);
		cell.TextLabel.Text = "Custom cell";
            return cell;
        }

still works. IOW the cell that is returned by `DequeueReusableCell` is readily usable for consumption (but how it's initialized by iOS varies by iOS versions). Whether Apple will see this change as a bug (or not) is unknown.
Comment 7 Sebastien Pouliot 2015-03-03 09:42:19 UTC
A different way to initialize (which will place, and run, your code later) would be to remove the `Initialize` call from the existing ctor (default and the IntPtr one) and replace the one that is called by `DequeueReusableCell`. E.g.

        public CustomCell()
        {
        }

        public CustomCell(IntPtr handle) : base(handle)
        {
 	}

		[Export ("initWithStyle:reuseIdentifier:")]
		public CustomCell (UITableViewCellStyle style, NSString reuseIdentifier) : base (style, reuseIdentifier)
		{
			Initialize();
		}

        private void Initialize()
        {
            TextLabel.Text = "Custom cell";
        }

This allows iOS to call `init` (and Layout) before calling `initWithStyle:reuseIdentifier:` which, at that point, would have it's subviews initialized.

Personally I kind of prefer the previous approach - because it's never safe to call virtual (anything that can be overridden) from a constructor. That's true for most (if not all) OO languages, e.g.

.NET https://msdn.microsoft.com/en-us/library/ms182331.aspx
C++ http://stackoverflow.com/a/962148/220643

and often lead to subtle bugs.