I'm trying to raise an exception in Swift by calling NSException.raise(). The definition is:
class func raise(_ name: String!, format format: String!, arguments argList: CVaListPointer)
But when I try something like the following:
NSException.raise("Exception", format:"Error: %@", arguments:getVaList([error]))
I get the compile error: Extra argument 'format' in call.
Any ideas what I'm doing wrong? I'm using XCode 6 Beta 5.
The problem seems to have been that I didn't treat the error as an optional. The following works:
var error: NSError?
NSException.raise("Exception", format:"Error: %@", arguments:getVaList([error!]))
Or you could do the following in case error is nil:
NSException.raise("Exception", format:"Error: %@", arguments:getVaList([error ?? "nil"]))
var e = NSException(name:"name", reason:"reason", userInfo:["key":"value"])
e.raise()
Or:
var e = NSException(name:"name", reason:"reason", userInfo:nil)
e.raise()
Or:
NSException(name:"name", reason:"reason", userInfo:nil).raise()
As the questioner discovered, the issue is that an optional object does not conform to CVarArgType
, and a non-optional object does, so you need to do some kind of unwrapping.
If we look at the types that conform to CVarArgType
, they include primitive numeric types, COpaquePointer
, and NSObject
has an extension that makes NSObject
conform to it. Unfortunately, that's a direct NSObject
, not NSObject?
, and not AnyObject?
as we might want. So if you have an NSObject?
, you could unwrap it with !
, but then if it's nil
, it'll crash. Or if you have an object that is not a subtype of NSObject
, then you also cannot use this.
But in C/Objective-C, you could pass any pointer type as a vararg, including any object pointer, and it worked no matter if it was a null pointer or not. How do we replicate this behavior in Swift?
By the way, they couldn't make AnyObject?
(i.e. Optional<AnyObject>
) conform to CVarArgType
, because you cannot define a method (whether in the class directly, or an extension) for a specific type argument of a generic type (like "template specialization" in C++). Perhaps Apple will add it in the future. But right now a method of Optional
must work for all type arguments. But we don't want to be able to pass non-object optionals as var args, so that doesn't work.
However, we can make a function that converts optional objects to CVarArgType
. We can take advantage of NSObject
's conformance, and handle the case it doesn't handle -- nil
. nil
is just a null pointer, and we can instead pass a different kind of null pointer, a COpaquePointer
null pointer, and it will work since all null pointers are the same at runtime:
import Foundation
func nsObjectToVarArg(obj: NSObject?) -> CVarArgType {
return obj ?? (nil as COpaquePointer)
}
What about for objects that are not (or we don't know are) subtypes of NSObject
? I made a version that is however more sketchy:
func anyObjectToVarArg(obj: AnyObject?) -> CVarArgType {
return (obj != nil) ?
Unmanaged<AnyObject>.passUnretained(obj!).toOpaque() :
(nil as COpaquePointer)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With