xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.48k stars 514 forks source link

NSDictionary has 4 selectors soft-deprecated that are not directly deprecated in NSMutableDictionary #14390

Open Therzok opened 2 years ago

Therzok commented 2 years ago

Steps to Reproduce

  1. Try to use NSDictionary and observe platform attributes being unexpected.

Expected Behavior

NSDictionary.ctor(string) -> Use NSDictionary.FromUrl(NSUrl, out NSError) NSDictionary.ctor(NSUrl) -> Use NSDictionary.FromUrl(NSUrl, out NSError)

Actual Behavior

NSDictionary.ctor(string) -> Use NSMutableDictionary(string) NSDictionary.ctor(NSUrl) -> Use NSMutableDictionary(NSUrl)

Environment

dotnet 6.0.201

Notes

Looking at the native headers, I see:

/* Reads dictionary stored in NSPropertyList format from the specified url. */
+ (nullable NSDictionary<NSString *, ObjectType> *)dictionaryWithContentsOfURL:(NSURL *)url error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) NS_SWIFT_UNAVAILABLE("Use initializer instead");

- (nullable NSDictionary<KeyType, ObjectType> *)initWithContentsOfFile:(NSString *)path API_DEPRECATED_WITH_REPLACEMENT("initWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));

- (nullable NSDictionary<KeyType, ObjectType> *)initWithContentsOfURL:(NSURL *)url API_DEPRECATED_WITH_REPLACEMENT("initWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));

For some reason, the NSMutableDictionary versions are not flagged:

+ (nullable NSMutableDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfFile:(NSString *)path;
+ (nullable NSMutableDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfURL:(NSURL *)url;
- (nullable NSMutableDictionary<KeyType, ObjectType> *)initWithContentsOfFile:(NSString *)path;
- (nullable NSMutableDictionary<KeyType, ObjectType> *)initWithContentsOfURL:(NSURL *)url;
chamons commented 2 years ago

Interesting enough, if you look at the online documentation:

https://developer.apple.com/documentation/foundation/nsmutabledictionary/1574188-dictionarywithcontentsoffile?language=objc

https://developer.apple.com/documentation/foundation/nsmutabledictionary/1574182-dictionarywithcontentsofurl?language=objc

Those are not deprecated, just the non-mutable ones.

chamons commented 2 years ago

And sharpie seems to agree with me:

 % sharpie query iphoneos15.4-arm64.pch -n dictionaryWithContentsOfURL: -b
// @interface NSDeprecated (NSDictionary)
[Category]
[BaseType (typeof(NSDictionary))]
interface NSDictionary_NSDeprecated
{
    // +(NSDictionary<KeyType,ObjectType> * _Nullable)dictionaryWithContentsOfURL:(NSURL * _Nonnull)url __attribute__((availability(macos, introduced=10.0, deprecated=100000))) __attribute__((availability(ios, introduced=2.0, deprecated=100000))) __attribute__((availability(watchos, introduced=2.0, deprecated=100000))) __attribute__((availability(tvos, introduced=9.0, deprecated=100000)));
    [Introduced (PlatformName.MacOSX, 10, 0)]
    [Deprecated (PlatformName.MacOSX, 100000, 0)]
    [Introduced (PlatformName.iOS, 2, 0)]
    [Deprecated (PlatformName.iOS, 100000, 0)]
    [Introduced (PlatformName.WatchOS, 2, 0)]
    [Deprecated (PlatformName.WatchOS, 100000, 0)]
    [Introduced (PlatformName.TvOS, 9, 0)]
    [Deprecated (PlatformName.TvOS, 100000, 0)]
    [Static]
    [Export ("dictionaryWithContentsOfURL:")]
    [return: NullAllowed]
    NSDictionary<NSObject, NSObject> DictionaryWithContentsOfURL (NSUrl url);
}

// @interface NSMutableDictionaryCreation (NSMutableDictionary)
[Category]
[BaseType (typeof(NSMutableDictionary))]
interface NSMutableDictionary_NSMutableDictionaryCreation
{
    // +(NSMutableDictionary<KeyType,ObjectType> * _Nullable)dictionaryWithContentsOfURL:(NSURL * _Nonnull)url;
    [Static]
    [Export ("dictionaryWithContentsOfURL:")]
    [return: NullAllowed]
    NSMutableDictionary<NSObject, NSObject> DictionaryWithContentsOfURL (NSUrl url);
}
chamons commented 2 years ago

So by default @Therzok I'm going to write this off as "Apple being Apple" and not a bug in the SDK.

If I missed something, please reopen.

Therzok commented 2 years ago

@chamons The non-error variants are being recommended, which are deprecated in native. Native recommendations are to use the out error versions.

chamons commented 2 years ago

Is that documented somewhere?

We tend to bind the Apple frameworks exactly as documented/header shows.

I'm not exactly following what your suggest we do here.

Therzok commented 2 years ago

Taken literally from the headers:

/* These methods are deprecated, and will be marked with API_DEPRECATED in a subsequent release. Use the variants that use errors instead. */
+ (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfFile:(NSString *)path API_DEPRECATED_WITH_REPLACEMENT("dictionaryWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));
+ (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfURL:(NSURL *)url API_DEPRECATED_WITH_REPLACEMENT("dictionaryWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));
- (nullable NSDictionary<KeyType, ObjectType> *)initWithContentsOfFile:(NSString *)path API_DEPRECATED_WITH_REPLACEMENT("initWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));
- (nullable NSDictionary<KeyType, ObjectType> *)initWithContentsOfURL:(NSURL *)url API_DEPRECATED_WITH_REPLACEMENT("initWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));
Therzok commented 2 years ago

Basically, NSDictionary.FromUrl(string) is suggesting new NSMutableDictionary(string) as a replacement, but it should be NSDictionary.FromUrl(string, out NSError error).

Edit: The ask is for NSDictionary. I also mentioned NSMutableDictionary in the issue description just because I spotted the inconsistency.

Therzok commented 2 years ago

Or, the former API can be undeprecated, and the NSError can be converted to throw an NSErrorException automatically. But I think the previous fix is more in line with the APIs in Xamarin.Mac.

chamons commented 2 years ago

Ok, it's only in the headers and no API_DEPRECATED yet. I haven't seen Apple do that in awhile.

chamons commented 2 years ago

Wait, those are actually on NSDictionary, not the mutable one.

@interface NSDictionary<KeyType, ObjectType> (NSDeprecated)
/// This method is unsafe because it could potentially cause buffer overruns. You should use -getObjects:andKeys:count:
- (void)getObjects:(ObjectType _Nonnull __unsafe_unretained [_Nullable])objects andKeys:(KeyType _Nonnull __unsafe_unretained [_Nullable])keys  NS_SWIFT_UNAVAILABLE("Use 'allKeys' and/or 'allValues' instead")  API_DEPRECATED("Use -getObjects:andKeys:count: instead", macos(10.0, 10.13), ios(2.0, 11.0), watchos(2.0, 4.0), tvos(9.0, 11.0));

/* These methods are deprecated, and will be marked with API_DEPRECATED in a subsequent release. Use the variants that use errors instead. */
+ (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfFile:(NSString *)path API_DEPRECATED_WITH_REPLACEMENT("dictionaryWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));
+ (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfURL:(NSURL *)url API_DEPRECATED_WITH_REPLACEMENT("dictionaryWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));
- (nullable NSDictionary<KeyType, ObjectType> *)initWithContentsOfFile:(NSString *)path API_DEPRECATED_WITH_REPLACEMENT("initWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));
- (nullable NSDictionary<KeyType, ObjectType> *)initWithContentsOfURL:(NSURL *)url API_DEPRECATED_WITH_REPLACEMENT("initWithContentsOfURL:error:", macos(10.0, API_TO_BE_DEPRECATED), ios(2.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED));

and the Mutable version does not have anything:

@interface NSMutableDictionary<KeyType, ObjectType> (NSMutableDictionaryCreation)

+ (instancetype)dictionaryWithCapacity:(NSUInteger)numItems;

+ (nullable NSMutableDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfFile:(NSString *)path;
+ (nullable NSMutableDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfURL:(NSURL *)url;
- (nullable NSMutableDictionary<KeyType, ObjectType> *)initWithContentsOfFile:(NSString *)path;
- (nullable NSMutableDictionary<KeyType, ObjectType> *)initWithContentsOfURL:(NSURL *)url;

@end

However, there is inheritance there.

We'll have to figure out how we want to proceed, if any (deprecate them now or wait for apple).