Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling NSException.raise() in Swift

Tags:

swift

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.

like image 508
Mark Horgan Avatar asked Aug 07 '14 14:08

Mark Horgan


3 Answers

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"]))
like image 72
Mark Horgan Avatar answered Oct 15 '22 04:10

Mark Horgan


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()
like image 21
zaph Avatar answered Oct 15 '22 04:10

zaph


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)
}
like image 43
newacct Avatar answered Oct 15 '22 04:10

newacct