[generator] Do not generate `BindingImplOptions.Optimizable` when code snippets are provided (#12165)

unless the snippet-based attribute also set the `Optimizable` property.

IOW this is now required to _opt-in_ (to optimize) instead of default,
with no ~easy~ way to turn it off.

Re-enabled each of the `[Dispose]` cases after protecting the required
extra calls they make.

Fix https://github.com/xamarin/xamarin-macios/issues/12150

* Add test for [Dispose] and SnippetAttribute subclasses

* Opt-in all [*Snippet] to be optimizable

This is exactly what we have been shipping for years (no changes).

Unlike [Dispose] there is no change in the generated code or the
optimizations.

Future snippets should come with tests - which is _normal_ for any
manual code (which they are) added to the SDK
This commit is contained in:
Sebastien Pouliot 2021-07-27 09:36:22 -04:00 коммит произвёл GitHub
Родитель edc088ad7b
Коммит 1fa43189b5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 232 добавлений и 42 удалений

Просмотреть файл

@ -103,6 +103,8 @@ namespace CoreAnimation {
}
}
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
[Preserve (Conditional = true)]
void OnDispose ()
{
if (calayerdelegate != null) {

Просмотреть файл

@ -34,6 +34,8 @@ namespace UIKit {
// Called by the Dispose() method, because this can run from a finalizer, we need to
// (a) reference the handle, that we will release later, and (b) to remove the targets on the
// UI thread.
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
[Preserve (Conditional = true)]
void OnDispose ()
{
var copyOfRecognizers = recognizers;

Просмотреть файл

@ -15103,16 +15103,16 @@ namespace AppKit {
void WillRemoveSubview ([NullAllowed] NSView subview);
[Export ("removeFromSuperview")]
[PreSnippet ("var mySuper = Superview;")]
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}")]
[PreSnippet ("var mySuper = Superview;", Optimizable = true)]
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}", Optimizable = true)]
void RemoveFromSuperview ();
[Export ("replaceSubview:with:")][PostGet ("Subviews")]
void ReplaceSubviewWith (NSView oldView, NSView newView);
[Export ("removeFromSuperviewWithoutNeedingDisplay")]
[PreSnippet ("var mySuper = Superview;")]
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}")]
[PreSnippet ("var mySuper = Superview;", Optimizable = true)]
[PostSnippet ("if (mySuper != null) {\n\t#pragma warning disable 168\n\tvar flush = mySuper.Subviews;\n#pragma warning restore 168\n\t}", Optimizable = true)]
void RemoveFromSuperviewWithoutNeedingDisplay ();
[Export ("resizeSubviewsWithOldSize:")]
@ -19670,16 +19670,16 @@ namespace AppKit {
CGRect ContentRectFor (CGRect frameRect);
[Export ("init")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }", Optimizable = true)]
IntPtr Constructor ();
[DesignatedInitializer]
[Export ("initWithContentRect:styleMask:backing:defer:")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }", Optimizable = true)]
IntPtr Constructor (CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation);
[Export ("initWithContentRect:styleMask:backing:defer:screen:")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }")]
[PostSnippet ("if (!DisableReleasedWhenClosedInConstructor) { ReleasedWhenClosed = false; }", Optimizable = true)]
IntPtr Constructor (CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation, NSScreen screen);
[Export ("title")]

Просмотреть файл

@ -12412,7 +12412,7 @@ namespace AVFoundation {
IntPtr Constructor (AVQueuePlayer player, AVPlayerItem itemToLoop, CMTimeRange loopRange);
#if !XAMCORE_4_0 // This API got introduced in Xcode 8.0 binding but is not currently present nor in Xcode 8.3 or Xcode 9.0 needs research
[PostSnippet ("loopingEnabled = false;")]
[PostSnippet ("loopingEnabled = false;", Optimizable = true)]
#endif
[Export ("disableLooping")]
void DisableLooping ();

Просмотреть файл

@ -186,7 +186,7 @@ namespace CoreAnimation {
}
[BaseType (typeof (NSObject))]
[Dispose ("OnDispose ();")]
[Dispose ("OnDispose ();", Optimizable = true)]
interface CALayer : CAMediaTiming, NSSecureCoding {
[Export ("layer")][Static]
CALayer Create ();
@ -432,7 +432,7 @@ namespace CoreAnimation {
string Name { get; set; }
[Export ("delegate", ArgumentSemantic.Weak)][NullAllowed]
NSObject WeakDelegate { get; [PostSnippet (@"SetCALayerDelegate (value as CALayerDelegate);")] set; }
NSObject WeakDelegate { get; [PostSnippet (@"SetCALayerDelegate (value as CALayerDelegate);", Optimizable = true)] set; }
[Wrap ("WeakDelegate")]
[Protocolize]

Просмотреть файл

@ -473,7 +473,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)]
[Static]
[Export ("loadFromHTMLWithRequest:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (NSUrlRequest request, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -489,7 +489,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)]
[Static]
[Export ("loadFromHTMLWithFileURL:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (NSUrl fileUrl, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -505,7 +505,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)]
[Static]
[Export ("loadFromHTMLWithString:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (string @string, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -521,7 +521,7 @@ namespace Foundation
[Mac (10,15), iOS (13,0)]
[Static]
[Export ("loadFromHTMLWithData:options:completionHandler:")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory")]
[PreSnippet ("GC.KeepAlive (WebKit.WKContentMode.Recommended); // no-op to ensure WebKit.framework is loaded into memory", Optimizable = true)]
[Async (ResultTypeName = "NSLoadFromHtmlResult")]
[EditorBrowsable (EditorBrowsableState.Advanced)]
void LoadFromHtml (NSData data, NSDictionary options, NSAttributedStringCompletionHandler completionHandler);
@ -3634,11 +3634,11 @@ namespace Foundation
[BaseType (typeof (NSData))]
interface NSMutableData {
[Static, Export ("dataWithCapacity:")] [Autorelease]
[PreSnippet ("if (capacity < 0 || capacity > nint.MaxValue) throw new ArgumentOutOfRangeException ();")]
[PreSnippet ("if (capacity < 0 || capacity > nint.MaxValue) throw new ArgumentOutOfRangeException ();", Optimizable = true)]
NSMutableData FromCapacity (nint capacity);
[Static, Export ("dataWithLength:")] [Autorelease]
[PreSnippet ("if (length < 0 || length > nint.MaxValue) throw new ArgumentOutOfRangeException ();")]
[PreSnippet ("if (length < 0 || length > nint.MaxValue) throw new ArgumentOutOfRangeException ();", Optimizable = true)]
NSMutableData FromLength (nint length);
[Static, Export ("data")] [Autorelease]
@ -3648,7 +3648,7 @@ namespace Foundation
IntPtr MutableBytes { get; }
[Export ("initWithCapacity:")]
[PreSnippet ("if (capacity > (ulong) nint.MaxValue) throw new ArgumentOutOfRangeException ();")]
[PreSnippet ("if (capacity > (ulong) nint.MaxValue) throw new ArgumentOutOfRangeException ();", Optimizable = true)]
IntPtr Constructor (nuint capacity);
[Export ("appendData:")]
@ -5050,7 +5050,7 @@ namespace Foundation
}
[BaseType (typeof(NSObject))]
[Dispose ("if (disposing) { Invalidate (); } ")]
[Dispose ("if (disposing) { Invalidate (); } ", Optimizable = true)]
// init returns NIL
[DisableDefaultCtor]
interface NSTimer {
@ -5086,6 +5086,8 @@ namespace Foundation
[Export ("fireDate", ArgumentSemantic.Copy)]
NSDate FireDate { get; set; }
// Note: preserving this member allows us to re-enable the `Optimizable` binding flag
[Preserve (Conditional = true)]
[Export ("invalidate")]
void Invalidate ();
@ -5402,9 +5404,9 @@ namespace Foundation
// in turns, means that the Intents.framework is loaded into memory and this makes the
// selectors (getter and setter) work at runtime. Other selectors do not need it.
// reference: https://github.com/xamarin/xamarin-macios/issues/4894
[PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory")]
[PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory", Optimizable = true)]
get;
[PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory")]
[PreSnippet ("GC.KeepAlive (Intents.INCallCapabilityOptions.AudioCall); // no-op to ensure Intents.framework is loaded into memory", Optimizable = true)]
set;
}
@ -8481,11 +8483,11 @@ namespace Foundation
[Export ("initWithCapacity:")]
IntPtr Constructor (nint capacity);
[PreSnippet ("Check (index);")]
[PreSnippet ("Check (index);", Optimizable = true)]
[Export ("insertString:atIndex:")]
void Insert (NSString str, nint index);
[PreSnippet ("Check (range);")]
[PreSnippet ("Check (range);", Optimizable = true)]
[Export ("deleteCharactersInRange:")]
void DeleteCharacters (NSRange range);
@ -8495,7 +8497,7 @@ namespace Foundation
[Export ("setString:")]
void SetString (NSString str);
[PreSnippet ("Check (range);")]
[PreSnippet ("Check (range);", Optimizable = true)]
[Export ("replaceOccurrencesOfString:withString:options:range:")]
nuint ReplaceOcurrences (NSString target, NSString replacement, NSStringCompareOptions options, NSRange range);
@ -8840,12 +8842,12 @@ namespace Foundation
[Export ("methodForSelector:")]
IntPtr GetMethodForSelector (Selector sel);
[PreSnippet ("if (!(this is INSCopying)) throw new InvalidOperationException (\"Type does not conform to NSCopying\");")]
[PreSnippet ("if (!(this is INSCopying)) throw new InvalidOperationException (\"Type does not conform to NSCopying\");", Optimizable = true)]
[Export ("copy")]
[return: Release ()]
NSObject Copy ();
[PreSnippet ("if (!(this is INSMutableCopying)) throw new InvalidOperationException (\"Type does not conform to NSMutableCopying\");")]
[PreSnippet ("if (!(this is INSMutableCopying)) throw new InvalidOperationException (\"Type does not conform to NSMutableCopying\");", Optimizable = true)]
[Export ("mutableCopy")]
[return: Release ()]
NSObject MutableCopy ();
@ -10637,7 +10639,7 @@ namespace Foundation
NSNotificationCenter DefaultCenter { get; }
[Export ("addObserver:selector:name:object:")]
[PostSnippet ("AddObserverToList (observer, aName, anObject);")]
[PostSnippet ("AddObserverToList (observer, aName, anObject);", Optimizable = true)]
void AddObserver (NSObject observer, Selector aSelector, [NullAllowed] NSString aName, [NullAllowed] NSObject anObject);
[Export ("postNotification:")]
@ -10650,11 +10652,11 @@ namespace Foundation
void PostNotificationName (string aName, [NullAllowed] NSObject anObject, [NullAllowed] NSDictionary aUserInfo);
[Export ("removeObserver:")]
[PostSnippet ("RemoveObserversFromList (observer, null, null);")]
[PostSnippet ("RemoveObserversFromList (observer, null, null);", Optimizable = true)]
void RemoveObserver (NSObject observer);
[Export ("removeObserver:name:object:")]
[PostSnippet ("RemoveObserversFromList (observer, aName, anObject);")]
[PostSnippet ("RemoveObserversFromList (observer, aName, anObject);", Optimizable = true)]
void RemoveObserver (NSObject observer, [NullAllowed] string aName, [NullAllowed] NSObject anObject);
[Export ("addObserverForName:object:queue:usingBlock:")]

Просмотреть файл

@ -583,10 +583,13 @@ public class SnippetAttribute : Attribute {
Code = s;
}
public string Code { get; set; }
public bool Optimizable { get; set; }
}
//
// PreSnippet code is inserted after the parameters have been validated/marshalled
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
//
public class PreSnippetAttribute : SnippetAttribute {
public PreSnippetAttribute (string s) : base (s) {}
@ -594,6 +597,7 @@ public class PreSnippetAttribute : SnippetAttribute {
//
// PrologueSnippet code is inserted before any code is generated
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
//
public class PrologueSnippetAttribute : SnippetAttribute {
public PrologueSnippetAttribute (string s) : base (s) {}
@ -601,13 +605,15 @@ public class PrologueSnippetAttribute : SnippetAttribute {
//
// PostSnippet code is inserted before returning, before paramters are disposed/released
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
//
public class PostSnippetAttribute : SnippetAttribute {
public PostSnippetAttribute (string s) : base (s) {}
}
//
// Code to run from a generated Dispose method
// Code to run from a generated Dispose method, before any generated code is executed
// Adding this attribute will, by default, make the method non-optimizable by the SDK tools
//
[AttributeUsage(AttributeTargets.Interface, AllowMultiple=true)]
public class DisposeAttribute : SnippetAttribute {

Просмотреть файл

@ -4034,7 +4034,18 @@ public partial class Generator : IMemberGatherer {
print (l);
}
}
bool IsOptimizable (MemberInfo method)
{
var optimizable = true;
var snippets = AttributeManager.GetCustomAttributes<SnippetAttribute> (method);
if (snippets.Length > 0) {
foreach (SnippetAttribute snippet in snippets)
optimizable &= snippet.Optimizable;
}
return optimizable;
}
[Flags]
public enum BodyOption {
None = 0x0,
@ -4956,7 +4967,7 @@ public partial class Generator : IMemberGatherer {
}
}
print_generated_code ();
print_generated_code (optimizable: IsOptimizable (pi));
PrintPropertyAttributes (pi, minfo.type);
PrintAttributes (pi, preserve:true, advice:true, bindAs:true);
@ -5511,7 +5522,7 @@ public partial class Generator : IMemberGatherer {
var mod = minfo.GetVisibility ();
var is_abstract = minfo.is_abstract;
print_generated_code ();
print_generated_code (optimizable: IsOptimizable (minfo.mi));
print ("{0} {1}{2}{3}",
mod,
minfo.GetModifiers (),
@ -7423,15 +7434,19 @@ public partial class Generator : IMemberGatherer {
// Do we need a dispose method?
//
if (!is_static_class){
var disposeAttr = AttributeManager.GetCustomAttributes<DisposeAttribute> (type);
if (disposeAttr.Length > 0 || instance_fields_to_clear_on_dispose.Count > 0){
print_generated_code (optimizable: disposeAttr.Length == 0);
var attrs = AttributeManager.GetCustomAttributes<DisposeAttribute> (type);
// historical note: unlike many attributes our `DisposeAttribute` has `AllowMultiple=true`
var has_dispose_attributes = attrs.Length > 0;
if (has_dispose_attributes || (instance_fields_to_clear_on_dispose.Count > 0)) {
// if there'a any [Dispose] attribute then they all must opt-in in order for the generated Dispose method to be optimizable
bool optimizable = !has_dispose_attributes || IsOptimizable (type);
print_generated_code (optimizable: optimizable);
print ("protected override void Dispose (bool disposing)");
print ("{");
indent++;
if (disposeAttr.Length > 0){
var snippet = disposeAttr [0];
Inject (snippet);
if (has_dispose_attributes) {
foreach (var da in attrs)
Inject (da);
}
print ("base.Dispose (disposing);");

Просмотреть файл

@ -59,7 +59,7 @@ namespace SafariServices {
[Static, Export ("supportsURL:")]
// Apple says it's __nonnull so let's be safe and maintain compatibility with our current behaviour
[PreSnippet ("if (url is null) return false;")]
[PreSnippet ("if (url is null) return false;", Optimizable = true)]
bool SupportsUrl ([NullAllowed] NSUrl url);
[Export ("addReadingListItemWithURL:title:previewText:error:")]

Просмотреть файл

@ -2035,7 +2035,7 @@ namespace UIKit {
void OpenUrl (NSUrl url, UIApplicationOpenUrlOptions options, [NullAllowed] Action<bool> completion);
[Export ("canOpenURL:")]
[PreSnippet ("if (url is null) return false;")] // null not really allowed (but it's a behaviour change with known bug reports)
[PreSnippet ("if (url is null) return false;", Optimizable = true)] // null not really allowed (but it's a behaviour change with known bug reports)
bool CanOpenUrl ([NullAllowed] NSUrl url);
[Export ("sendEvent:")]
@ -5562,7 +5562,7 @@ namespace UIKit {
#if !WATCH
[BaseType (typeof(NSObject), Delegates=new string [] {"WeakDelegate"}, Events=new Type[] {typeof (UIGestureRecognizerDelegate)})]
[Dispose ("OnDispose ();")]
[Dispose ("OnDispose ();", Optimizable = true)]
interface UIGestureRecognizer {
[DesignatedInitializer]
[Export ("initWithTarget:action:")]
@ -13243,7 +13243,7 @@ namespace UIKit {
[Export ("typingAttributes", ArgumentSemantic.Copy)]
NSDictionary TypingAttributes {
// this avoids a crash (see unit tests) and behave like UITextField does (return null)
[PreSnippet ("if (SelectedRange.Length == 0) return null;")]
[PreSnippet ("if (SelectedRange.Length == 0) return null;", Optimizable = true)]
get;
set;
}

Просмотреть файл

@ -645,6 +645,85 @@ namespace GeneratorTests
[Test]
public void GHIssue9065_Sealed () => BuildFile (Profile.iOS, nowarnings: true, "ghissue9065.cs");
// looking for [BindingImpl (BindingImplOptions.Optimizable)]
bool IsOptimizable (MethodDefinition method)
{
const int Optimizable = 0x2; // BindingImplOptions flag
if (!method.HasCustomAttributes)
return false;
foreach (var ca in method.CustomAttributes) {
if (ca.AttributeType.Name != "BindingImplAttribute")
continue;
foreach (var a in ca.ConstructorArguments)
return (((int) a.Value & Optimizable) == Optimizable);
}
return false;
}
[Test]
public void DisposeAttributeOptimizable ()
{
var bgen = BuildFile (Profile.iOS, "tests/dispose-attribute.cs");
// processing custom attributes (like its properties) will call Resolve so we must be able to find the platform assembly to run this test
var platform_dll = Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/Xamarin.iOS.dll");
var resolver = bgen.ApiAssembly.MainModule.AssemblyResolver as BaseAssemblyResolver;
resolver.AddSearchDirectory (Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/"));
// [Dispose] is, by default, not optimizable
var with_dispose = bgen.ApiAssembly.MainModule.GetType ("NS", "WithDispose").Methods.First ((v) => v.Name == "Dispose");
Assert.NotNull (with_dispose, "WithDispose");
Assert.That (IsOptimizable (with_dispose), Is.False, "WithDispose/Optimizable");
// [Dispose] can opt-in being optimizable
var with_dispose_optin = bgen.ApiAssembly.MainModule.GetType ("NS", "WithDisposeOptInOptimizable").Methods.First ((v) => v.Name == "Dispose");
Assert.NotNull (with_dispose_optin, "WithDisposeOptInOptimizable");
Assert.That (IsOptimizable (with_dispose_optin), Is.True, "WithDisposeOptInOptimizable/Optimizable");
// Without a [Dispose] attribute the generated method is optimizable
var without_dispose = bgen.ApiAssembly.MainModule.GetType ("NS", "WithoutDispose").Methods.First ((v) => v.Name == "Dispose");
Assert.NotNull (without_dispose, "WitoutDispose");
Assert.That (IsOptimizable (without_dispose), Is.True, "WitoutDispose/Optimizable");
}
[Test]
public void SnippetAttributesOptimizable ()
{
var bgen = BuildFile (Profile.iOS, "tests/snippet-attributes.cs");
// processing custom attributes (like its properties) will call Resolve so we must be able to find the platform assembly to run this test
var platform_dll = Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/Xamarin.iOS.dll");
var resolver = bgen.ApiAssembly.MainModule.AssemblyResolver as BaseAssemblyResolver;
resolver.AddSearchDirectory (Path.Combine (Configuration.SdkRootXI, "lib/mono/Xamarin.iOS/"));
// [SnippetAttribute] subclasses are, by default, not optimizable
var not_opt = bgen.ApiAssembly.MainModule.GetType ("NS", "NotOptimizable");
Assert.NotNull (not_opt, "NotOptimizable");
var pre_not_opt = not_opt.Methods.First ((v) => v.Name == "Pre");
Assert.That (IsOptimizable (pre_not_opt), Is.False, "NotOptimizable/Pre");
var prologue_not_opt = not_opt.Methods.First ((v) => v.Name == "Prologue");
Assert.That (IsOptimizable (prologue_not_opt), Is.False, "NotOptimizable/Prologue");
var post_not_opt = not_opt.Methods.First ((v) => v.Name == "Post");
Assert.That (IsOptimizable (post_not_opt), Is.False, "NotOptimizable/Post");
// [SnippetAttribute] subclasses can opt-in being optimizable
var optin_opt = bgen.ApiAssembly.MainModule.GetType ("NS", "OptInOptimizable");
Assert.NotNull (optin_opt, "OptInOptimizable");
var pre_optin_opt = optin_opt.Methods.First ((v) => v.Name == "Pre");
Assert.That (IsOptimizable (pre_optin_opt), Is.True, "OptInOptimizable/Pre");
var prologue_optin_opt = optin_opt.Methods.First ((v) => v.Name == "Prologue");
Assert.That (IsOptimizable (prologue_optin_opt), Is.True, "OptInOptimizable/Prologue");
var post_optin_opt = optin_opt.Methods.First ((v) => v.Name == "Post");
Assert.That (IsOptimizable (post_optin_opt), Is.True, "OptInOptimizable/Post");
// Without a [SnippetAttribute] subclass attribute the generated method is optimizable
var nothing = bgen.ApiAssembly.MainModule.GetType ("NS", "NoSnippet").Methods.First ((v) => v.Name == "Nothing");
Assert.NotNull (nothing, "NoSnippet");
Assert.That (IsOptimizable (nothing), Is.True, "Nothing/Optimizable");
}
BGenTool BuildFile (Profile profile, params string [] filenames)
{
return BuildFile (profile, true, false, filenames);

Просмотреть файл

@ -0,0 +1,36 @@
using System;
using Foundation;
using ObjCRuntime;
namespace NS {
// injecting custom code makes the Dispose method not-optimizable by default
[Dispose ("Console.WriteLine (\"Disposing!\");")]
[BaseType (typeof (NSObject))]
interface WithDispose {
[Export ("delegate", ArgumentSemantic.Weak)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
// but we can opt-in to make it optimizable
[Dispose ("// just a comment, that's safe to optimize, if not very useful otherwise", Optimizable = true)]
[BaseType (typeof (NSObject))]
interface WithDisposeOptInOptimizable {
[Export ("delegate", ArgumentSemantic.Weak)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
// if nothing is injected then we know we generate code that our tools can optimize
[BaseType (typeof (NSObject))]
interface WithoutDispose {
// this ensure we have a Dispose method generated for the type
[Export ("delegate", ArgumentSemantic.Weak)]
[NullAllowed]
NSObject WeakDelegate { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,48 @@
using System;
using Foundation;
using ObjCRuntime;
namespace NS {
// injecting custom code makes the method method not-optimizable by default
[BaseType (typeof (NSObject))]
interface NotOptimizable {
[PreSnippet ("Console.WriteLine (\"Pre!\");")]
[Export ("pre")]
void Pre ();
[PrologueSnippet ("Console.WriteLine (\"Prologue!\");")]
[Export ("prologue")]
void Prologue ();
[PostSnippet ("Console.WriteLine (\"Post!\");")]
[Export ("post")]
void Post ();
}
// but we can opt-in to make it optimizable
[BaseType (typeof (NSObject))]
interface OptInOptimizable {
[PreSnippet ("Console.WriteLine (\"Pre!\");", Optimizable = true)]
[Export ("pre")]
void Pre ();
[PrologueSnippet ("Console.WriteLine (\"Prologue!\");", Optimizable = true)]
[Export ("prologue")]
void Prologue ();
[PostSnippet ("Console.WriteLine (\"Post!\");", Optimizable = true)]
[Export ("post")]
void Post ();
}
// if nothing is injected then we know we generate code that our tools can optimize
[BaseType (typeof (NSObject))]
interface NoSnippet {
[Export ("nothing")]
void Nothing ();
}
}