init an NSNumber from enum element in mixed-language project - objective-c

I have a mixed-language project, Objective C and Swift, in XCode 6.
Singleton.h
#import <Foundation/Foundation.h>
enum {
enum_A = 0,
enum_B,
enum_C,
enum_D,
enum_E,
enum_F,
enum_G,
} enums;
#interface Singleton : NSObject
+ (id)sharedSingleton;
#end
Singleton.m
// Nothing's special in this file
#import "Singleton.h"
static Singleton *shared = nil;
#implementation Singleton
- (id)init {
self = [super init];
if (self) {
}
return self;
}
#pragma mark - Interface
+ (Singleton *)sharedSingleton {
static dispatch_once_t pred;
dispatch_once(&pred, ^{
shared = [[Singleton alloc] init];
});
return shared;
}
#end
ViewController.swift
import UIKit
class ViewController: UIViewController {
let singleton = Singleton.sharedSingleton() as Singleton
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let n = NSNumber(char: enum_E) // ERROR HERE!!!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
(Of course I had to setup bridging header file, having #import "Singleton.h" added).
The ERROR is:
Cannot invoke 'init' with an argument list of type '(char: Int)'
It's strange that Swift can still recognize enum_E (I see it colorized in blue) but still pops up this error.
I tried (char)enum_E but still no luck.
Do you have any ideas?
Thanks,

Okay, apparently there actually is a difference between enums created in Objective-C and Swift. I assumed there was no difference, thus I only tested my approach in a Swift Playground.
enum created in Swift
// UInt32 used to have the same underlying type in both examples
enum TestEnum : UInt32 {
case A, B, C
}
var x = NSNumber(unsignedInt: TestEnum.C.rawValue)
// x == 2
To get the raw value from an enum value in Swift, you have to explicitly transform the enum value into the raw value. This can be done by adding .rawValue to your enum value.
enum created in Objective-C
Objective-C
enum TestEnum {
A = 0,
B = 1,
C = 2
};
Swift
let x : TestEnum = C
var number = NSNumber(unsignedInt: C.value) // alternative: x.value
println("the number is \(number)")
// Outputs: the number is 2
The difference to the Swift enums seems to be that you have to use .value instead of .rawValue and you can not prefix them with the type. The raw type in this case is UInt32.
Tested in Xcode 6.1.1, iOS SDK 8.1, iOS Simulator 8.1

Related

Exposing a Swift variable in Objective-C

I am using a Swift class which uses a variable sharedInstance. When I am trying to use this variable in my Objective-C file, I am getting the error:
No known class method for selector 'sharedInstance'
My Swift class:
class SpeakToMeForUnity : NSObject, SFSpeechRecognizerDelegate {
fileprivate var speechRecognizer : SFSpeechRecognizer?
fileprivate var recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
fileprivate var recognitionTask : SFSpeechRecognitionTask?
fileprivate var audioEngine : AVAudioEngine?
static let sharedInstance = SpeakToMeForUnity()
override fileprivate init() {
super.init()
self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
self.audioEngine = AVAudioEngine()
self.speechRecognizer?.delegate = self
}
// Some other functions
}
Below is my Objective-C file:
#import <Speech/Speech.h>
#import "SpeakToMeForUnity-Swift.h"
extern "C"
{
void _prepareRecording()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance prepareRecording];
}
void _recordButtonTapped()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance recordButtonTapped];
}
}
I am using Swift 4 and Xcode 10.3.
Normally to make Swift available in Objective-C you just need to import your swift module #import "ProductModuleName-Swift.h", as you're doing.
If you need the swift code in the Objective-C header use #class MySwiftClass; or #protocol MySwiftClass;.
You can force some class or property to be accessible in Objective-C by adding the attribute #objc.
Remember that private or fileprivate access level attributes will not be accessible unless you add the previous attribute.
To expose things to ObjC, you need to mark them #objc. In this case you should add this before class and before the static let.
In previous versions of Swift, inheritance from NSObject automatically implied #objc, but that was removed, and it now needs to be explicit.

public var of an Objective C enum type inside a Swift class not accessible from another Objective C class [duplicate]

