Skip to content

Commit 965bbe9

Browse files
[RGen] Generate the initWithCoder: constructor.
If a class inherits from NSCoding or implements INSCoding we generate the constructor for the 'initWithCoder:' selector. If the user defined a constructor with the selector 'initWithCoder:' selector, we will skip the user definition and will add our own. Later the analyzer will be used to let the user know that 'initWithCoder:' is automatically added.
1 parent 81c3edf commit 965bbe9

File tree

11 files changed

+707
-0
lines changed

11 files changed

+707
-0
lines changed

src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ namespace Microsoft.Macios.Generator.Emitters;
2525
/// Emitter for Objective-C classes.
2626
/// </summary>
2727
class ClassEmitter : IClassEmitter {
28+
29+
const string initWithCoderSelector = "initWithCoder:";
30+
2831
/// <inheritdoc />
2932
public string GetSymbolName (in Binding binding)
3033
{
@@ -47,6 +50,45 @@ public string GetSymbolName (in Binding binding)
4750
"ObjCRuntime",
4851
];
4952

53+
/// <summary>
54+
/// Emits the NSCoding constructor (initWithCoder:) for classes that inherit from NSCoding or implement INSCoding.
55+
/// This constructor is required for proper serialization support and includes appropriate thread checks when needed.
56+
/// </summary>
57+
/// <param name="context">The current binding context containing class information.</param>
58+
/// <param name="classBlock">The writer for the class block to emit the constructor into.</param>
59+
static void EmitNSCondingConstructor (in BindingContext context, TabbedWriter<StringWriter> classBlock)
60+
{
61+
// used to check if we need to check that we are running in the ui thread, this is a special case
62+
// for the NSCoding constructor
63+
var uiThreadCheck = (context.NeedsThreadChecks)
64+
? EnsureUiThread (context.RootContext.CurrentPlatform)
65+
: null;
66+
67+
classBlock.WriteDocumentation (Documentation.Class.InitWithCoder (context.Changes.Name));
68+
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
69+
classBlock.AppendDesignatedInitializer ();
70+
classBlock.AppendEditorBrowsableAttribute (EditorBrowsableState.Advanced);
71+
if (GeneratorConfiguration.BGenCompatible) {
72+
classBlock.AppendBgenExportAttribute (initWithCoderSelector);
73+
}
74+
using (var constructorBlock = classBlock.CreateBlock (
75+
$"public {context.Changes.Name} (NSCoder coder) : base (NSObjectFlag.Empty)", block: true)) {
76+
if (uiThreadCheck is not null) {
77+
constructorBlock.WriteLine (uiThreadCheck.ToString ());
78+
constructorBlock.WriteLine ();
79+
}
80+
81+
constructorBlock.WriteRaw (
82+
$@"if (IsDirectBinding) {{
83+
InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSend_IntPtr (this.Handle, Selector.GetHandle (""{initWithCoderSelector}""), coder.Handle), ""{initWithCoderSelector}"");
84+
}} else {{
85+
InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_IntPtr (this.SuperHandle, Selector.GetHandle (""{initWithCoderSelector}:""), coder.Handle), ""{initWithCoderSelector}"");
86+
}}
87+
GC.KeepAlive (coder);
88+
");
89+
}
90+
}
91+
5092
/// <summary>
5193
/// Emit the code for all the constructors in the class.
5294
/// </summary>
@@ -57,6 +99,12 @@ void EmitConstructors (in BindingContext context, TabbedWriter<StringWriter> cla
5799
// When dealing with constructors we cannot sort them by name because the name is always the same as the class
58100
// instead we will sort them by the selector name so that we will always generate the constructors in the same order
59101
foreach (var constructor in context.Changes.Constructors.OrderBy (c => c.ExportMethodData.Selector)) {
102+
// if the class implements extends NSConding or implements INSCoding we need to skip the constructor
103+
// with the selector 'initWithCoder:' since that is generated by default and we do not want to have two
104+
// constructors with the same signature
105+
if (context.Changes.TypeInfo.IsNSCoding && constructor.Selector == initWithCoderSelector)
106+
continue;
107+
60108
classBlock.AppendMemberAvailability (constructor.SymbolAvailability);
61109
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
62110
if (constructor.ExportMethodData.Flags.HasFlag (Constructor.DesignatedInitializer)) {
@@ -255,6 +303,12 @@ public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out
255303
this.EmitDefaultNSObjectConstructors (className: bindingContext.Changes.Name,
256304
classBlock: classBlock,
257305
disableDefaultCtor: bindingData.Flags.HasFlag (ObjCBindings.Class.DisableDefaultCtor));
306+
307+
// A class that inherits from NSCoding OR implements the INSCoding interface requires to create
308+
// the 'initWithCoder:' constructor
309+
if (bindingContext.Changes.TypeInfo.IsNSCoding) {
310+
EmitNSCondingConstructor (bindingContext, classBlock);
311+
}
258312

259313
// emit any other constructor that is not the default one
260314
EmitConstructors (bindingContext, classBlock);

src/rgen/Microsoft.Macios.Generator/Emitters/Documentation.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ public static string DefaultInitWithHandle (string name) =>
138138

139139
public static string TrampolineStaticClass (string name) =>
140140
"/// <summary>This class bridges native block invocations that call into C#</summary>";
141+
142+
public static string InitWithCoder (string name) =>
143+
@"/// <summary>A constructor that initializes the object from the data stored in the unarchiver object.</summary>
144+
/// <param name=""coder"">The unarchiver object.</param>
145+
/// <remarks>
146+
/// <para>This constructor is provided to allow the class to be initialized from an unarchiver (for example, during NIB deserialization). This is part of the <see cref=""Foundation.NSCoding"" /> protocol.</para>
147+
/// <para>If developers want to create a subclass of this object and continue to support deserialization from an archive, they should implement a constructor with an identical signature: taking a single parameter of type <see cref=""Foundation.NSCoder"" /> and decorate it with the <c>[Export(""initWithCoder:""]</c> attribute.</para>
148+
/// <para>The state of this object can also be serialized by using the <see cref=""Foundation.INSCoding.EncodeTo"" /> companion method.</para>
149+
/// </remarks>";
141150

142151
}
143152

@@ -149,5 +158,6 @@ public static string EmptyConstructor (string name) =>
149158
public static string InitWithDictionary (string name) =>
150159
$@"/// <summary>Creates a new <see cref=""{name}"" /> from the values that are specified in <paramref name=""dictionary"" />.</summary>
151160
/// <param name=""dictionary"">The dictionary to use to populate the properties of this type.</param>";
161+
152162
}
153163
}

