@objc Optional Int

Hi,

I have a computed property that is of the type Int? that I want to expose to Objective-C

Problem

  • It throws the compilation error Property cannot be marked @objc because its type cannot be represented in Objective-C

Workaround

  • I have managed to use NSNumber

Question

  • Is there a better way (better than NSNumber) to expose a Optional Int to Objective-C?

Code

class Car {
    // Property cannot be marked @objc because its type cannot be represented in Objective-C
    @objc var price: Int? {
        10
    }
}

Other than using NSNumber or a similar class, the only way I can think of is to define a struct in Objective-C:

typedef struct optional_int {
  NSInteger value;
  BOOL has_value;
} optional_int;

and then writing some conversions in Swift:

extension optional_int {
    init(_ optional: Int?) {
        if let optional {
            self.value = optional
            self.has_value = true
        } else {
            self.value = 0 // or whatever
            self.has_value = false
        }
    }

    var asOptional: Int? {
        has_value ? value : nil
    }
}
1 Like

The other thing I can think of that’s common is to return an invalid value. For a price, all valid values are positive, so you could use -1 to indicate that the price is unknown or absent, for example. This is probably the most idiomatic approach of the three.

Thanks @bbrk24

I suppose we need to use an Objective-C type.

For now I have used an Optional NSNumber and since it is used for UserDefaults and checking for the existence of a value.

I think it just highlights how awesome swift's optional is only when it is not available in Objective-C.

What is very strange is that String Optional is somehow allowed in @objc. Not sure if it is because of a bridging to NSString

You can export to ObjC a different entity under the same name:

class Car {
    var price: Int? { 10 }
    
    @objc(price)
    var __price: NSNumber? { price.map { NSNumber(value: $0) } }
}
10 Likes

Thanks @Nickolas_Pohilets, please bear with me as I try to understand.

Aren't price and __price different variables?

Currently I am using number for @objc and another computed variable for swift type. Is that similar to your approach or does "__" have special meaning?

Yes, they are different variables. But only one of the provides storage and/or business logic. Another one is a bridge for the different language.

Double underscore does not have any special meaning, just a convention I often use, trying to follow behaviour of the NS_REFINED_FOR_SWIFT attribute. Also, when showing .swiftinterface, Xcode hides members with names starting from underscore. IIRC, this also excludes it from the autocompletion. So it makes __price kinda hidden in Swift.

But in Objective-C, things are other way around. price is not exported to ObjC at all, because it is not @objc. And __price is exported as NSNumber *price. So both languages use identifier price and get API suitable for the language.

If you need to bridge in the opposite direction - from Objective-C to Swift, then you can declare Objective-C declaration using NS_REFINED_FOR_SWIFT and then implement extension in Swift that provides more Swift-friendly wrappers:

@interface Car: NSObject
// Gets exported to Swift as `var __price: NSNumber?`
@property(nonatomic, readonly, nullable) NSNumber *price NS_REFINED_FOR_SWIFT;
@end
extension Car {
    var price: Int? { __price.map(\.intValue) }
}
5 Likes

@Nickolas_Pohilets Thank you so much for the detailed explanation, very helpful!