Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is Foundation._GenericObjCError.NilError?

I'm calling some old Objective-C code from Swift, and it will often throw this error, even if it seems nothing went wrong:

do {
    try objCObject.someMethod()
}
catch {
    print(error)
    // Trying to handle the error here
}

Where that method's Objective-C signature is like this:

- (BOOL) someMethodWithError: (NSError **) outError;

Putting a breakpoint inside that catch I can see this by using the LLDB console.

(lldb) po error
Foundation._GenericObjCError.nilError

(lldb) po error as NSError
Error Domain=Foundation._GenericObjCError Code=0 "(null)"

What is happening here and how do I handle this? When I try to write a special case for this in Swift, I get this:

/Path/To/My Code.swift:200:27: error: module 'Foundation' has no member named '_GenericObjCError'
                    catch Foundation._GenericObjCError.nilError {
                          ^~~~~~~~~~ ~~~~~~~~~~~~~~~~~
like image 382
Ky. Avatar asked Apr 11 '19 20:04

Ky.


1 Answers

This is caused when an Objective-C method uses the standard Cocoa approach to error throwing: take an NSError ** as the last parameter and return a BOOL with YES indicating success. If this works as intended, the return value will only ever be NO if an error has occurred, and will then set the NSError ** object accordingly.

Swift expects this to be how all Objective-C methods with this signature work.

What you're seeing is what happens when one of these methods misbehaves for some reason and returns NO without setting the NSError ** parameter to anything (or, explicitly setting it to nil).

This could be due to a number of factors, such as returning an error code that is implicitly cast to the BOOL (so, the 0 success code is translated to the NO failure code), or writing its return line in such a way that its logic doesn't always return YES on success, or because there actually was an error but the author didn't know what to set the NSError ** to, etc.

As for dealing with this, here is what I would do:

If you don't know the author's intent, or the author documented that this indicates a success state

In this case, I think it's safest to assume the author simply made an error and returned the wrong value. Best to ignore the thrown error entirely.

do {
    try objCObject.someMethod()
}
catch {
    let nsError = (error as NSError)
    if nsError.code == 0,
        nsError.domain == "Foundation._GenericObjCError" {
        print("Got invalid error from Objective-C")
    }
    else {
        // Actually handle your error here
    }
}

If the author documented that this indicates an error state

In this case, treat it as the error that the author documented. You may tweak the above example code to treat this error specially if desired.

If you are the author

This is easy. Simply change your method so that it only ever returns NO when you are in a real error state, and always make sure that the NSError ** object has been set to a valid error object if the caller requested that.

- (BOOL) someMethodWithError: (NSError **) outError {
    [self.something attempt];
    if (!self.something.succeeded) {
        if (nil != outError) {
            *outError = [self makeSomeDescriptiveErrorFromSomething: self.something];
        }
        return NO;
    }
    else {
        return YES;
    }
}
like image 139
Ky. Avatar answered Oct 12 '22 02:10

Ky.