Improve Swift APIs for NSExpression and NSIncrementalStore


(Andrew Tetlaw) #1

Apologies if this has been discussed, I couldn't find any mention in the
list or on https://github.com/apple/swift-3-api-guidelines-review.

The NSIncrementalStore method:

    func referenceObjectForObjectID(_ objectID: NSManagedObjectID) ->
AnyObject

Can thrown an exception, but it's not marked as throws. From the
documentation:

"This method raises an NSInvalidArgumentException if the object ID was not
created by the receiving store."

NSExpression has a constantValue property:

    var constantValue: AnyObject { get }

that can raise an exception at runtime, but it's not marked as throws. From
the documentation:

'Accessing this property raises an exception if it is not applicable to the
expression."

The NSExpression constantValue property can also return nil at runtime, but
it's return type is AnyObject; not an optional.

If you construct an NSComparisonPredicate with a format string like:

    "property == nil"

and you examine the expression returned by predicate.rightExpression. You
can't call expression.constantValue from Swift because it'll crash with
EXC_BAD_ACCESS. You also can't protect against a nil value because the
return type is not optional.

To protect Swift against both of these situations, you have to use
Objective-C wrapper functions. For example to protect against exceptions:

NSException *_Nullable IADCatchObjCException(void (^_Nullable block)())
{
    NSException *exception = nil;
    @try {
        if (block) {
            block();
        }
    } @catch (NSException *e) {
        exception = e;
    }
    return exception;
}

To protect against a surprise nil at runtime:

inline _Nullable id IADMaybeNilValue(_Nullable id value)
{
    return value ?: nil;
}

I was hoping this could be improved for the Swift API?

···

--
Andrew Tetlaw


(Jordan Rose) #2

[swift-evolution to bcc]

Hi, Andrew. Objective-C exceptions are not the same as Swift errors (the things that are thrown); they have effectively come to indicate fatal conditions in Apple’s frameworks, and neither Objective-C nor Swift are compiled exception-safe in the C++ sense.* (That is, if an Objective-C exception is raised, the program will leak, and some objects may be in an invalid state.)

Swift errors correspond to the Cocoa NSError idiom, and the latter is imported as such.

Separately, issues with Cocoa APIs should be filed as bug reports with Apple, rather than raised on the swift.org mailing lists or bug tracker. The Swift Open Source project does not control Apple’s APIs, and the features which do affect particular frameworks (the SDK “overlays” and “API notes”) come from the particular framework teams within Apple.

Best,
Jordan

* Objective-C can be compiled in an exception-safe mode, but it’s off by default. I don’t know if Apple’s frameworks are compiled in this mode.

···

On Jul 11, 2016, at 20:07, Andrew Tetlaw via swift-evolution <swift-evolution@swift.org> wrote:

Apologies if this has been discussed, I couldn't find any mention in the list or on https://github.com/apple/swift-3-api-guidelines-review.

The NSIncrementalStore method:

    func referenceObjectForObjectID(_ objectID: NSManagedObjectID) -> AnyObject

Can thrown an exception, but it's not marked as throws. From the documentation:

"This method raises an NSInvalidArgumentException if the object ID was not created by the receiving store."

NSExpression has a constantValue property:

    var constantValue: AnyObject { get }

that can raise an exception at runtime, but it's not marked as throws. From the documentation:

'Accessing this property raises an exception if it is not applicable to the expression."

The NSExpression constantValue property can also return nil at runtime, but it's return type is AnyObject; not an optional.

If you construct an NSComparisonPredicate with a format string like:

    "property == nil"

and you examine the expression returned by predicate.rightExpression. You can't call expression.constantValue from Swift because it'll crash with EXC_BAD_ACCESS. You also can't protect against a nil value because the return type is not optional.

To protect Swift against both of these situations, you have to use Objective-C wrapper functions. For example to protect against exceptions:

NSException *_Nullable IADCatchObjCException(void (^_Nullable block)())
{
    NSException *exception = nil;
    @try {
        if (block) {
            block();
        }
    } @catch (NSException *e) {
        exception = e;
    }
    return exception;
}

To protect against a surprise nil at runtime:

inline _Nullable id IADMaybeNilValue(_Nullable id value)
{
    return value ?: nil;
}

I was hoping this could be improved for the Swift API?

--
Andrew Tetlaw
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution