Convincing Swift That a Function Will Never Return, Due to a Thrown Exception

Convincing Swift that a function will never return, due to a thrown Exception

Swift's @noreturn attribute marks functions and methods as not returning to their caller.

As probably the simplest example, the signature of the built-in function abort()'s is:

@noreturn func abort()

This gives the compiler all the information it needs. For example, the following will compile just fine:

func alwaysFail() -> Int {
abort()
}

Although alwaysFail() theoretically returns an Int, Swift knows that execution can't continue after abort() is called.

The reason my original code didn't work is because NSException.raise is a pre-Swift method, and therefore doesn't have the @noreturn attribute. To easily solve this, I can either use abort():

func shouldBeOverridden() -> ReturnType {
println("Subclass has not implemented abstract method `shouldBeOverridden`!")
abort()
}

or, if I still want to use NSException, I can define an extension with the proper attribute

extension NSException {
@noreturn func noReturnRaise() {
self.raise()
abort() // This will never run, but Swift will complain that the function isn't really @noreturn if I don't call a @noreturn function before execution finishes.
}
}

As a third option, I can just use a never-called abort() after an NSException.raise() to placate the compiler. The earlier option, using an extension, is really just an abstraction for doing this:

func shouldBeOverridden() -> ReturnType {
let exception = NSException(
name: "Not implemented!",
reason: "A concrete subclass did not provide its own implementation of shouldBeOverridden()",
userInfo: nil
)
exception.raise()
abort() // never called
}

When and how to use @noreturn attribute in Swift?

First of all, any function returns something (an empty tuple at least) even if you don't declare any return type.

(@noreturn is obsolete; see Swift 3 Update below.)
No, there are functions which terminate the process immediately
and do not return to the caller. These are marked in Swift
with @noreturn, such as

@noreturn public func fatalError(@autoclosure message: () -> String = default, file: StaticString = #file, line: UInt = #line)
@noreturn public func preconditionFailure(@autoclosure message: () -> String = default, file: StaticString = #file, line: UInt = #line)
@noreturn public func abort()
@noreturn public func exit(_: Int32)

and there may be more.

(Remark: Similar annotations exist in other programming languages
or compilers, such as [[noreturn]] in C++11, __attribute__((noreturn)) as a GCC extension, or _Noreturn for the
Clang compiler.)

You can mark your own function with @noreturn if it also terminates
the process unconditionally, e.g. by calling one of the built-in functions, such as

@noreturn func myFatalError() {
// Do something else and then ...
fatalError("Something went wrong!")
}

Now you can use your function in the else clause of a guard statement:

guard let n = Int("1234") else { myFatalError() }

@noreturn functions can also be used to mark cases that "should not
occur" and indicate a programming error. A simple example
(an extract from Missing return UITableViewCell):

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyTableViewCell

switch (indexPath.row) {
case 0:
cell = tableView.dequeueReusableCellWithIdentifier("cell0", forIndexPath: indexPath) as! MyTableViewCell
cell.backgroundColor = UIColor.greenColor()
case 1:
cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! MyTableViewCell
cell.backgroundColor = UIColor.redColor()
default:
myFatalError()
}
// Setup other cell properties ...
return cell
}

Without myFatalError() marked as @noreturn, the compiler would
complain about a missing return in the default case.


Update: In Swift 3 (Xcode 8 beta 6) the @noreturn attribute
has been replaced by a Never return type, so the above example
would now be written as

func myFatalError() -> Never  {
// Do something else and then ...
fatalError("Something went wrong!")
}

Swift do-try-catch syntax

There are two important points to the Swift 2 error handling model: exhaustiveness and resiliency. Together, they boil down to your do/catch statement needing to catch every possible error, not just the ones you know you can throw.

Notice that you don't declare what types of errors a function can throw, only whether it throws at all. It's a zero-one-infinity sort of problem: as someone defining a function for others (including your future self) to use, you don't want to have to make every client of your function adapt to every change in the implementation of your function, including what errors it can throw. You want code that calls your function to be resilient to such change.

Because your function can't say what kind of errors it throws (or might throw in the future), the catch blocks that catch it errors don't know what types of errors it might throw. So, in addition to handling the error types you know about, you need to handle the ones you don't with a universal catch statement -- that way if your function changes the set of errors it throws in the future, callers will still catch its errors.

do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
print("Not me error")
} catch SandwichError.DoItYourself {
print("do it error")
} catch let error {
print(error.localizedDescription)
}

But let's not stop there. Think about this resilience idea some more. The way you've designed your sandwich, you have to describe errors in every place where you use them. That means that whenever you change the set of error cases, you have to change every place that uses them... not very fun.

The idea behind defining your own error types is to let you centralize things like that. You could define a description method for your errors:

extension SandwichError: CustomStringConvertible {
var description: String {
switch self {
case NotMe: return "Not me error"
case DoItYourself: return "Try sudo"
}
}
}

And then your error handling code can ask your error type to describe itself -- now every place where you handle errors can use the same code, and handle possible future error cases, too.

do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch let error as SandwichError {
print(error.description)
} catch {
print("i dunno")
}