tests/rgen/Microsoft.Macios.Generator.Tests/Classes/ClassGenerationTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ public class TestDataGenerator : BaseTestDataGenerator, IEnumerable<object []> {
117117
{"EventTests_RgenNSKeyedArchiverDelegate.g.cs", "ExpectedEventTests_RgenNSKeyedArchiverDelegate.cs"}
118118
}
119119
},
120+
121+
// NSCoding tests
122+
new (ApplePlatform.iOS, "NSCodingSubclass", "NSCodingSubclass.cs", "ExpectedNSCodingSubclass.cs"),
123+
new (ApplePlatform.TVOS, "NSCodingSubclass", "NSCodingSubclass.cs", "ExpectedNSCodingSubclass.cs"),
124+
new (ApplePlatform.MacCatalyst, "NSCodingSubclass", "NSCodingSubclass.cs", "ExpectedNSCodingSubclass.cs"),
125+
new (ApplePlatform.MacOSX, "NSCodingSubclass", "macosNSCodingSubclass.cs", "macosExpectedNSCodingSubclass.cs"),
126+
127+
new (ApplePlatform.iOS, "NSCodingSubclassWithConstructor", "NSCodingSubclassWithConstructor.cs", "ExpectedNSCodingSubclassWithConstructor.cs"),
128+
new (ApplePlatform.TVOS, "NSCodingSubclassWithConstructor", "NSCodingSubclassWithConstructor.cs", "ExpectedNSCodingSubclassWithConstructor.cs"),
129+
new (ApplePlatform.MacCatalyst, "NSCodingSubclassWithConstructor", "NSCodingSubclassWithConstructor.cs", "ExpectedNSCodingSubclassWithConstructor.cs"),
130+
new (ApplePlatform.MacOSX, "NSCodingSubclassWithConstructor", "macosNSCodingSubclassWithConstructor.cs", "macosExpectedNSCodingSubclassWithConstructor.cs"),
120131
};
121132

