diff --git a/src/Accessibility/AXHearingUtilities.cs b/src/Accessibility/AXHearingUtilities.cs index e8f061a495ac..0bbb8fb63d45 100644 --- a/src/Accessibility/AXHearingUtilities.cs +++ b/src/Accessibility/AXHearingUtilities.cs @@ -38,7 +38,7 @@ public static AXHearingDeviceEar GetMFiHearingDeviceStreamingEar () public static NSUuid [] GetMFiHearingDevicePairedUuids () { - return NSArray.ArrayFromHandle (AXMFiHearingDevicePairedUUIDs ()); + return NSArray.NonNullArrayFromHandleDropNullElements (AXMFiHearingDevicePairedUUIDs ()); } } } diff --git a/src/AddressBook/ABAddressBook.cs b/src/AddressBook/ABAddressBook.cs index 5fde5ef1b33a..1cad31d1891e 100644 --- a/src/AddressBook/ABAddressBook.cs +++ b/src/AddressBook/ABAddressBook.cs @@ -422,7 +422,7 @@ public nint PeopleCount { public ABPerson [] GetPeople () { var cfArrayRef = ABAddressBookCopyArrayOfAllPeople (GetCheckedHandle ()); - return NSArray.ArrayFromHandle (cfArrayRef, h => new ABPerson (h, this), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, h => new ABPerson (h, this), releaseHandle: true); } [DllImport (Constants.AddressBookLibrary)] @@ -438,7 +438,7 @@ public ABPerson [] GetPeople (ABRecord source) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (source)); var cfArrayRef = ABAddressBookCopyArrayOfAllPeopleInSource (GetCheckedHandle (), source.Handle); GC.KeepAlive (source); - return NSArray.ArrayFromHandle (cfArrayRef, l => new ABPerson (l, this), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, l => new ABPerson (l, this), releaseHandle: true); } [DllImport (Constants.AddressBookLibrary)] @@ -455,7 +455,7 @@ public ABPerson [] GetPeople (ABRecord source, ABPersonSortBy sortOrdering) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (source)); var cfArrayRef = ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering (GetCheckedHandle (), source.Handle, sortOrdering); GC.KeepAlive (source); - return NSArray.ArrayFromHandle (cfArrayRef, l => new ABPerson (l, this), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, l => new ABPerson (l, this), releaseHandle: true); } [DllImport (Constants.AddressBookLibrary)] @@ -490,7 +490,7 @@ public nint GroupCount { public ABGroup [] GetGroups () { var cfArrayRef = ABAddressBookCopyArrayOfAllGroups (GetCheckedHandle ()); - return NSArray.ArrayFromHandle (cfArrayRef, h => new ABGroup (h, this), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, h => new ABGroup (h, this), releaseHandle: true); } [DllImport (Constants.AddressBookLibrary)] @@ -507,7 +507,7 @@ public ABGroup [] GetGroups (ABRecord source) var cfArrayRef = ABAddressBookCopyArrayOfAllGroupsInSource (GetCheckedHandle (), source.Handle); GC.KeepAlive (source); - return NSArray.ArrayFromHandle (cfArrayRef, l => new ABGroup (l, this), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, l => new ABGroup (l, this), releaseHandle: true); } [DllImport (Constants.AddressBookLibrary)] @@ -720,7 +720,7 @@ public ABPerson [] GetPeopleWithName (string name) var nameHandle = CFString.CreateNative (name); try { var cfArrayRef = ABAddressBookCopyPeopleWithName (Handle, nameHandle); - return NSArray.ArrayFromHandle (cfArrayRef, h => new ABPerson (h, this), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, h => new ABPerson (h, this), releaseHandle: true); } finally { CFString.ReleaseNative (nameHandle); } @@ -739,7 +739,7 @@ public ABPerson [] GetPeopleWithName (string name) public ABSource []? GetAllSources () { var cfArrayRef = ABAddressBookCopyArrayOfAllSources (GetCheckedHandle ()); - return NSArray.ArrayFromHandle (cfArrayRef, h => new ABSource (h, this), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, h => new ABSource (h, this), releaseHandle: true); } [DllImport (Constants.AddressBookLibrary)] diff --git a/src/AddressBook/ABGroup.cs b/src/AddressBook/ABGroup.cs index 67df40ed2c08..e42cdd9bc422 100644 --- a/src/AddressBook/ABGroup.cs +++ b/src/AddressBook/ABGroup.cs @@ -219,11 +219,8 @@ IEnumerator IEnumerable.GetEnumerator () public IEnumerator GetEnumerator () { var cfArrayRef = ABGroupCopyArrayOfAllMembers (Handle); - IEnumerable? e = null; - if (cfArrayRef == IntPtr.Zero) - e = new ABRecord [0]; - else - e = NSArray.ArrayFromHandle (cfArrayRef, h => ABRecord.FromHandle (h, AddressBook), releaseHandle: true); + IEnumerable e; + e = NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, h => ABRecord.FromHandle (h, AddressBook), releaseHandle: true); return e.GetEnumerator (); } @@ -248,9 +245,7 @@ public IEnumerator GetEnumerator () public ABRecord [] GetMembers (ABPersonSortBy sortOrdering) { var cfArrayRef = ABGroupCopyArrayOfAllMembersWithSortOrdering (Handle, sortOrdering); - if (cfArrayRef == IntPtr.Zero) - return new ABRecord [0]; - return NSArray.ArrayFromHandle (cfArrayRef, h => ABRecord.FromHandle (h, AddressBook), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, h => ABRecord.FromHandle (h, AddressBook), releaseHandle: true); } [DllImport (Constants.AddressBookLibrary)] diff --git a/src/AddressBook/ABMultiValue.cs b/src/AddressBook/ABMultiValue.cs index 0bb23505893c..d65b841ad8e4 100644 --- a/src/AddressBook/ABMultiValue.cs +++ b/src/AddressBook/ABMultiValue.cs @@ -319,8 +319,7 @@ public ABPropertyType PropertyType { /// public T [] GetValues () { - return NSArray.ArrayFromHandle (ABMultiValue.CopyArrayOfAllValues (Handle), toManaged, releaseHandle: true) - ?? Array.Empty (); + return NSArray.NonNullArrayFromHandleDropNullElements (ABMultiValue.CopyArrayOfAllValues (Handle), toManaged, releaseHandle: true); } /// diff --git a/src/AppKit/NSAccessibility.cs b/src/AppKit/NSAccessibility.cs index 94b363ceca4e..aeda40297a04 100644 --- a/src/AppKit/NSAccessibility.cs +++ b/src/AppKit/NSAccessibility.cs @@ -198,7 +198,7 @@ public static void PostNotification (NSObject element, NSString notification) var handle = NSAccessibilityUnignoredChildren (originalChildren.Handle); GC.KeepAlive (originalChildren); - return NSArray.ArrayFromHandle (handle); + return NSArray.ArrayFromHandleDropNullElements (handle); } [DllImport (Constants.AppKitLibrary)] @@ -215,7 +215,7 @@ public static void PostNotification (NSObject element, NSString notification) var handle = NSAccessibilityUnignoredChildrenForOnlyChild (originalChild.Handle); GC.KeepAlive (originalChild); - return NSArray.ArrayFromHandle (handle); + return NSArray.ArrayFromHandleDropNullElements (handle); } [DllImport (Constants.AppKitLibrary)] diff --git a/src/AuthenticationServices/PublicPrivateKeyAuthentication.cs b/src/AuthenticationServices/PublicPrivateKeyAuthentication.cs index 9d9dc8b2907e..485180207a75 100644 --- a/src/AuthenticationServices/PublicPrivateKeyAuthentication.cs +++ b/src/AuthenticationServices/PublicPrivateKeyAuthentication.cs @@ -10,7 +10,6 @@ #if !TVOS using CoreGraphics; -using System.Linq; #nullable enable @@ -25,14 +24,14 @@ public static class PublicPrivateKeyAuthentication { public static ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransport []? GetAllSupportedPublicKeyCredentialDescriptorTransports () { - NSString []? nsStringArray = NSArray.ArrayFromHandle (ASAuthorizationAllSupportedPublicKeyCredentialDescriptorTransports ()); + var nsStringArray = NSArray.StringArrayFromHandle (ASAuthorizationAllSupportedPublicKeyCredentialDescriptorTransports ()); if (nsStringArray is null) return null; - ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransport [] asArray = new ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransport [nsStringArray.Count ()]; - for (var i = 0; i < nsStringArray.Count (); i++) { - switch (nsStringArray [i].Description) { + var asArray = new ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransport [nsStringArray.Length]; + for (var i = 0; i < nsStringArray.Length; i++) { + switch (nsStringArray [i]) { case "usb": asArray [i] = ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransport.Usb; break; diff --git a/src/CoreAnimation/CADefs.cs b/src/CoreAnimation/CADefs.cs index 1899ea53ec3d..6405f5cefc2d 100644 --- a/src/CoreAnimation/CADefs.cs +++ b/src/CoreAnimation/CADefs.cs @@ -52,24 +52,14 @@ CGColor CreateColor (NativeHandle p) /// An array of colors defining the gradient. These values can be animated. /// To be added. /// To be added. - public CGColor [] Colors { + public CGColor []? Colors { get { - return NSArray.ArrayFromHandle (_Colors, CreateColor); + return NSArray.ArrayFromHandleDropNullElements (_Colors, CreateColor); } set { - if (value is null) { - _Colors = IntPtr.Zero; - return; - } - - var ptrs = new NativeHandle [value.Length]; - for (int i = 0; i < ptrs.Length; i++) - ptrs [i] = value [i].Handle; - - using (NSArray array = NSArray.FromIntPtrs (ptrs)) { - _Colors = array.Handle; - } + using var array = NSArray.FromIntPtrs (value, NativeObjectExtensions.GetHandle); + _Colors = array.GetHandle (); } } } diff --git a/src/CoreGraphics/CGPath.cs b/src/CoreGraphics/CGPath.cs index 2b8844470b15..19ee8c66c540 100644 --- a/src/CoreGraphics/CGPath.cs +++ b/src/CoreGraphics/CGPath.cs @@ -841,9 +841,7 @@ public void Apply (ApplierFunction func) public CGPath [] GetSeparateComponents (bool evenOddFillRule) { var cfArrayRef = CGPathCreateSeparateComponents (Handle, evenOddFillRule.AsByte ()); - if (cfArrayRef == IntPtr.Zero) - return Array.Empty (); - return NSArray.ArrayFromHandle (cfArrayRef); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef); } [SupportedOSPlatform ("ios16.0")] diff --git a/src/CoreML/MLModel.cs b/src/CoreML/MLModel.cs index 539aef822cf0..a995a4321241 100644 --- a/src/CoreML/MLModel.cs +++ b/src/CoreML/MLModel.cs @@ -18,9 +18,7 @@ public partial class MLModel { public static IMLComputeDeviceProtocol [] AllComputeDevices { get { var ptr = MLAllComputeDevices (); - if (ptr == IntPtr.Zero) - return Array.Empty (); - return NSArray.ArrayFromHandle (ptr); + return NSArray.NonNullArrayFromHandleDropNullElements (ptr); } } } diff --git a/src/CoreML/MLMultiArray.cs b/src/CoreML/MLMultiArray.cs index a2c747cff417..b4c85111cfc0 100644 --- a/src/CoreML/MLMultiArray.cs +++ b/src/CoreML/MLMultiArray.cs @@ -22,7 +22,7 @@ static NSNumber [] ConvertArray (nint [] value) // NSArray => nint[] internal static nint [] ConvertArray (IntPtr handle) { - return NSArray.ArrayFromHandle (handle, (v) => (nint) Messaging.IntPtr_objc_msgSend (v, Selector.GetHandle ("integerValue"))); + return NSArray.NonNullArrayFromHandleDropNullElements (handle, (v) => (nint) Messaging.IntPtr_objc_msgSend (v, Selector.GetHandle ("integerValue"))); } /// To be added. diff --git a/src/CoreServices/LaunchServices.cs b/src/CoreServices/LaunchServices.cs index 33a827dee339..2eac64c70bd7 100644 --- a/src/CoreServices/LaunchServices.cs +++ b/src/CoreServices/LaunchServices.cs @@ -189,7 +189,7 @@ public static NSUrl [] GetApplicationUrlsForUrl (NSUrl url, LSRoles roles = LSRo if (url is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (url)); - var result = NSArray.ArrayFromHandle ( + var result = NSArray.NonNullArrayFromHandleDropNullElements ( LSCopyApplicationURLsForURL (url.Handle, roles), releaseHandle: true ); @@ -260,7 +260,7 @@ public static NSUrl [] GetApplicationUrlsForBundleIdentifier (string bundleIdent var bundleIdentifierHandle = CFString.CreateNative (bundleIdentifier); try { - return NSArray.ArrayFromHandle ( + return NSArray.NonNullArrayFromHandleDropNullElements ( LSCopyApplicationURLsForBundleIdentifier (bundleIdentifierHandle, IntPtr.Zero), releaseHandle: true ); diff --git a/src/CoreText/Adapter.cs b/src/CoreText/Adapter.cs index b9f9fc9ba2d8..af706156e441 100644 --- a/src/CoreText/Adapter.cs +++ b/src/CoreText/Adapter.cs @@ -84,9 +84,7 @@ public static void AssertWritable (NSDictionary dictionary) var cfArrayRef = CFDictionary.GetValue (dictionary.Handle, key.Handle); GC.KeepAlive (dictionary); GC.KeepAlive (key); - if (cfArrayRef == NativeHandle.Zero || CFArray.GetCount (cfArrayRef) == 0) - return new T [0]; - return NSArray.ArrayFromHandle (cfArrayRef, converter); + return NSArray.NonNullArrayFromHandleDropNullElements (cfArrayRef, converter); } public static float? GetSingleValue (IDictionary dictionary, NSObject? key) diff --git a/src/CoreText/CTFont.cs b/src/CoreText/CTFont.cs index 28d674c3c8c9..c6f07569c765 100644 --- a/src/CoreText/CTFont.cs +++ b/src/CoreText/CTFont.cs @@ -3560,9 +3560,7 @@ public CTFontFeatureSettings [] GetFeatureSettings () public CTFontTable [] GetAvailableTables (CTFontTableOptions options) { var cfArrayRef = CTFontCopyAvailableTables (Handle, options); - if (cfArrayRef == IntPtr.Zero) - return Array.Empty (); - return NSArray.ArrayFromHandle (cfArrayRef, v => { + return NSArray.NonNullArrayFromHandle (cfArrayRef, v => { return (CTFontTable) (uint) (IntPtr) v; }, true); } diff --git a/src/CoreText/CTFontManager.cs b/src/CoreText/CTFontManager.cs index fcd4c705ebb1..f36ec20189a7 100644 --- a/src/CoreText/CTFontManager.cs +++ b/src/CoreText/CTFontManager.cs @@ -153,18 +153,6 @@ static NSArray EnsureNonNullArray (object [] items, string name) return NSArray.FromObjects (items); } - static T []? ArrayFromHandle (IntPtr handle, bool releaseAfterUse) where T : class, INativeObject - { - if (handle == IntPtr.Zero) - return null; - try { - return NSArray.ArrayFromHandle (handle); - } finally { - if (releaseAfterUse) - CFObject.CFRelease (handle); - } - } - [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] @@ -198,7 +186,7 @@ static NSArray EnsureNonNullArray (object [] items, string name) return null; GC.KeepAlive (arr); } - return ArrayFromHandle (error_array, releaseAfterUse: true); + return NSArray.ArrayFromHandleDropNullElements (error_array, releaseHandle: true); } } @@ -211,7 +199,7 @@ static unsafe byte TrampolineRegistrationHandler (IntPtr block, /* NSArray */ In if (del is null) return 0; - var rv = del (NSArray.ArrayFromHandle (errors), done == 0 ? false : true); + var rv = del (NSArray.NonNullArrayFromHandleDropNullElements (errors), done == 0 ? false : true); return rv ? (byte) 1 : (byte) 0; } @@ -308,7 +296,7 @@ public static void RegisterFonts (NSUrl [] fontUrls, CTFontManagerScope scope, b return null; GC.KeepAlive (arr); } - return ArrayFromHandle (error_array, releaseAfterUse: true); + return NSArray.ArrayFromHandleDropNullElements (error_array, releaseHandle: true); } } @@ -589,7 +577,7 @@ public unsafe static void UnregisterFontDescriptors (CTFontDescriptor [] fontDes { var p = CTFontManagerCopyRegisteredFontDescriptors (scope, enabled.AsByte ()); // Copy/Create rule - we must release the CFArrayRef - return ArrayFromHandle (p, releaseAfterUse: true); + return NSArray.ArrayFromHandleDropNullElements (p, releaseHandle: true); } #endif @@ -628,7 +616,7 @@ public unsafe static void UnregisterFontDescriptors (CTFontDescriptor [] fontDes var p = CTFontManagerCreateFontDescriptorsFromData (data.Handle); GC.KeepAlive (data); // Copy/Create rule - we must release the CFArrayRef - return ArrayFromHandle (p, releaseAfterUse: true); + return NSArray.ArrayFromHandleDropNullElements (p, releaseHandle: true); } #if __IOS__ @@ -676,7 +664,7 @@ static unsafe void TrampolineRequestFonts (IntPtr block, /* CFArray */ IntPtr fo { var del = BlockLiteral.GetTarget (block); if (del is not null) - del (NSArray.ArrayFromHandle (fontDescriptors)); + del (NSArray.NonNullArrayFromHandleDropNullElements (fontDescriptors)); } [SupportedOSPlatform ("ios13.0")] diff --git a/src/CoreVideo/CVPixelFormatDescription.cs b/src/CoreVideo/CVPixelFormatDescription.cs index 2fb0d853fc12..5a86b8498001 100644 --- a/src/CoreVideo/CVPixelFormatDescription.cs +++ b/src/CoreVideo/CVPixelFormatDescription.cs @@ -281,7 +281,7 @@ static CVPixelFormatDescription () /// Get all the known pixel format types. public static NSNumber [] AllTypes { get { - return NSArray.ArrayFromHandle (CVPixelFormatDescriptionArrayCreateWithAllPixelFormatTypes (IntPtr.Zero), releaseHandle: true); + return NSArray.NonNullArrayFromHandleDropNullElements (CVPixelFormatDescriptionArrayCreateWithAllPixelFormatTypes (IntPtr.Zero), releaseHandle: true); } } diff --git a/src/Foundation/DictionaryContainer.cs b/src/Foundation/DictionaryContainer.cs index f373b9acb8eb..76b82f406d5e 100644 --- a/src/Foundation/DictionaryContainer.cs +++ b/src/Foundation/DictionaryContainer.cs @@ -203,7 +203,7 @@ bool TryGetNativeValue (NativeHandle key, out NativeHandle value) if (!TryGetNativeValue (key, out var value)) return null; - return NSArray.ArrayFromHandle (value); + return NSArray.ArrayFromHandleDropNullElements (value); } /// Returns the nullable array of associated with the specified . diff --git a/src/Foundation/NSArray.cs b/src/Foundation/NSArray.cs index 26815d1a9618..236f64c43321 100644 --- a/src/Foundation/NSArray.cs +++ b/src/Foundation/NSArray.cs @@ -413,6 +413,25 @@ public static NSArray FromIntPtrs (NativeHandle [] vals) } } + /// Create an from the specified pointers. + /// Array of pointers (to instances). + /// A delegate to convert each array element to a native handle. + /// If the array is null, is returned. + [return: NotNullIfNotNull (nameof (array))] + internal static NSArray? FromIntPtrs (T []? array, Func getHandle) + { + if (array is null) + return null; + + var handles = new NativeHandle [array.Length]; + for (var i = 0; i < handles.Length; i++) + handles [i] = getHandle (array [i]); + + var rv = FromIntPtrs (handles); + GC.KeepAlive (array); + return rv; + } + internal static nuint GetCount (IntPtr handle) { #if MONOMAC @@ -439,34 +458,22 @@ internal static NativeHandle GetAtIndex (NativeHandle handle, nuint i) return CFArray.StringArrayFromHandle (handle); } #endif // !XAMCORE_5_0 -#nullable disable + /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. /// Parameter type, determines the kind of array returned. /// Pointer (handle) to the unmanaged object. - /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. /// An C# array with the values. /// - /// Use this method to get a set of NSObject arrays from a handle to an NSArray - /// - /// (someHandle); + /// + /// (someHandle); /// ]]> - /// - /// - static public T [] ArrayFromHandle (NativeHandle handle) where T : class, INativeObject + /// + /// + public static T? []? ArrayFromHandle (NativeHandle handle) where T : class, INativeObject { - if (handle == NativeHandle.Zero) - return null; - - var c = GetCount (handle); - T [] ret = new T [c]; - - for (uint i = 0; i < c; i++) { - ret [i] = UnsafeGetItem (handle, i); - } - return ret; + return ArrayFromHandle (handle, false); } /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. @@ -475,7 +482,6 @@ static public T [] ArrayFromHandle (NativeHandle handle) where T : class, INa /// Whether the native NSArray instance should be released before returning or not. /// A C# array with the values. /// - /// Use this method to get a set of NSObject arrays from a handle to an NSArray /// /// (NativeHandle handle) where T : class, INa /// ]]> /// /// - public static T [] ArrayFromHandle (NativeHandle handle, bool releaseHandle) where T : class, INativeObject + public static T? []? ArrayFromHandle (NativeHandle handle, bool releaseHandle) where T : class, INativeObject { - var rv = ArrayFromHandle (handle); - if (releaseHandle && handle != NativeHandle.Zero) - NSObject.DangerousRelease (handle); - return rv; + return ArrayFromHandle (handle, h => Runtime.GetINativeObject (h, false), NSNullBehavior.ConvertToNull, releaseHandle); } - static Array ArrayFromHandle (NativeHandle handle, Type elementType) + /// 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. + /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. + /// An C# array with the values. + /// + /// + /// (someHandle, myCreator); + /// ]]> + /// + /// + public static T? []? ArrayFromHandle (NativeHandle handle, Converter creator) + { + return ArrayFromHandle (handle, creator, NSNullBehavior.ConvertToNull, false); + } + + /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. + /// 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 handle. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values, or if the handle is . + public static T? []? ArrayFromHandle (NativeHandle handle, Converter creator, bool releaseHandle) + { + return ArrayFromHandle (handle, creator, NSNullBehavior.ConvertToNull, releaseHandle); + } + + /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// A delegate to convert a native handle to an object of type T. + /// How to handle null and NSNull elements in the native array. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values, or if the handle is . + internal static T? []? ArrayFromHandle (NativeHandle handle, Converter createObject, NSNullBehavior nsNullElementBehavior, bool releaseHandle = false) { if (handle == NativeHandle.Zero) return null; - var c = (int) GetCount (handle); - var rv = Array.CreateInstance (elementType, c); - for (int i = 0; i < c; i++) { - rv.SetValue (UnsafeGetItem (handle, (nuint) i, elementType), i); + 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 (!TryGetItem (val, createObject, nsNullElementBehavior, i, out var value)) + continue; + ret [nextIndex++] = value; + } + + if (nextIndex != count) + Array.Resize (ref ret, (int) nextIndex); + + return ret; + } finally { + if (releaseHandle) + NSObject.DangerousRelease (handle); } - return rv; } + /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// How to handle null and NSNull elements in the native array. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values, or if the handle is . + internal static T? []? ArrayFromHandle (NativeHandle handle, NSNullBehavior nsNullElementBehavior, bool releaseHandle = false) where T : class, INativeObject + { + return ArrayFromHandle (handle, (h) => Runtime.GetINativeObject (h, false), nsNullElementBehavior, releaseHandle); + } + + /// Attempts to get an item from the native array, handling null and NSNull elements according to the specified behavior. + /// The type of the item to create. + /// The native handle of the element. + /// A delegate to convert a native handle to an object of type T. + /// How to handle null and NSNull elements. + /// The index of the element in the source array (used for error messages). + /// When this method returns, contains the created object, or the default value if the element was skipped. + /// if the element should be included in the result array; if it should be skipped. + static bool TryGetItem (NativeHandle elementHandle, Converter createObject, NSNullBehavior nsNullElementBehavior, nuint index, out T? value) + { + value = default (T); + + switch (nsNullElementBehavior) { + case NSNullBehavior.Drop: + if (elementHandle == NativeHandle.Zero) + return false; + if (elementHandle == NSNull.NullHandle) + return false; + value = createObject (elementHandle); + return value is not null; + case NSNullBehavior.DropIfIncompatible: + if (elementHandle == NativeHandle.Zero) + return false; + if (elementHandle == NSNull.NullHandle) { + if (NSNull.Null is T nullT) + value = nullT; + } else { + value = createObject (elementHandle); + } + return value is not null; + case NSNullBehavior.ConvertToNull: + if (elementHandle == NativeHandle.Zero) + return true; + if (elementHandle == NSNull.NullHandle) + return true; + value = createObject (elementHandle); + return true; + case NSNullBehavior.Throw: + if (elementHandle != NSNull.NullHandle && elementHandle != NativeHandle.Zero) + value = createObject (elementHandle); + if (value is null) + throw new InvalidOperationException ($"Invalid null element at index {index}"); + return true; + default: + throw new InvalidOperationException ($"Unknown null behavior: {nsNullElementBehavior}"); + } + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, guaranteeing a non-null return value. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values. Returns an empty array if the handle is . + internal static T? [] NonNullArrayFromHandle (NativeHandle handle, bool releaseHandle = false) where T : class, INativeObject + { + return NonNullArrayFromHandle (handle, NSNullBehavior.ConvertToNull, releaseHandle); + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, guaranteeing a non-null return value. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// How to handle null and NSNull elements in the native array. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values. Returns an empty array if the handle is . + internal static T? [] NonNullArrayFromHandle (NativeHandle handle, NSNullBehavior nsNullElementBehavior, bool releaseHandle = false) where T : class, INativeObject + { + var rv = ArrayFromHandle (handle, nsNullElementBehavior, releaseHandle); + return rv ?? Array.Empty (); + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, guaranteeing a non-null return value. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// A delegate to convert a native handle to an object of type T. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values. Returns an empty array if the handle is . + internal static T? [] NonNullArrayFromHandle (NativeHandle handle, Converter creator, bool releaseHandle = false) + { + var rv = ArrayFromHandle (handle, creator, NSNullBehavior.ConvertToNull, releaseHandle); + return rv ?? Array.Empty (); + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, dropping any null or NSNull elements. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values (excluding null elements), or if the handle is . + internal static T []? ArrayFromHandleDropNullElements (NativeHandle handle, bool releaseHandle = false) where T : class, INativeObject + { + return ArrayFromHandleDropNullElements (handle, (h) => Runtime.GetINativeObject (h, false)!, releaseHandle); + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, dropping any null or NSNull elements. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// A delegate to convert a native handle to an object of type T. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values (excluding null elements), or if the handle is . + internal static T []? ArrayFromHandleDropNullElements (NativeHandle handle, Converter createObject, bool releaseHandle = false) + { + return ArrayFromHandle (handle, createObject, NSNullBehavior.Drop, releaseHandle)!; + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, dropping null elements and guaranteeing a non-null return value. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values (excluding null elements). Returns an empty array if the handle is . + internal static T [] NonNullArrayFromHandleDropNullElements (NativeHandle handle, bool releaseHandle = false) where T : class, INativeObject + { + return NonNullArrayFromHandleDropNullElements (handle, (h) => Runtime.GetINativeObject (h, false)!, releaseHandle); + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, dropping null elements and guaranteeing a non-null return value. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// A delegate to convert a native handle to an object of type T. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values (excluding null elements). Returns an empty array if the handle is . + internal static T [] NonNullArrayFromHandleDropNullElements (NativeHandle handle, Converter createObject, bool releaseHandle = false) + { + return NonNullArrayFromHandleDropNullElements (handle, createObject, NSNullBehavior.Drop, releaseHandle); + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, dropping null elements and guaranteeing a non-null return value. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// How to handle null and NSNull elements in the native array. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values (excluding null elements). Returns an empty array if the handle is . + internal static T [] NonNullArrayFromHandleDropNullElements (NativeHandle handle, NSNullBehavior nsNullElementBehavior, bool releaseHandle = false) where T : class, INativeObject + { + return NonNullArrayFromHandleDropNullElements (handle, (h) => Runtime.GetINativeObject (h, false)!, nsNullElementBehavior, releaseHandle); + } + + /// Returns a strongly-typed C# array from a handle to an NSArray, dropping null elements and guaranteeing a non-null return value. + /// Parameter type, determines the kind of array returned. + /// Pointer (handle) to the unmanaged object. + /// A delegate to convert a native handle to an object of type T. + /// How to handle null and NSNull elements in the native array. + /// Whether the native NSArray instance should be released before returning or not. + /// A C# array with the values (excluding null elements). Returns an empty array if the handle is . + internal static T [] NonNullArrayFromHandleDropNullElements (NativeHandle handle, Converter createObject, NSNullBehavior nsNullElementBehavior, bool releaseHandle = false) + { + var rv = ArrayFromHandle (handle, createObject, nsNullElementBehavior, releaseHandle); + if (rv is null) + return Array.Empty (); + return rv!; + } + +#nullable disable + static public T [] EnumsFromHandle (NativeHandle handle) where T : struct, IConvertible { if (handle == NativeHandle.Zero) @@ -599,16 +819,7 @@ static public T [] FromArrayNative (NSArray weakArray) where T : class, INati /// static public T [] ArrayFromHandleFunc (NativeHandle handle, Func createObject) { - if (handle == NativeHandle.Zero) - return null; - - var c = GetCount (handle); - T [] ret = new T [c]; - - for (uint i = 0; i < c; i++) - ret [i] = createObject (GetAtIndex (handle, i)); - - return ret; + return ArrayFromHandle (handle, (v) => createObject (v)); } /// Create a managed array from a pointer to a native NSArray instance. @@ -617,12 +828,10 @@ static public T [] ArrayFromHandleFunc (NativeHandle handle, FuncWhether the native NSArray instance should be released before returning or not. public static T [] ArrayFromHandleFunc (NativeHandle handle, Func createObject, bool releaseHandle) { - var rv = ArrayFromHandleFunc (handle, createObject); - if (releaseHandle && handle != NativeHandle.Zero) - NSObject.DangerousRelease (handle); - return rv; + return ArrayFromHandle (handle, (v) => createObject (v), releaseHandle); } +#nullable enable /// Creates 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. @@ -632,35 +841,16 @@ public static T [] ArrayFromHandleFunc (NativeHandle handle, Func /// 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); - } + return ArrayFromHandleDropNullElements (handle, + (dictionaryHandle) => { + return createObjectFromDictionary (Runtime.GetNSObject (dictionaryHandle)!); + }, + releaseHandle); } /// 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. @@ -679,52 +869,16 @@ internal static T [] NonNullDictionaryArrayFromHandleDropNullElements (Native 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. - /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray. - /// An C# array with the values. - /// - /// Use this method to get a set of NSObject arrays from a handle to an NSArray. Instead of wrapping the results in NSObjects, the code invokes your method to create the return value. - /// - /// (someHandle, myCreator); - /// ]]> - /// - /// - static public T [] ArrayFromHandle (NativeHandle handle, Converter creator) - { - if (handle == NativeHandle.Zero) - return null; - - var c = GetCount (handle); - T [] ret = new T [c]; - - for (uint i = 0; i < c; i++) - ret [i] = creator (GetAtIndex (handle, i)); - - return ret; - } - - static public T [] ArrayFromHandle (NativeHandle handle, Converter creator, bool releaseHandle) - { - var rv = ArrayFromHandle (handle, creator); - if (releaseHandle && handle != NativeHandle.Zero) - NSObject.DangerousRelease (handle); - return rv; - } // FIXME: before proving a real `this` indexer we need to clean the issues between // NSObject and INativeObject coexistance across all the API (it can not return T) - static T UnsafeGetItem (NativeHandle handle, nuint index) where T : class, INativeObject + /// Gets a single item from a native NSArray at the specified index, without bounds checking. + /// The type of the item to retrieve. + /// Pointer (handle) to the native NSArray. + /// The zero-based index of the element to retrieve. + /// The item at the specified index, or if the element is an NSNull instance. + static T? UnsafeGetItem (NativeHandle handle, nuint index) where T : class, INativeObject { var val = GetAtIndex (handle, index); // A native code could return NSArray with NSNull.Null elements @@ -736,18 +890,7 @@ static T UnsafeGetItem (NativeHandle handle, nuint index) where T : class, IN return Runtime.GetINativeObject (val, false); } - static object UnsafeGetItem (NativeHandle handle, nuint index, Type type) - { - var val = GetAtIndex (handle, index); - // A native code could return NSArray with NSNull.Null elements - // and they should be valid for things like T : NSDate so we handle - // them as just null values inside the array - if (val == NSNull.NullHandle) - return null; - - return Runtime.GetINativeObject (val, false, type); - } - +#nullable disable // can return an INativeObject or an NSObject /// To be added. /// To be added. diff --git a/src/Foundation/NSDictionary_2.cs b/src/Foundation/NSDictionary_2.cs index 1e1a32a39b4b..574928fc33c5 100644 --- a/src/Foundation/NSDictionary_2.cs +++ b/src/Foundation/NSDictionary_2.cs @@ -185,7 +185,7 @@ public Dictionary ToDictionary (Func public TKey [] Keys { get { using (var pool = new NSAutoreleasePool ()) - return NSArray.ArrayFromHandle (_AllKeys ()); + return NSArray.NonNullArrayFromHandleDropNullElements (_AllKeys (), NSNullBehavior.DropIfIncompatible); } } @@ -199,7 +199,7 @@ public TKey [] KeysForObject (TValue obj) ArgumentNullException.ThrowIfNull (obj); using (var pool = new NSAutoreleasePool ()) { - var ret = NSArray.ArrayFromHandle (_AllKeysForObject (obj.Handle)); + var ret = NSArray.NonNullArrayFromHandleDropNullElements (_AllKeysForObject (obj.Handle), NSNullBehavior.DropIfIncompatible); GC.KeepAlive (obj); return ret; } @@ -212,7 +212,7 @@ public TKey [] KeysForObject (TValue obj) public TValue [] Values { get { using (var pool = new NSAutoreleasePool ()) - return NSArray.ArrayFromHandle (_AllValues ()); + return NSArray.NonNullArrayFromHandleDropNullElements (_AllValues (), NSNullBehavior.DropIfIncompatible); } } @@ -232,7 +232,7 @@ public TValue [] ObjectsForKeys (TKey [] keys, TValue marker) using (var pool = new NSAutoreleasePool ()) { var keysArray = NSArray.FromNativeObjects (keys); - var result = NSArray.ArrayFromHandle (_ObjectsForKeys (keysArray.Handle, marker.Handle)); + var result = NSArray.NonNullArrayFromHandleDropNullElements (_ObjectsForKeys (keysArray.Handle, marker.Handle), NSNullBehavior.DropIfIncompatible); GC.KeepAlive (keysArray); GC.KeepAlive (marker); return result; diff --git a/src/Foundation/NSMutableDictionary_2.cs b/src/Foundation/NSMutableDictionary_2.cs index 702407d3034e..5503c4b38485 100644 --- a/src/Foundation/NSMutableDictionary_2.cs +++ b/src/Foundation/NSMutableDictionary_2.cs @@ -128,7 +128,7 @@ public NSMutableDictionary (TKey? key, TValue? value) public TKey [] Keys { get { using (var pool = new NSAutoreleasePool ()) - return NSArray.ArrayFromHandle (_AllKeys ()); + return NSArray.NonNullArrayFromHandleDropNullElements (_AllKeys (), NSNullBehavior.DropIfIncompatible); } } @@ -142,7 +142,7 @@ public TKey [] KeysForObject (TValue obj) ArgumentNullException.ThrowIfNull (obj); using (var pool = new NSAutoreleasePool ()) { - var result = NSArray.ArrayFromHandle (_AllKeysForObject (obj.Handle)); + var result = NSArray.NonNullArrayFromHandleDropNullElements (_AllKeysForObject (obj.Handle), NSNullBehavior.DropIfIncompatible); GC.KeepAlive (obj); return result; } @@ -155,7 +155,7 @@ public TKey [] KeysForObject (TValue obj) public TValue [] Values { get { using (var pool = new NSAutoreleasePool ()) - return NSArray.ArrayFromHandle (_AllValues ()); + return NSArray.NonNullArrayFromHandleDropNullElements (_AllValues (), NSNullBehavior.DropIfIncompatible); } } @@ -174,7 +174,7 @@ public TValue [] ObjectsForKeys (TKey [] keys, TValue marker) return []; var keysArray = NSArray.FromNativeObjects (keys); - var result = NSArray.ArrayFromHandle (_ObjectsForKeys (keysArray.Handle, marker.Handle)); + var result = NSArray.NonNullArrayFromHandleDropNullElements (_ObjectsForKeys (keysArray.Handle, marker.Handle), NSNullBehavior.DropIfIncompatible); GC.KeepAlive (keysArray); GC.KeepAlive (marker); return result; diff --git a/src/Foundation/NSNullBehavior.cs b/src/Foundation/NSNullBehavior.cs new file mode 100644 index 000000000000..1b45796a2da1 --- /dev/null +++ b/src/Foundation/NSNullBehavior.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Foundation; + +enum NSNullBehavior { + /// NSNull values are dropped. + Drop, + /// NSNull values are dropped, unless they're compatible with the target type (NSObject, NSNull, INativeObject, etc.). + DropIfIncompatible, + /// NSNull values are converted to . + ConvertToNull, + /// An exception is thrown. + Throw, +} diff --git a/src/Foundation/NSOrderedSet.cs b/src/Foundation/NSOrderedSet.cs index 41ba53892239..26955fef61c3 100644 --- a/src/Foundation/NSOrderedSet.cs +++ b/src/Foundation/NSOrderedSet.cs @@ -70,7 +70,7 @@ public NSObject this [nint idx] { public T [] ToArray () where T : class, INativeObject { IntPtr nsarr = _ToArray (); - return NSArray.ArrayFromHandle (nsarr); + return NSArray.NonNullArrayFromHandleDropNullElements (nsarr, nsNullElementBehavior: NSNullBehavior.DropIfIncompatible); } /// Creates a new from an array of strongly typed values. diff --git a/src/Foundation/NSSet.cs b/src/Foundation/NSSet.cs index 6aa7ae5584bb..4def13b6525b 100644 --- a/src/Foundation/NSSet.cs +++ b/src/Foundation/NSSet.cs @@ -71,7 +71,7 @@ public NSSet (params string? [] strings) : this (NSArray.FromStrings (strings)) public T [] ToArray () where T : class, INativeObject { IntPtr nsarr = _AllObjects (); - return NSArray.ArrayFromHandle (nsarr); + return NSArray.NonNullArrayFromHandleDropNullElements (nsarr, nsNullElementBehavior: NSNullBehavior.DropIfIncompatible); } /// Creates a new from an array of strongly typed values. diff --git a/src/Foundation/NSUrlSessionHandler.cs b/src/Foundation/NSUrlSessionHandler.cs index 5ecaed7f9a69..fa759871d4c3 100644 --- a/src/Foundation/NSUrlSessionHandler.cs +++ b/src/Foundation/NSUrlSessionHandler.cs @@ -782,8 +782,8 @@ public ServerCertificateCustomValidationCallbackHelper (Func 0 ? certificates [0] : null; + var certificates = ConvertCertificates (secTrust); + var certificate = certificates.Length > 0 ? certificates [0] : null; using X509Chain chain = CreateChain (certificates); SslPolicyErrors sslPolicyErrors = EvaluateSslPolicyErrors (certificate, chain, secTrust); @@ -796,6 +796,8 @@ X509Certificate2 [] ConvertCertificates (SecTrust secTrust) if (SystemVersion.IsAtLeastXcode13) { var originalChain = secTrust.GetCertificateChain (); + if (originalChain is null) + return Array.Empty (); for (int i = 0; i < originalChain.Length; i++) certificates [i] = originalChain [i].ToX509Certificate2 (); } else { diff --git a/src/GameplayKit/GKObstacleGraph.cs b/src/GameplayKit/GKObstacleGraph.cs index 9d8a600d7833..7b599d29e5c7 100644 --- a/src/GameplayKit/GKObstacleGraph.cs +++ b/src/GameplayKit/GKObstacleGraph.cs @@ -16,9 +16,9 @@ public partial class GKObstacleGraph { /// Returns the array of corresponding to the . /// To be added. /// To be added. - public GKGraphNode2D [] GetNodes (GKPolygonObstacle obstacle) + public GKGraphNode2D []? GetNodes (GKPolygonObstacle obstacle) { - return NSArray.ArrayFromHandle (_GetNodes (obstacle)); + return NSArray.ArrayFromHandleDropNullElements (_GetNodes (obstacle)); } } @@ -67,9 +67,9 @@ public GKObstacleGraph (NSCoder coder) : base (coder) /// To be added. /// To be added. /// To be added. - public new NodeType [] GetNodes (GKPolygonObstacle obstacle) + public new NodeType []? GetNodes (GKPolygonObstacle obstacle) { - return NSArray.ArrayFromHandle (_GetNodes (obstacle)); + return NSArray.ArrayFromHandleDropNullElements (_GetNodes (obstacle)); } } } diff --git a/src/GameplayKit/NSArray_GameplayKit.cs b/src/GameplayKit/NSArray_GameplayKit.cs index 056969c23394..7f02af96914d 100644 --- a/src/GameplayKit/NSArray_GameplayKit.cs +++ b/src/GameplayKit/NSArray_GameplayKit.cs @@ -29,7 +29,7 @@ public static T [] GetShuffledArray (this NSArray This, GKRandomSource random { if (randomSource is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (randomSource)); - T [] result = NSArray.ArrayFromHandle (Messaging.IntPtr_objc_msgSend_IntPtr (This.Handle, Selector.GetHandle ("shuffledArrayWithRandomSource:"), randomSource.Handle)); + var result = NSArray.NonNullArrayFromHandleDropNullElements (Messaging.IntPtr_objc_msgSend_IntPtr (This.Handle, Selector.GetHandle ("shuffledArrayWithRandomSource:"), randomSource.Handle)); GC.KeepAlive (This); GC.KeepAlive (randomSource); return result; @@ -43,7 +43,7 @@ public static T [] GetShuffledArray (this NSArray This, GKRandomSource random [Export ("shuffledArray")] public static T [] GetShuffledArray (this NSArray This) where T : class, INativeObject { - T [] result = NSArray.ArrayFromHandle (Messaging.IntPtr_objc_msgSend (This.Handle, Selector.GetHandle ("shuffledArray"))); + var result = NSArray.NonNullArrayFromHandleDropNullElements (Messaging.IntPtr_objc_msgSend (This.Handle, Selector.GetHandle ("shuffledArray"))); GC.KeepAlive (This); return result; } diff --git a/src/MediaPlayer/MPNowPlayingInfoCenter.cs b/src/MediaPlayer/MPNowPlayingInfoCenter.cs index 9f86b3696296..633c7a68b17e 100644 --- a/src/MediaPlayer/MPNowPlayingInfoCenter.cs +++ b/src/MediaPlayer/MPNowPlayingInfoCenter.cs @@ -286,11 +286,11 @@ internal MPNowPlayingInfo (NSDictionary? source) DefaultPlaybackRate = (result as NSNumber)?.DoubleValue; if (TryGetValue (source, MPNowPlayingInfoCenter.PropertyAvailableLanguageOptions, out result)) { - AvailableLanguageOptions = NSArray.ArrayFromHandle (result.Handle); + AvailableLanguageOptions = NSArray.ArrayFromHandleDropNullElements (result.Handle); GC.KeepAlive (result); } if (TryGetValue (source, MPNowPlayingInfoCenter.PropertyCurrentLanguageOptions, out result)) { - CurrentLanguageOptions = NSArray.ArrayFromHandle (result.Handle); + CurrentLanguageOptions = NSArray.ArrayFromHandleDropNullElements (result.Handle); GC.KeepAlive (result); } if (TryGetValue (source, MPNowPlayingInfoCenter.PropertyCollectionIdentifier, out result)) diff --git a/src/Metal/MTLDevice.cs b/src/Metal/MTLDevice.cs index a24dbbfaa543..9a30415ba5fe 100644 --- a/src/Metal/MTLDevice.cs +++ b/src/Metal/MTLDevice.cs @@ -63,12 +63,10 @@ public static IMTLDevice? SystemDefault { [SupportedOSPlatform ("macos")] [SupportedOSPlatform ("ios18.0")] [SupportedOSPlatform ("tvos18.0")] - public static IMTLDevice [] GetAllDevices () + public static IMTLDevice []? GetAllDevices () { var rv = MTLCopyAllDevices (); - var devices = NSArray.ArrayFromHandle (rv); - NSObject.DangerousRelease (rv); - return devices; + return NSArray.ArrayFromHandleDropNullElements (rv, releaseHandle: true); } #if MONOMAC @@ -96,13 +94,10 @@ public static IMTLDevice [] GetAllDevices (MTLDeviceNotificationHandler handler, rv = MTLCopyAllDevicesWithObserver (&observer_handle, &block); } - var obj = NSArray.ArrayFromHandle (rv); - NSObject.DangerousRelease (rv); + // owns: Apple's documentation says "The observer out parameter is returned with a +1 retain count [...]." + observer = Runtime.GetNSObject (observer_handle, owns: true); - observer = Runtime.GetNSObject (observer_handle); - NSObject.DangerousRelease (observer_handle); // Apple's documentation says "The observer out parameter is returned with a +1 retain count [...]." - - return obj; + return NSArray.NonNullArrayFromHandleDropNullElements (rv, releaseHandle: true); } /// To be added. diff --git a/src/NaturalLanguage/NLVectorDictionary.cs b/src/NaturalLanguage/NLVectorDictionary.cs index 5a1854db5b10..737643661756 100644 --- a/src/NaturalLanguage/NLVectorDictionary.cs +++ b/src/NaturalLanguage/NLVectorDictionary.cs @@ -19,14 +19,14 @@ public NLVectorDictionary (NSDictionary dictionary) : base (dictionary) { } - public float [] this [NSString key] { + public float []? this [NSString key] { get { if (key is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (key)); var a = CFDictionary.GetValue (Dictionary.Handle, key.Handle); GC.KeepAlive (key); - return NSArray.ArrayFromHandle (a, input => { + return NSArray.ArrayFromHandleDropNullElements (a, input => { return new NSNumber (input).FloatValue; }); } @@ -41,7 +41,7 @@ public float [] this [NSString key] { } } - public float [] this [string key] { + public float []? this [string key] { get { return this [(NSString) key]; } diff --git a/src/Network/NWEndpoint.cs b/src/Network/NWEndpoint.cs index 98669b67138f..77bb494d20e4 100644 --- a/src/Network/NWEndpoint.cs +++ b/src/Network/NWEndpoint.cs @@ -241,11 +241,6 @@ public NWTxtRecord? TxtRecord { } } - internal NWEndpoint []? FromNSArrayHandle (IntPtr handle) - { - return NSArray.ArrayFromHandle (handle); - } - /// Returns an autoreleased NSArray handle. internal IntPtr ToNSArrayHandle (NWEndpoint [] array) { diff --git a/src/ObjCRuntime/RegistrarHelper.cs b/src/ObjCRuntime/RegistrarHelper.cs index cbe3e442205b..e334ebf10c59 100644 --- a/src/ObjCRuntime/RegistrarHelper.cs +++ b/src/ObjCRuntime/RegistrarHelper.cs @@ -295,7 +295,7 @@ unsafe static void NSArray_string_managed_to_native (IntPtr* ptr, string [] valu *ptr = rv; } - unsafe static void NSArray_native_to_managed (IntPtr* ptr, ref T []? value, ref T []? copy) where T : class, INativeObject + unsafe static void NSArray_native_to_managed (IntPtr* ptr, ref T? []? value, ref T? []? copy) where T : class, INativeObject { if (ptr is not null) { value = NSArray.ArrayFromHandle (*ptr); diff --git a/src/Photos/PHFetchResult.cs b/src/Photos/PHFetchResult.cs index 27132407198b..bdb4fd002ef8 100644 --- a/src/Photos/PHFetchResult.cs +++ b/src/Photos/PHFetchResult.cs @@ -40,10 +40,10 @@ IEnumerator IEnumerable.GetEnumerator () /// Returns the objects at , all of which must be type T. /// To be added. /// To be added. - public T [] ObjectsAt (NSIndexSet indexes) where T : NSObject + public T? [] ObjectsAt (NSIndexSet indexes) where T : NSObject { var nsarr = _ObjectsAt (indexes); - return NSArray.ArrayFromHandle (nsarr); + return NSArray.NonNullArrayFromHandle (nsarr); } } } diff --git a/src/Security/Certificate.cs b/src/Security/Certificate.cs index db58710b6a1b..16406191d629 100644 --- a/src/Security/Certificate.cs +++ b/src/Security/Certificate.cs @@ -534,7 +534,6 @@ public static SecIdentity Import (byte [] data, string password) throw new ArgumentException (nameof (password)); using (var pwstring = new NSString (password)) using (var options = NSMutableDictionary.FromObjectAndKey (pwstring, SecImportExport.Passphrase)) { - NSDictionary [] array; #if __MACOS__ /* There are unfortunate platform differences for SecPKCS12Import: * @@ -609,11 +608,11 @@ public static SecIdentity Import (byte [] data, string password) if (OperatingSystem.IsMacOSVersionAtLeast (15, 0)) options.Add (SecImportExport.ToMemoryOnly, NSNumber.FromBoolean (true)); #endif - SecStatusCode result = SecImportExport.ImportPkcs12 (data, options, out array); + SecStatusCode result = SecImportExport.ImportPkcs12 (data, options, out var array); if (result != SecStatusCode.Success) throw new InvalidOperationException (result.ToString ()); - return new SecIdentity (array [0].LowlevelObjectForKey (SecImportExport.Identity.Handle), false); + return new SecIdentity (array! [0].LowlevelObjectForKey (SecImportExport.Identity.Handle), false); } } diff --git a/src/Security/ImportExport.cs b/src/Security/ImportExport.cs index b7875c50e0bb..a2714e5b7d24 100644 --- a/src/Security/ImportExport.cs +++ b/src/Security/ImportExport.cs @@ -45,7 +45,7 @@ public partial class SecImportExport { /// To be added. /// To be added. /// To be added. - static public SecStatusCode ImportPkcs12 (byte [] buffer, NSDictionary options, out NSDictionary [] array) + static public SecStatusCode ImportPkcs12 (byte [] buffer, NSDictionary options, out NSDictionary []? array) { using (NSData data = NSData.FromArray (buffer)) { return ImportPkcs12 (data, options, out array); @@ -58,7 +58,7 @@ static public SecStatusCode ImportPkcs12 (byte [] buffer, NSDictionary options, /// To be added. /// To be added. /// To be added. - static public SecStatusCode ImportPkcs12 (NSData data, NSDictionary options, out NSDictionary [] array) + static public SecStatusCode ImportPkcs12 (NSData data, NSDictionary options, out NSDictionary []? array) { if (options is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (options)); @@ -70,8 +70,7 @@ static public SecStatusCode ImportPkcs12 (NSData data, NSDictionary options, out GC.KeepAlive (data); GC.KeepAlive (options); } - array = NSArray.ArrayFromHandle (handle); - NSObject.DangerousRelease (handle); + array = NSArray.ArrayFromHandleDropNullElements (handle, releaseHandle: true); return code; } } diff --git a/src/Security/SecIdentity2.cs b/src/Security/SecIdentity2.cs index a26bef4aaea5..d5806ffa9d98 100644 --- a/src/Security/SecIdentity2.cs +++ b/src/Security/SecIdentity2.cs @@ -78,14 +78,10 @@ public SecIdentity2 (SecIdentity identity, params SecCertificate [] certificates /// To be added. /// To be added. /// To be added. - public SecCertificate [] Certificates { + public SecCertificate []? Certificates { get { var certArray = sec_identity_copy_certificates_ref (GetCheckedHandle ()); - try { - return NSArray.ArrayFromHandle (certArray); - } finally { - CFObject.CFRelease (certArray); - } + return NSArray.ArrayFromHandleDropNullElements (certArray, releaseHandle: true); } } diff --git a/src/Security/SecTrust.cs b/src/Security/SecTrust.cs index c6d260aa38d2..ba83a42778d5 100644 --- a/src/Security/SecTrust.cs +++ b/src/Security/SecTrust.cs @@ -52,7 +52,7 @@ public SecTrust (SecCertificate certificate, SecPolicy policy) [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] [SupportedOSPlatform ("tvos")] - public SecPolicy [] GetPolicies () + public SecPolicy []? GetPolicies () { IntPtr p = IntPtr.Zero; SecStatusCode result; @@ -61,7 +61,7 @@ public SecPolicy [] GetPolicies () } if (result != SecStatusCode.Success) throw new InvalidOperationException (result.ToString ()); - return NSArray.ArrayFromHandle (p, releaseHandle: true); + return NSArray.ArrayFromHandleDropNullElements (p, releaseHandle: true); } [DllImport (Constants.SecurityLibrary)] @@ -164,7 +164,7 @@ public bool NetworkFetchAllowed { [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] [SupportedOSPlatform ("tvos")] - public SecCertificate [] GetCustomAnchorCertificates () + public SecCertificate []? GetCustomAnchorCertificates () { IntPtr p; SecStatusCode result; @@ -173,7 +173,7 @@ public SecCertificate [] GetCustomAnchorCertificates () } if (result != SecStatusCode.Success) throw new InvalidOperationException (result.ToString ()); - return NSArray.ArrayFromHandle (p, releaseHandle: true); + return NSArray.ArrayFromHandleDropNullElements (p, releaseHandle: true); } [SupportedOSPlatform ("ios")] diff --git a/src/Security/Trust.cs b/src/Security/Trust.cs index f9102014cd83..526d223540e3 100644 --- a/src/Security/Trust.cs +++ b/src/Security/Trust.cs @@ -238,8 +238,8 @@ public SecCertificate this [nint index] { [SupportedOSPlatform ("macos")] [SupportedOSPlatform ("ios15.0")] [SupportedOSPlatform ("maccatalyst")] - public SecCertificate [] GetCertificateChain () - => NSArray.ArrayFromHandle (SecTrustCopyCertificateChain (Handle), releaseHandle: true); + public SecCertificate []? GetCertificateChain () + => NSArray.ArrayFromHandleDropNullElements (SecTrustCopyCertificateChain (Handle), releaseHandle: true); [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] diff --git a/src/UIKit/UICellAccessory.cs b/src/UIKit/UICellAccessory.cs index a743fbb1aa7e..9e404c6c0756 100644 --- a/src/UIKit/UICellAccessory.cs +++ b/src/UIKit/UICellAccessory.cs @@ -67,7 +67,7 @@ static unsafe nuint Invoke (IntPtr block, IntPtr accessories) var del = BlockLiteral.GetTarget (block); if (del is null) return default; - nuint retval = del (NSArray.ArrayFromHandle (accessories)); + nuint retval = del (NSArray.NonNullArrayFromHandleDropNullElements (accessories)); return retval; } } /* class SDUICellAccessoryPosition */ diff --git a/src/VideoToolbox/VTRawProcessingSession.cs b/src/VideoToolbox/VTRawProcessingSession.cs index 3cc095d2b929..334720681b79 100644 --- a/src/VideoToolbox/VTRawProcessingSession.cs +++ b/src/VideoToolbox/VTRawProcessingSession.cs @@ -134,7 +134,7 @@ static void VTRawProcessingParameterChangeHandlerCallback (IntPtr block, IntPtr { var del = BlockLiteral.GetTarget (block); if (del is not null) { - var newParams = NSArray.ArrayFromHandle (newParameters); + var newParams = NSArray.ArrayFromHandleDropNullElements (newParameters); del (newParams); } } @@ -197,11 +197,8 @@ unsafe static extern VTStatus VTRAWProcessingSessionCopyProcessingParameters ( unsafe { status = VTRAWProcessingSessionCopyProcessingParameters (GetCheckedHandle (), &handle); } - if (status == VTStatus.Ok && handle != IntPtr.Zero) { - var rv = NSArray.ArrayFromHandle (handle)!; - NSObject.DangerousRelease (handle); // owns: true - return rv; - } + if (status == VTStatus.Ok) + return NSArray.ArrayFromHandleDropNullElements (handle, releaseHandle: true); return null; } diff --git a/src/Vision/VNRequest.cs b/src/Vision/VNRequest.cs index 83b2893a7870..d1ed9e9a51bb 100644 --- a/src/Vision/VNRequest.cs +++ b/src/Vision/VNRequest.cs @@ -26,13 +26,13 @@ public partial class VNRequest { /// ]]> /// /// - public virtual T [] GetResults () where T : VNObservation + public virtual T []? GetResults () where T : VNObservation { // From docs: If the request failed, this property will be nil; // otherwise, it will be an array of zero or more VNObservation // subclasses specific to the VNRequest subclass. // ArrayFromHandle does the null checking for us. - return NSArray.ArrayFromHandle (_Results); + return NSArray.ArrayFromHandleDropNullElements (_Results); } } } diff --git a/src/frameworks.sources b/src/frameworks.sources index 049ccf36f46a..50e4ca60490e 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -870,6 +870,7 @@ FOUNDATION_SOURCES = \ Foundation/NSNetService.cs \ Foundation/NSNotificationCenter.cs \ Foundation/NSNull.cs \ + Foundation/NSNullBehavior.cs \ Foundation/NSOrderedSet.cs \ Foundation/NSOrderedSet_1.cs \ Foundation/NSOutputStream.cs \ diff --git a/tests/cecil-tests/Documentation.KnownFailures.txt b/tests/cecil-tests/Documentation.KnownFailures.txt index 028cfab1de5e..267086a2c81b 100644 --- a/tests/cecil-tests/Documentation.KnownFailures.txt +++ b/tests/cecil-tests/Documentation.KnownFailures.txt @@ -11800,7 +11800,6 @@ M:Foundation.INSUrlSessionTaskDelegate.NeedNewBodyStream(Foundation.NSUrlSession M:Foundation.INSUrlSessionWebSocketDelegate.DidClose(Foundation.NSUrlSession,Foundation.NSUrlSessionWebSocketTask,Foundation.NSUrlSessionWebSocketCloseCode,Foundation.NSData) M:Foundation.INSUrlSessionWebSocketDelegate.DidOpen(Foundation.NSUrlSession,Foundation.NSUrlSessionWebSocketTask,System.String) M:Foundation.INSXpcListenerDelegate.ShouldAcceptConnection(Foundation.NSXpcListener,Foundation.NSXpcConnection) -M:Foundation.NSArray.ArrayFromHandle``1(ObjCRuntime.NativeHandle,System.Converter{ObjCRuntime.NativeHandle,``0},System.Boolean) M:Foundation.NSArray.EnumsFromHandle``1(ObjCRuntime.NativeHandle) M:Foundation.NSArray.ToArray M:Foundation.NSArray.ToArray``1 diff --git a/tests/cecil-tests/HandleSafety.KnownFailures.cs b/tests/cecil-tests/HandleSafety.KnownFailures.cs index c8f5e6514460..4d45acc67b81 100644 --- a/tests/cecil-tests/HandleSafety.KnownFailures.cs +++ b/tests/cecil-tests/HandleSafety.KnownFailures.cs @@ -15,7 +15,6 @@ public partial class HandleSafetyTest { "AudioUnit.AUScheduledAudioFileRegion.GetAudioFileRegion ()", "AudioUnit.SamplerInstrumentData.ToStruct ()", "AVFoundation.AVCaptureReactionType_Extensions.GetSystemImage (AVFoundation.AVCaptureReactionType)", - "CoreAnimation.CAGradientLayer.set_Colors (CoreGraphics.CGColor[])", "CoreFoundation.CFArray.Create (ObjCRuntime.INativeObject[])", "CoreFoundation.CFDataBuffer.get_Handle ()", "CoreFoundation.CFDictionary.FromObjectsAndKeys (ObjCRuntime.INativeObject[], ObjCRuntime.INativeObject[])", diff --git a/tests/monotouch-test/CoreAnimation/CAGradientLayerTest.cs b/tests/monotouch-test/CoreAnimation/CAGradientLayerTest.cs new file mode 100644 index 000000000000..41b3056a928a --- /dev/null +++ b/tests/monotouch-test/CoreAnimation/CAGradientLayerTest.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// Unit tests for CAGradientLayer +// + +using CoreAnimation; +using CoreGraphics; + +namespace MonoTouchFixtures.CoreAnimation { + + [TestFixture] + [Preserve (AllMembers = true)] + public class CAGradientLayerTest { + + [Test] + public void Colors_GetSet () + { + using var layer = new CAGradientLayer (); + Assert.IsNull (layer.Colors, "Colors/default"); + + var red = new CGColor (1, 0, 0); + var green = new CGColor (0, 1, 0); + var blue = new CGColor (0, 0, 1); + layer.Colors = new CGColor [] { red, green, blue }; + var colors = layer.Colors; + Assert.IsNotNull (colors, "Colors/assigned"); + Assert.AreEqual (3, colors!.Length, "Colors/length"); + + layer.Colors = null; + Assert.IsNull (layer.Colors, "Colors/null"); + } + } +} diff --git a/tests/monotouch-test/CoreML/MLModelTest.cs b/tests/monotouch-test/CoreML/MLModelTest.cs new file mode 100644 index 000000000000..ef1ed7ea628c --- /dev/null +++ b/tests/monotouch-test/CoreML/MLModelTest.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// Unit tests for MLModel +// + +using CoreML; + +namespace MonoTouchFixtures.CoreML { + + [TestFixture] + [Preserve (AllMembers = true)] + public class MLModelTest { + + [Test] + public void AllComputeDevices () + { + TestRuntime.AssertXcodeVersion (15, 0); + + var devices = MLModel.AllComputeDevices; + Assert.IsNotNull (devices, "AllComputeDevices"); + Assert.That (devices.Length, Is.GreaterThanOrEqualTo (1), "AllComputeDevices/length"); + } + } +} diff --git a/tests/monotouch-test/CoreText/CTFontGetAvailableTablesTest.cs b/tests/monotouch-test/CoreText/CTFontGetAvailableTablesTest.cs new file mode 100644 index 000000000000..ff8e83e6dde5 --- /dev/null +++ b/tests/monotouch-test/CoreText/CTFontGetAvailableTablesTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// Unit tests for CTFont.GetAvailableTables +// + +using CoreText; + +namespace MonoTouchFixtures.CoreText { + + [TestFixture] + [Preserve (AllMembers = true)] + public class CTFontGetAvailableTablesTest { + + [Test] + public void GetAvailableTables () + { + using var font = new CTFont ("Helvetica", 12); + var tables = font.GetAvailableTables (CTFontTableOptions.None); + Assert.IsNotNull (tables, "tables"); + Assert.That (tables.Length, Is.GreaterThan (0), "tables/length"); + } + } +} diff --git a/tests/monotouch-test/GameplayKit/GKObstacleGraphTest.cs b/tests/monotouch-test/GameplayKit/GKObstacleGraphTest.cs new file mode 100644 index 000000000000..e0781d060ed5 --- /dev/null +++ b/tests/monotouch-test/GameplayKit/GKObstacleGraphTest.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// Unit tests for GKObstacleGraph +// + +using System.Numerics; +using GameplayKit; + +namespace MonoTouchFixtures.GameplayKit { + + [TestFixture] + [Preserve (AllMembers = true)] + public class GKObstacleGraphTest { + + [Test] + public void GetNodes_ReturnsNullForUnknownObstacle () + { + TestRuntime.AssertXcodeVersion (7, 0); + + var points = new [] { + new Vector2 (0, 0), + new Vector2 (10, 0), + new Vector2 (10, 10), + }; + var obstacle = GKPolygonObstacle.FromPoints (points); + var graph = GKObstacleGraph.FromObstacles (new GKPolygonObstacle [] { obstacle }, 1.0f); + Assert.IsNotNull (graph, "graph"); + + var nodes = graph!.GetNodes (obstacle); + // May return null or a valid array depending on the graph state + // The key thing is it doesn't crash + if (nodes is not null) + Assert.That (nodes.Length, Is.GreaterThanOrEqualTo (0), "nodes/length"); + + // Query for an obstacle not in the graph + var otherObstacle = GKPolygonObstacle.FromPoints (new [] { + new Vector2 (100, 100), + new Vector2 (110, 100), + new Vector2 (110, 110), + }); + var otherNodes = graph.GetNodes (otherObstacle); + // An obstacle not in the graph may return null or an empty array + if (otherNodes is not null) + Assert.AreEqual (0, otherNodes.Length, "otherNodes/empty"); + } + } +} diff --git a/tests/monotouch-test/GameplayKit/NSArrayGameplayKitTest.cs b/tests/monotouch-test/GameplayKit/NSArrayGameplayKitTest.cs new file mode 100644 index 000000000000..971f06e6ef24 --- /dev/null +++ b/tests/monotouch-test/GameplayKit/NSArrayGameplayKitTest.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// Unit tests for NSArray_GameplayKit +// + +using GameplayKit; + +namespace MonoTouchFixtures.GameplayKit { + + [TestFixture] + [Preserve (AllMembers = true)] + public class NSArrayGameplayKitTest { + + [Test] + public void GetShuffledArray_WithRandomSource () + { + TestRuntime.AssertXcodeVersion (8, 0); + + var array = NSArray.FromNSObjects ( + (NSString) "a", + (NSString) "b", + (NSString) "c", + (NSString) "d", + (NSString) "e" + ); + var randomSource = new GKMersenneTwisterRandomSource (); + + var shuffled = array.GetShuffledArray (randomSource); + Assert.IsNotNull (shuffled, "shuffled"); + Assert.AreEqual (5, shuffled.Length, "shuffled/length"); + } + + [Test] + public void GetShuffledArray_NoArgs () + { + TestRuntime.AssertXcodeVersion (8, 0); + + var array = NSArray.FromNSObjects ( + (NSString) "a", + (NSString) "b", + (NSString) "c" + ); + + var shuffled = array.GetShuffledArray (); + Assert.IsNotNull (shuffled, "shuffled"); + Assert.AreEqual (3, shuffled.Length, "shuffled/length"); + } + } +} diff --git a/tests/monotouch-test/Security/IdentityTest.cs b/tests/monotouch-test/Security/IdentityTest.cs index a6e1084901a4..32e03316aaac 100644 --- a/tests/monotouch-test/Security/IdentityTest.cs +++ b/tests/monotouch-test/Security/IdentityTest.cs @@ -66,5 +66,16 @@ public void AccessCertificates () Assert.That (call, Is.EqualTo (1), "call"); } } + + [Test] + public void Certificates () + { + TestRuntime.AssertXcodeVersion (11, 0); + using var i1 = GetIdentity (); + using var i2 = new SecIdentity2 (i1, i1.Certificate); + var certs = i2.Certificates; + Assert.IsNotNull (certs, "Certificates"); + Assert.That (certs!.Length, Is.GreaterThanOrEqualTo (1), "Certificates/length"); + } } } diff --git a/tests/monotouch-test/Vision/VNRequestGetResultsTest.cs b/tests/monotouch-test/Vision/VNRequestGetResultsTest.cs new file mode 100644 index 000000000000..e8350f1056f5 --- /dev/null +++ b/tests/monotouch-test/Vision/VNRequestGetResultsTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// Unit tests for VNRequest.GetResults +// + +using CoreGraphics; +using Vision; + +namespace MonoTouchFixtures.Vision { + + [TestFixture] + [Preserve (AllMembers = true)] + public class VNRequestGetResultsTest { + + [Test] + public void GetResults_BeforePerform_ReturnsNull () + { + TestRuntime.AssertXcodeVersion (11, 0); + + using var request = new VNDetectFaceRectanglesRequest ((request, error) => { }); + var results = request.GetResults (); + Assert.IsNull (results, "GetResults/before-perform"); + } + + [Test] + public void GetResults_AfterPerform () + { + TestRuntime.AssertXcodeVersion (11, 0); + + using var request = new VNDetectRectanglesRequest ((request, error) => { }); + // Create a simple 100x100 white image + var colorSpace = CGColorSpace.CreateDeviceRGB (); + using var context = new CGBitmapContext (null, 100, 100, 8, 400, colorSpace, CGImageAlphaInfo.PremultipliedLast); + context.SetFillColor (new CGColor (1, 1, 1)); + context.FillRect (new CGRect (0, 0, 100, 100)); + using var image = context.ToImage ()!; + + using var handler = new VNImageRequestHandler (image, new NSDictionary ()); + handler.Perform (new VNRequest [] { request }, out var error); + + // Results may be empty but should not be null after performing + var results = request.GetResults (); + Assert.IsNotNull (results, "GetResults/after-perform"); + } + } +}