I am trying to access a Swift class's Double? property from Objective-C.
class BusinessDetailViewController: UIViewController {
var lat : Double?
var lon : Double?
// Other elements...
}
In another view controller, I am trying to access lat like following:
#import "i5km-Swift.h"
#interface ViewController ()
#property (strong, nonatomic) BusinessDetailViewController *businessDetailViewController;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.businessDetailViewController = [[BusinessDetailViewController alloc] initWithNibName:#"BusinessDetailViewController" bundle:nil];
self.businessDetailViewController.lat = businessArray[1]; /* THIS GIVES ME AN ERROR */
}
and I am getting
Property 'lat' not found on object of type 'BusinessDetailViewController *'
Why can't I access this property? What am I missing?
Optional values of non-Objective-C types aren't bridged into Objective-C. That is, the first three properties of TestClass below would be available in Objective-C, but the fourth wouldn't:
class TestClass: NSObject {
var nsNumberVar: NSNumber = 0 // obj-c type, ok
var nsNumberOpt: NSNumber? // optional obj-c type, ok
var doubleVar: Double = 0 // bridged Swift-native type, ok
var doubleOpt: Double? // not bridged, inaccessible
}
In your Objective-C code, you'd access those first three properties like this:
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = #1.0;
optTest.nsNumberVar = #2.0;
optTest.doubleVar = 3.0;
In your case, you can either convert lat and long to be non-Optional or switch them to be instances of NSNumber.
Note that you need to be careful about your Objective-C code if you take the second approach (switching lat and lon to non-optional properties of type NSNumber) -- while the Swift compiler will prevent you from assigning nil to non-optional properties, the Objective-C compiler has no qualms about allowing it, letting nil values sneak into your Swift code with no chance of catching them at runtime. Consider this method on TestClass:
extension TestClass {
func badIdea() {
// print the string value if it exists, or 'nil' otherwise
println(nsNumberOpt?.stringValue ?? "nil")
// non-optional: must have a value, right?
println(nsNumberVar.stringValue)
}
}
This works fine if invoked with values in both of the properties, but if nsNumberVar is set to nil from the Objective-C code, this will crash at runtime. Note that there is no way to check whether or not nsNumberVar is nil before using it!
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = #1.0;
optTest.nsNumberVar = #2.0;
[optTest badIdea];
// prints 1, 2
optTest.nsNumberOpt = nil;
optTest.nsNumberVar = nil;
[optTest badIdea];
// prints nil, then crashes with an EXC_BAD_ACCESS exception
If your property is a Swift protocol type, just add #objc in front of it.
Example:
class Foo: UIViewController {
var delegate: FooDelegate?
...
}
#objc protocol FooDelegate {
func bar()
}
Optionals is a swift specific feature, not available in obj-c. Optional class instances work because a nil optional can be mapped to a nil value, but value types (int, floats, etc.) are not reference types, hence variable of those types don't store a reference, but the value itself.
I don't know if there's a solution - a possible workaround is creating non optional properties mapping the nil value to an unused data type value (such as -1 when representing an index, or 999999 for a coordinate):
class Test {
var lat : Double? {
didSet {
self._lat = self.lat != nil ? self.lat! : 999999
}
}
var lon : Double? {
didSet {
self._lon = self.lon != nil ? self.lon! : 999999
}
}
var _lat: Double = 99999999
var _lon: Double = 99999999
}
That should expose the _lat and _lon properties to obj-c.
Note that I have never tried that, so please let us know if it works.
[UInt? Int? or Double? properties] cannot be marked #objc because its type cannot be represented in Objective-C.
It is, however, possible to "wrap" them in a NSNumber like so :
class Foo {
var bar: Double?
}
// MARK: Objective-C Support
extension Foo {
/// bar is `Double?` in Swift and `(NSNumber * _Nullable)` in Objective-C
#objc(bar)
var z_objc_bar: NSNumber? {
get {
return bar as NSNumber?
}
set(value) {
bar = value?.doubleValue ?? nil
}
}
}

Subclassing Objective-C class with factory method in Swift, the generics doesn't work

I have a Base class with a factory method written in Objective-C.(some lib)
#interface Base : NSObject
#property(nonatomic, strong) NSString *content;
+ (instancetype)baseWithContent:(NSString *)content;
#end
//==================
#implementation Base
+ (instancetype)baseWithContent:(NSString *)content {
Base* base = [[Base alloc]init];
base.content = content;
return base;
}
#end
Then I subclassing it in swift and casting it into AnyObject.(ignore the Bridging-Header part)
class Child: Base {}
var c = Child(content: "Child")
print("before casting", type(of:c))
print("after casting", type(of:c as AnyObject))
Get this strange result that it become a Base after casting.
before casting Optional<Child>
after casting Base
Actually if i use a designated initializer to override the generated-convenience initializer from objective-c, I get the right result.
class Child: Base {
init?(content:String) {
super.init()
self.content = content
}
}
before casting Optional<Child>
after casting Child
Am I making any mistake? Thanks for answering.
I'm using Xcode Version 8.1 (8B62) and Swift 3.0.1
Your factory method implementation in Obj-C is wrong. It always creates an instance of Base. To fix it:
+ (instancetype)baseWithContent:(NSString *)content {
Base *base = [[self alloc] init]; //self instead of Base
base.content = content;
return base;
}
Basically, your factory method implementation doesn't match its return type, the result are type problems in Swift because Swift trusts type declarations.