122133
public IEnumerator<object []> GetEnumerator ()
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// <auto-generated />
2+
3+
#nullable enable
4+
5+
using AVFoundation;
6+
using CoreGraphics;
7+
using Foundation;
8+
using ObjCBindings;
9+
using ObjCRuntime;
10+
using System;
11+
using System.ComponentModel;
12+
using System.Diagnostics;
13+
using System.Diagnostics.CodeAnalysis;
14+
using System.Drawing;
15+
using System.Runtime.InteropServices;
16+
using System.Runtime.Versioning;
17+
18+
namespace UIKit;
19+
20+
[SupportedOSPlatform ("macos")]
21+
[SupportedOSPlatform ("ios")]
22+
[SupportedOSPlatform ("tvos")]
23+
[SupportedOSPlatform ("maccatalyst13.1")]
24+
[Register ("NSCodingSubclass", true)]
25+
public partial class NSCodingSubclass
26+
{
27+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
28+
static readonly global::ObjCRuntime.NativeHandle class_ptr = global::ObjCRuntime.Class.GetHandle ("NSCodingSubclass");
29+
30+
/// <summary>The Objective-C class handle for this class.</summary>
31+
/// <value>The pointer to the Objective-C class.</value>
32+
/// <remarks>
33+
/// Each managed class mirrors an unmanaged Objective-C class.
34+
/// This value contains the pointer to the Objective-C class.
35+
/// It is similar to calling the managed <see cref=\"ObjCRuntime.Class.GetHandle(string)\" /> or the native <see href=\"https://developer.apple.com/documentation/objectivec/1418952-objc_getclass\">objc_getClass</see> method with the type name.
36+
/// </remarks>
37+
public override global::ObjCRuntime.NativeHandle ClassHandle => class_ptr;
38+
39+
/// <summary>Creates a new <see cref="NSCodingSubclass" /> with default values.</summary>
40+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
41+
[DesignatedInitializer]
42+
[Export ("init")]
43+
public NSCodingSubclass () : base (global::Foundation.NSObjectFlag.Empty)
44+
{
45+
if (IsDirectBinding)
46+
InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSend (this.Handle, global::ObjCRuntime.Selector.GetHandle ("init")), "init");
47+
else
48+
InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper (this.SuperHandle, global::ObjCRuntime.Selector.GetHandle ("init")), "init");
49+
}
50+
51+
/// <summary>Constructor to call on derived classes to skip initialization and merely allocate the object.</summary>
52+
/// <param name="t">Unused sentinel value, pass NSObjectFlag.Empty.</param>
53+
/// <remarks>
54+
/// <para>
55+
/// This constructor should be called by derived classes when they completely construct the object in managed code and merely want the runtime to allocate and initialize the <see cref="Foundation.NSObject" />.
56+
/// This is required to implement the two-step initialization process that Objective-C uses, the first step is to perform the object allocation, the second step is to initialize the object.
57+
/// When developers invoke this constructor, they take advantage of a direct path that goes all the way up to <see cref="Foundation.NSObject" /> to merely allocate the object's memory and bind the Objective-C and C# objects together.
58+
/// The actual initialization of the object is up to the developer.
59+
/// </para>
60+
/// <para>
61+
/// This constructor is typically used by the binding generator to allocate the object, but prevent the actual initialization to take place.
62+
/// Once the allocation has taken place, the constructor has to initialize the object.
63+
/// With constructors generated by the binding generator this means that it manually invokes one of the "init" methods to initialize the object.
64+
/// </para>
65+
/// <para>It is the developer's responsibility to completely initialize the object if they chain up using this constructor chain.</para>
66+
/// <para>
67+
/// In general, if the developer's constructor invokes the corresponding base implementation, then it should also call an Objective-C init method.
68+
/// If this is not the case, developers should instead chain to the proper constructor in their class.
69+
/// </para>
70+
/// <para>
71+
/// The argument value is ignored and merely ensures that the only code that is executed is the construction phase is the basic <see cref="Foundation.NSObject" /> allocation and runtime type registration.
72+
/// Typically the chaining would look like this:
73+
/// </para>
74+
/// <example>
75+
/// <code lang="csharp lang-csharp"><![CDATA[
76+
/// //
77+
/// // The NSObjectFlag constructor merely allocates the object and registers the C# class with the Objective-C runtime if necessary.
78+
/// // No actual initXxx method is invoked, that is done later in the constructor
79+
/// //
80+
/// // This is taken from the iOS SDK's source code for the UIView class:
81+
/// //
82+
/// [Export ("initWithFrame:")]
83+
/// public UIView (System.Drawing.RectangleF frame) : base (NSObjectFlag.Empty)
84+
/// {
85+
/// // Invoke the init method now.
86+
/// var initWithFrame = new Selector ("initWithFrame:").Handle;
87+
/// if (IsDirectBinding) {
88+
/// Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSend_CGRect (this.Handle, initWithFrame, frame);
89+
/// } else {
90+
/// Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_CGRect (this.SuperHandle, initWithFrame, frame);
91+
/// }
92+
/// }
93+
/// ]]></code>
94+
/// </example>
95+
/// </remarks>
96+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
97+
[EditorBrowsable (EditorBrowsableState.Advanced)]
98+
protected NSCodingSubclass (global::Foundation.NSObjectFlag t) : base (t) {}
99+
100+
/// <summary>A constructor used when creating managed representations of unmanaged objects. Called by the runtime.</summary>
101+
/// <param name="handle">Pointer (handle) to the unmanaged object.</param>
102+
/// <remarks>
103+
/// <para>
104+
/// This constructor is invoked by the runtime infrastructure (<see cref="ObjCRuntime.Runtime.GetNSObject(System.IntPtr)" />) to create a new managed representation for a pointer to an unmanaged Objective-C object.
105+
/// Developers should not invoke this method directly, instead they should call <see cref="ObjCRuntime.Runtime.GetNSObject(System.IntPtr)" /> as it will prevent two instances of a managed object pointing to the same native object.
106+
/// </para>
107+
/// </remarks>
108+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
109+
[EditorBrowsable (EditorBrowsableState.Advanced)]
110+
protected internal NSCodingSubclass (global::ObjCRuntime.NativeHandle handle) : base (handle) {}
111+
112+
/// <summary>A constructor that initializes the object from the data stored in the unarchiver object.</summary>
113+
/// <param name="coder">The unarchiver object.</param>
114+
/// <remarks>
115+
/// <para>This constructor is provided to allow the class to be initialized from an unarchiver (for example, during NIB deserialization). This is part of the <see cref="Foundation.NSCoding" /> protocol.</para>
116+
/// <para>If developers want to create a subclass of this object and continue to support deserialization from an archive, they should implement a constructor with an identical signature: taking a single parameter of type <see cref="Foundation.NSCoder" /> and decorate it with the <c>[Export("initWithCoder:"]</c> attribute.</para>
117+
/// <para>The state of this object can also be serialized by using the <see cref="Foundation.INSCoding.EncodeTo" /> companion method.</para>
118+
/// </remarks>
119+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
120+
[DesignatedInitializer]
121+
[EditorBrowsable (EditorBrowsableState.Advanced)]
122+
[Export ("initWithCoder:")]
123+
public NSCodingSubclass (NSCoder coder) : base (NSObjectFlag.Empty)
124+
{
125+
UIKit.UIApplication.EnsureUIThread ();
126+
127+
if (IsDirectBinding) {
128+
InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSend_IntPtr (this.Handle, Selector.GetHandle ("initWithCoder:"), coder.Handle), "initWithCoder:");
129+
} else {
130+
InitializeHandle (global::ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_IntPtr (this.SuperHandle, Selector.GetHandle ("initWithCoder::"), coder.Handle), "initWithCoder:");
131+
}
132+
GC.KeepAlive (coder);
133+
}
134+
}

0 commit comments

Comments
 (0)