Hello everyone,
I was writing a unit test in Swift for a class written in Objective-C when I came across the following behaviour: nonnull
properties of some Foundation types (e.g. NSString
or NSArray
) declared in a Objective-C class are automatically set to a default value (e.g. an empty string or an empty array) when they're bridged to Swift.
Here's a code sample demonstrating this behaviour. Given an Objective-C class:
@interface Team : NSObject
@property (nonatomic, copy, nonnull) NSString *name;
@property (nonatomic, copy, nonnull) NSArray<NSString *> *codes;
@end
@implementation Team
// Note there is no explicit initialisation here
@end
and a unit test written in Swift:
final class TeamTests: XCTestCase {
func testExample() {
let team = Team()
XCTAssertNil(team.name) // fails, name is an empty string
XCTAssertNil(team.codes) // fails, codes is en empty array
XCTAssert(team.name.isEmpty)
XCTAssert(team.codes.isEmpty)
}
}
the assertions checking for nil
always fail. That shouldn’t be surprising, as the types become String
(not String?
) and Array<String>
(not Array<String>?
) in Swift. The same assertions written in an Objective-C test succeed:
@interface TeamTests : XCTestCase
@end
@implementation TeamTests
- (void)testExample {
Team *team = [[Team alloc] init];
XCTAssertNil(team.name); // succeeds
XCTAssertNil(team.codes); // succeeds
}
@end
Admittedly this is a programmer error. Without initialising name
and codes
to proper values we are indeed breaking the contract established by nonnull
in the public interface.
And I would argue that this implicit initialisation makes sense. Without setting these properties to any value, it is not possible to implement the equivalent generated interface:
open class Team : NSObject {
open var name: String
open var codes: [String]
}
An empty string and an empty array are perfect candidates for a sensible default value when bridging.
Is this expected and/or intended behaviour or rather a detail of the bridging implementation. Moreover, is it documented anywhere?
Thank you!
PS: Whether the compiler should emit a warning when using XCTAssertNil
with a non optional type is another discussion.