diff --git a/src/AudioToolbox/AudioToolbox.cs b/src/AudioToolbox/AudioToolbox.cs index 2dd9149cd005..be3c0679dcb4 100644 --- a/src/AudioToolbox/AudioToolbox.cs +++ b/src/AudioToolbox/AudioToolbox.cs @@ -129,21 +129,14 @@ public static class SoundBank { if (url is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (url)); - InstrumentInfo []? result = null; IntPtr array = IntPtr.Zero; - OSStatus error; unsafe { - error = CopyInstrumentInfoFromSoundBank (url.Handle, &array); + var error = CopyInstrumentInfoFromSoundBank (url.Handle, &array); GC.KeepAlive (url); + if (error != 0) + return null; } - if (array != IntPtr.Zero) { - var dicts = NSArray.ArrayFromHandle (array); - result = new InstrumentInfo [dicts.Length]; - for (int i = 0; i < dicts.Length; i++) - result [i] = new InstrumentInfo (dicts [i]); - CFObject.CFRelease (array); - } - return (error != 0) ? null : result; + return NSArray.DictionaryArrayFromHandleDropNullElements (array, dict => new InstrumentInfo (dict), releaseHandle: true); } } } diff --git a/src/AudioUnit/AudioComponent.cs b/src/AudioUnit/AudioComponent.cs index 741ccb1eda9b..17021824a9c5 100644 --- a/src/AudioUnit/AudioComponent.cs +++ b/src/AudioUnit/AudioComponent.cs @@ -671,7 +671,7 @@ public AudioComponentInfo []? ComponentList { get { using var nameHandle = new TransientCFString (Name); var cHandle = AudioUnitExtensionCopyComponentList (nameHandle); - return NSArray.ArrayFromHandle (cHandle, h => new AudioComponentInfo (Runtime.GetNSObject (h)!), releaseHandle: true); + return NSArray.DictionaryArrayFromHandleDropNullElements (cHandle, h => new AudioComponentInfo (h), releaseHandle: true); } set { using var nameHandle = new TransientCFString (Name); diff --git a/src/CoreFoundation/CFProxySupport.cs b/src/CoreFoundation/CFProxySupport.cs index d52b9ffefbd1..02351a914125 100644 --- a/src/CoreFoundation/CFProxySupport.cs +++ b/src/CoreFoundation/CFProxySupport.cs @@ -263,7 +263,7 @@ public static partial class CFNetwork { /* CFStringRef __nonnull */ IntPtr proxyAutoConfigurationScript, /* CFURLRef __nonnull */ IntPtr targetURL, /* CFErrorRef __nullable * __nullable */ IntPtr* error); - static NSArray? CopyProxiesForAutoConfigurationScript (NSString proxyAutoConfigurationScript, NSUrl targetURL) + static IntPtr CopyProxiesForAutoConfigurationScript (NSString proxyAutoConfigurationScript, NSUrl targetURL) { IntPtr err; IntPtr native; @@ -272,7 +272,7 @@ public static partial class CFNetwork { GC.KeepAlive (proxyAutoConfigurationScript); GC.KeepAlive (targetURL); } - return native == IntPtr.Zero ? null : new NSArray (native); + return native; } /// JavaScript source to be executed to obtain a list of proxies to use. @@ -288,21 +288,9 @@ public static partial class CFNetwork { if (targetURL is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (targetURL)); - using (var array = CopyProxiesForAutoConfigurationScript (proxyAutoConfigurationScript, targetURL)) { - if (array is null) - return null; - - NSDictionary [] dictionaries = NSArray.ArrayFromHandle (array.Handle); - GC.KeepAlive (array); - if (dictionaries is null) - return null; - - CFProxy [] proxies = new CFProxy [dictionaries.Length]; - for (int i = 0; i < dictionaries.Length; i++) - proxies [i] = new CFProxy (dictionaries [i]); - - return proxies; - } + var array = CopyProxiesForAutoConfigurationScript (proxyAutoConfigurationScript, targetURL); + var proxies = NSArray.DictionaryArrayFromHandleDropNullElements (array, (dict) => new CFProxy (dict), releaseHandle: true); + return proxies; } /// @@ -325,12 +313,12 @@ public static partial class CFNetwork { /* CFURLRef __nonnull */ IntPtr url, /* CFDictionaryRef __nonnull */ IntPtr proxySettings); - static NSArray? CopyProxiesForURL (NSUrl url, NSDictionary proxySettings) + static IntPtr CopyProxiesForURL (NSUrl url, NSDictionary proxySettings) { IntPtr native = CFNetworkCopyProxiesForURL (url.Handle, proxySettings.Handle); GC.KeepAlive (url); GC.KeepAlive (proxySettings); - return native == IntPtr.Zero ? null : new NSArray (native); + return native; } /// The target URL to connect to. @@ -349,21 +337,9 @@ public static partial class CFNetwork { if (proxySettings is null) return null; - using (NSArray? array = CopyProxiesForURL (url, proxySettings.Dictionary)) { - if (array is null) - return null; - - NSDictionary [] dictionaries = NSArray.ArrayFromHandle (array.Handle); - GC.KeepAlive (array); - if (dictionaries is null) - return null; - - CFProxy [] proxies = new CFProxy [dictionaries.Length]; - for (int i = 0; i < dictionaries.Length; i++) - proxies [i] = new CFProxy (dictionaries [i]); - - return proxies; - } + var array = CopyProxiesForURL (url, proxySettings.Dictionary); + var proxies = NSArray.DictionaryArrayFromHandleDropNullElements (array, (dict) => new CFProxy (dict), releaseHandle: true); + return proxies; } /// The target Uri to connect to. diff --git a/src/CoreText/CTFont.cs b/src/CoreText/CTFont.cs index 9586fbc8a0f3..8d56556049c2 100644 --- a/src/CoreText/CTFont.cs +++ b/src/CoreText/CTFont.cs @@ -3475,10 +3475,7 @@ public nint GetLigatureCaretPositions (CGGlyph glyph, nfloat [] positions) public CTFontVariationAxes [] GetVariationAxes () { var cfArrayRef = CTFontCopyVariationAxes (Handle); - if (cfArrayRef == IntPtr.Zero) - return Array.Empty (); - return NSArray.ArrayFromHandle (cfArrayRef, - d => new CTFontVariationAxes (Runtime.GetNSObject (d)!), true); + return NSArray.NonNullDictionaryArrayFromHandleDropNullElements (cfArrayRef, d => new CTFontVariationAxes (d), true); } [DllImport (Constants.CoreTextLibrary)] @@ -3507,10 +3504,8 @@ public CTFontVariationAxes [] GetVariationAxes () public CTFontFeatures [] GetFeatures () { var cfArrayRef = CTFontCopyFeatures (Handle); - if (cfArrayRef == IntPtr.Zero) - return Array.Empty (); - return NSArray.ArrayFromHandle (cfArrayRef, - d => new CTFontFeatures (Runtime.GetNSObject (d)!), true); + return NSArray.NonNullDictionaryArrayFromHandleDropNullElements (cfArrayRef, + d => new CTFontFeatures (d), true); } [DllImport (Constants.CoreTextLibrary)] @@ -3523,10 +3518,8 @@ public CTFontFeatures [] GetFeatures () public CTFontFeatureSettings [] GetFeatureSettings () { var cfArrayRef = CTFontCopyFeatureSettings (Handle); - if (cfArrayRef == IntPtr.Zero) - return Array.Empty (); - return NSArray.ArrayFromHandle (cfArrayRef, - d => new CTFontFeatureSettings (Runtime.GetNSObject (d)!), true); + return NSArray.NonNullDictionaryArrayFromHandleDropNullElements (cfArrayRef, + d => new CTFontFeatureSettings (d), true); } #endregion diff --git a/src/Foundation/NSArray.cs b/src/Foundation/NSArray.cs index c39fc9fe72a4..26815d1a9618 100644 --- a/src/Foundation/NSArray.cs +++ b/src/Foundation/NSArray.cs @@ -623,6 +623,64 @@ public static T [] ArrayFromHandleFunc (NativeHandle handle, FuncCreates a managed array from a pointer to a native NSArray of NSDictionary objects, dropping null and NSNull elements. + /// The type of objects to create from the dictionaries. + /// The pointer to the native NSArray instance containing NSDictionary objects. + /// A factory function that creates an instance of type T from an NSDictionary. + /// Whether the native NSArray instance should be released before returning or not. Defaults to false. + /// A C# array with the values, or null if the handle is zero. Null and NSNull elements are excluded from the result. + /// + /// This method converts a native NSArray of NSDictionary objects into a managed array. Any null or NSNull elements in the source array are skipped, and the resulting array is resized accordingly. + /// +#nullable enable + internal static T []? DictionaryArrayFromHandleDropNullElements (NativeHandle handle, Func createObjectFromDictionary, bool releaseHandle = false) + { + if (handle == NativeHandle.Zero) + return null; + + try { + var count = GetCount (handle); + var ret = new T [count]; + nuint nextIndex = 0; + + for (nuint i = 0; i < count; i++) { + var val = GetAtIndex (handle, i); + if (val == IntPtr.Zero || val == NSNull.NullHandle) + continue; + var dict = Runtime.GetNSObject (val); + if (dict is null) + continue; + ret [nextIndex++] = createObjectFromDictionary (dict); + } + + if (nextIndex != count) + Array.Resize (ref ret, (int) nextIndex); + + return ret; + } finally { + if (releaseHandle) + NSObject.DangerousRelease (handle); + } + } + + /// Creates a managed array from a pointer to a native NSArray of NSDictionary objects, dropping null and NSNull elements. Always returns a non-null array. + /// The type of objects to create from the dictionaries. + /// The pointer to the native NSArray instance containing NSDictionary objects. + /// A factory function that creates an instance of type T from an NSDictionary. + /// Whether the native NSArray instance should be released before returning or not. Defaults to false. + /// A C# array with the values. Returns an empty array if the handle is zero. Null and NSNull elements are excluded from the result. + /// + /// This method is a wrapper around that guarantees a non-null return value. If the handle is zero or null, an empty array is returned instead of null. + /// + internal static T [] NonNullDictionaryArrayFromHandleDropNullElements (NativeHandle handle, Func createObjectFromDictionary, bool releaseHandle = false) + { + var rv = DictionaryArrayFromHandleDropNullElements (handle, createObjectFromDictionary, releaseHandle); + if (rv is null) + return Array.Empty (); + return rv; + } +#nullable disable + /// Parameter type, determines the kind of array returned. /// Pointer (handle) to the unmanaged object. /// Method that can create objects of type T from a given IntPtr. diff --git a/src/VideoToolbox/VTVideoEncoder.cs b/src/VideoToolbox/VTVideoEncoder.cs index fcd701552ac4..45ebaf643191 100644 --- a/src/VideoToolbox/VTVideoEncoder.cs +++ b/src/VideoToolbox/VTVideoEncoder.cs @@ -35,13 +35,7 @@ public class VTVideoEncoder { return null; } - var dicts = NSArray.ArrayFromHandle (array); - var ret = new VTVideoEncoder [dicts.Length]; - int i = 0; - foreach (var dict in dicts) - ret [i++] = new VTVideoEncoder (dict); - CFObject.CFRelease (array); - return ret; + return NSArray.DictionaryArrayFromHandleDropNullElements (array, dict => new VTVideoEncoder (dict), releaseHandle: true); } /// To be added. diff --git a/tests/monotouch-test/CoreText/FontTest.cs b/tests/monotouch-test/CoreText/FontTest.cs index fc00ed2dd3b0..3d90d0211117 100644 --- a/tests/monotouch-test/CoreText/FontTest.cs +++ b/tests/monotouch-test/CoreText/FontTest.cs @@ -163,5 +163,16 @@ class AdaptiveImageProvider : NSObject, ICTAdaptiveImageProviding { return null; } } + + [Test] + public void GetVariationAxes () + { + using (var font = new CTFont ("HoeflerText-Regular", 10)) { + var axes = font.GetVariationAxes (); + Assert.IsNotNull (axes, "axes"); + // HoeflerText-Regular has no variation axes, so we expect an empty array + Assert.That (axes.Length, Is.EqualTo (0), "Length"); + } + } } }