4

I have an objective c library that can be used in Swift using the bridging header.

One of my public methods is annotated as returning nonnull but in fact it can return nil in some cases.

I was expecting that a Swift code calling this method would crash because "unwrapping an nil optional value" but in fact it doesn't.

Objective-C:

- (nonnull UserService *)users
{
    if (!_users && [self checkStarted]) {
        _users = [[UserService alloc] init];
    }
    return _users;
}

-(BOOL) checkStarted
{
    return NO;
}

Swift Header:

The generate header looks like this:

func users() -> UserService

Swift Usage:

let userService = sdk.users()

When I po the returned value, I get this result

po userService
<uninitialized>

How come?

Jan
  • 7,444
  • 9
  • 50
  • 74
  • What version of Swift? – par Oct 17 '16 at 17:32
  • I am returning nil (updated original question to make it more clear). Swift 3 – Jan Oct 17 '16 at 17:34
  • If `userService` can be nil, simply write `let userService : UserService? = sdk.users()`. Now if it's `nil` you can find that out coherently. – matt Oct 17 '16 at 17:42
  • 1
    If the Objective-C method can return `nil`, why is it flagged as `nonnull` instead of `nullable`? – rmaddy Oct 17 '16 at 17:45
  • @rmaddy Because it's a bad API? Even Apple's own APIs make this mistake in a couple of notorious places. It's just something you have to work around. – matt Oct 17 '16 at 17:48
  • Well that's a bug in my code that it can return nil. It should not. What I am trying to understand here is why it does not crash – Jan Oct 17 '16 at 17:48
  • 2
    My *guess* would be that the behaviour is undefined. Did you try `print(userService)` or even access one of its properties? – Martin R Oct 17 '16 at 18:22
  • yes, I called a method on it and nothing happend. As if it was a nil object in Objc – Jan Oct 17 '16 at 18:23
  • 1
    BTW the Clang analyzer should be catching this sort of mistake for you. – matt Oct 17 '16 at 18:28
  • If it helps at all, I wouldn't expect "unwrapping an nil optional value" on account of the fact that you never unwrap an optional value. I'll bet Martin R is right about undefined behaviour, with the current implementation just happening to give you a result that doesn't crash because it dispatches via the Objective-C runtime in this case. – Tommy Oct 17 '16 at 20:10
  • The clang analyzer does not seem to catch that – Jan Oct 18 '16 at 07:45

1 Answers1

0

Maybe there is a misunderstanding: In many programming languages you can make many mistakes like annotating a value constraint and then not fulfilling it. I've read the C standard dozens of times, that contains many of such mistakes. The result is usually that you get a undefined behavior. It is not that the result is a definite crash. I have never read such a "promise". And it makes no sense.

So, simply said: The behavior is undefined. Undefined behavior includes the possibility that nothing crashes.

How can that happen?

Annotating non-null gives the compiler the oppertunity to make code better in some situations in others not. There might be an advantage the compiler can take through optimization. But of course there are situations in which the compiler cannot do so.

Since you get an Objective-C object, every message to that object has to be dispatched at run time. There is no way to do that at compile time (at least the methods can be replaced, methods can be overwritten in subclasses and so on) even you use that object in Swift. Dynamic Dispatching is the way Objective-C works, even if you put it in a Sift context.

So the Swift call becomes an Objective-C message. Objective-C messages are delivered by the Objective-C runtime and this can handle messages to nil. So there is no problem.

If something else happens with the object, you may get a crash. I. e. if the generated code tries to access the isa pointer, you may get a null pointer exception.

Maybe. Maybe not. This is undefined.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50