This also paves the way for error types (or extensions on them) to support other ways of reporting errors -- for example, you could have an extension on your error type that knows how to present a UIAlertController for reporting the error to an iOS user.

How do I declare that a computed property 'throws' in Swift?

This functionality is added for read-only computed properties in Swift 5.5 as part of SE-0310 (included in Xcode 13).

Based on SE-0310, the syntax would be:

class SomeClass {
var someProperty: Int {
get throws {
throw Err("SNAFU")
}
}
}

Here is the previous answer for versions of Swift prior to 5.5:

You cannot throw from a computed property. You must use a function if you want to throw. The Declarations section of the Language Reference part at the end of The Swift Programming Language only lists throws (and rethrows) as a keyword for function and initializer declarations.

Intercepting crashes on iOS

former Crashlytics iOS SDK maintainer here.

The code you've written above does have a number of technical issues.

The first is there are actually very few functions that are defined as safe to invoke inside a signal handler. man sigaction lists them. The code you've written is not signal-safe and will deadlock from time to time. It all will depend on what the crashed thread is doing at the time.

The second is you are attempting to just exit the program after your handler. You have to keep in mind that signals/exception handlers are process-wide resources, and you might not be the only one using them. You have to save pre-existing handlers and then restore them after handling. Otherwise, you can negatively affect other systems the app might be using. As you've currently written this, even Apple's own crash reporter will not be invoked. But, perhaps you want this behavior.

Third, you aren't capturing all threads stacks. This is critical information for a crash report, but adds a lot of complexity.

Fourth, signals actually aren't the lowest level error system. Not to be confused with run time exceptions (ie NSException) mach exceptions are the underlying mechanism used to implement signals on iOS. They are a much more robust system, but are also far more complex. Signals have a bunch of pitfalls and limitations that mach exceptions get around.

These are just the issues that come to me off the top of my head. Crash reporting is tricky business. But, I don't want you to think it's magic, of course it's not. You can build a system that works.

One thing I do want to point out, is that crash reporters give you no feedback on failure. So, you might build something that works 25% of the time, and because you are only seeing valid reports, you think "hey, this works great!". Crashlytics had to put in effort over many years to identify the causes of failure and try to mitigate them. If this is all interesting to you, you can check out a talk I did about the Crashlytics system.

Update:

So, what would happen if you ship this code? Well, sometimes you'll get useful reports. Sometimes, your crash handling code will itself crash, which will cause an infinite loop. Sometimes your code will deadlock, and effectively hang your app.

Apple has made exit public API (for better or worse), so you are absolutely within the rules to use it.

I would recommend continuing down this road for learning purposes only. If you have a real app that you care about, I think it would be more responsible to integrate an existing open-source reporting system and point it to a backend server that you control. No 3rd parties, but also no need to worry about doing more harm than good.

Why I always get NO when performSelector:withObject:@YES in iOS, which is different in macOS?

-[NSObject performSelector:withObject:] is only supposed to be used with a method that takes exactly one object-pointer parameter, and returns an object-pointer. Your method takes a BOOL parameter, not an object-pointer parameter, so it cannot be used with -[NSObject performSelector:withObject:].

If you are always going to send the message handler: and you know the method has a BOOL parameter, you should just call it directly:

[self handler:YES];

If the name of the method will be determined dynamically at runtime, but you know the signature of the method will always be the same (in this case exactly one parameter of type BOOL, returning nothing), you can call it with objc_msgSend(). You must cast objc_msgSend to the appropriate function type for the underlying implementing function of the method before calling it (remember, the implementing function for Objective-C methods have the first two parameters being self and _cmd, followed by the declared parameters). This is because objc_msgSend is a trampoline that calls into the appropriate implementing function with all the registers and stack used for storing the arguments intact, so you must call it with the calling convention for the implementing function. In your case, you would do:

SEL selector = @selector(handler:); // assume this is computed dynamically at runtime
((void (*)(id, SEL, BOOL))objc_msgSend)(self, selector, YES);

By the way, if you look at the source code for -[NSObject performSelector:withObject:], you will see that they do the same thing -- they know that the signature of the method must be one parameter of type id and a return type of id, so they cast objc_msgSend to id (*)(id, SEL, id) and then call it.

In the rare case where the signature of the method will also vary dynamically and is not known at compile-time, then you will have to use NSInvocation.

Let's consider what happened in your case when you used -[NSObject performSelector:withObject:] with a method of the wrong signature. Inside, they call objc_msgSend(), which is equivalent to calling the underlying implementing function, with a function pointer of the type id (*)(id, SEL, id). However, the implementing function of your method actually has type void (id, SEL, BOOL). So you are calling a function using a function pointer of a different type. According to the C standard (C99 standard, section 6.5.2.2, paragraph 9):

If the function is defined with a type that is not compatible with the
type (of the expression) pointed to by the expression that denotes the
called function, the behavior is undefined.

So basically what you are seeing is undefined behavior. Undefined behavior means anything can happen. It could return one thing on one system and another thing on another, as you're seeing, or it could crash the whole program. You can't rely on any specific behavior.



Related Topics



Leave a reply



Submit