How do you call a singleton class method from Objective C in a Swift class?

I have the following objective C code which I need to get into a Swift class :
In Logger.m -
+ (Logger *) log
{
static Logger *sharedLog = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedLogger = [[self alloc] init];
});
return sharedLogger;
}
- (void) printString:(NSString *) s
{
NSLog(s)
}
Which is in Logger.h as -
+ (Logger *) log;
- (void) printString:(NSString *) s;
Now, I have this code bridged in to Swift Project - LoggerUserApp where I'm trying to use the above print method from the singleton log shared class method.
I've tried -
Logger.log().printString("String") // Compiler Error. Use Object Construction Logger()
Logger().log().printString("String") // Compiler Error.
Logger().log.printString("String") // Compiler Error.
Logger.printString("String") // This does not call log
Can someone tell me what might be going wrong?
If the Swift compiler mistakenly identifies a method as a class factory method, you can use the NS_SWIFT_NAME macro, passing the Swift signature of the method to have it imported correctly. For example:
+ (id)recordWithQuality:(double)quality NS_SWIFT_NAME(record(quality:));
so,your method should be this:
+ (Logger *) log NS_SWIFT_NAME(log());
I can't reproduce your example completely, but at first sight, this should work:
Logger.log().printString("String")
Since your Obj-C singleton is a function returning the singleton (hence Logger.log() will return your singleton.
But since in your example code I see Logger and then PALog I can't say if this is all you need.
There are some other errors in your example: sharedLog and than sharedLogger.
Here is an example that should work:
Logger.h
#interface Logger : NSObject
+ (Logger *) log;
- (void) printString:(NSString *) s;
#end
Logger.m
#import "Logger.h"
#implementation Logger
+ (Logger *) log
{
static Logger *sharedLog = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedLog = [[self alloc] init];
});
return sharedLog;
}
- (void) printString:(NSString *) s
{
NSLog(#"%#", s);
}
#end
...-Bridging-Header.h
#import "Logger.h"
ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Logger.log().printString("Hello")
}
}
In the Swift update, if the class method's name is similar to the class name, then it just takes that as a custom initializer. Now since there was no parameter in the method, it wouldn't come up as the default initializer would come up instead.
Hence, to solve this problem, I had to change the name of the class method log to something different like newLog... and it worked fine.
Logger.newLog().printString("")

Extract notification userinfo in a mixed language project

I am working on a mixed language project, combining both Objective C and Swift in XCode 6.
In this project, the Singleton (Objective C) class posts a notification which is then received by ViewController (Swift).
Singleton.h
#import <Foundation/Foundation.h>
NSString *const notificationString = #"notificationString";
#interface Singleton : NSObject
+ (id)sharedSingleton;
- (void)post;
#end
Singleton.m
#import "Singleton.h"
static Singleton *shared = nil;
#implementation Singleton
- (id)init {
self = [super init];
if (self) {
}
return self;
}
#pragma mark - Interface
+ (Singleton *)sharedSingleton {
static dispatch_once_t pred;
dispatch_once(&pred, ^{
shared = [[Singleton alloc] init];
});
return shared;
}
- (void)post {
char bytes[5] = {5, 7, 9, 1, 3};
NSDictionary *objects = #{#"device":[NSData dataWithBytes:bytes length:5], #"step1":[NSNumber numberWithInt:4], #"step2":[NSNumber numberWithInt:7]};
[[NSNotificationCenter defaultCenter] postNotificationName:notificationString
object:self
userInfo:objects];
}
#end
Of course, in this mixed-language project, bridging header must be setup correctly (just by adding #import "Singleton.h" in it)
ViewController.swift
import UIKit
class ViewController: UIViewController {
let singleton = Singleton.sharedSingleton() as Singleton
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "action:", name: notificationString, object: nil)
singleton.post()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Notification
func action(notification: NSNotification) {
let userInfo = notification.userInfo as Dictionary<String, String> // things go wrong here?!
let hash = userInfo["device"]
let completed = userInfo["step1"]
let total = userInfo["step2"]
}
}
This makes no compilation error. However, at run time, XCode reports:
fatal error: dictionary cannot be bridged from Objective-C
notification.userInfo contains an NSDictionary built by NSSTring: NSData, NSSTring: NSNumber, NSSTring: NSNumber while this command let userInfo = notification.userInfo as Dictionary<String, String> is trying to convert to Dictionary<String, String>
Does this cause the fatal error?
In ViewController.swift, what should I do to "read" NSDictionary passed in notification.userInfo, sent from Singleton.m?
Thanks in advance
try doing this
let userInfo = notification.userInfo as Dictionary<String, AnyObject>
as you indicated, the userInfo dictionary contains NSData, NSNUmber for values.