23

I'm having some trouble accessing a Swift Singleton from Objective-C.

@objc class SingletonTest: NSObject {

    // swiftSharedInstance is not accessible from ObjC
    class var swiftSharedInstance: SingletonTest {
    struct Singleton {
        static let instance = SingletonTest()
        }
        return Singleton.instance
    }        
}

swiftSharedInstance can not be reached.

ekinsol
  • 501
  • 1
  • 3
  • 9

8 Answers8

26

Nicky Goethlis's answer is correct but I just want to add another way of Singleton creation termed as One line Singleton" in Swift which I came across recently and it does not use Struct:

Singleton.swift

@objc class Singleton: NSObject {

  static let _singletonInstance = Singleton()
  private override init() {
    //This prevents others from using the default '()' initializer for this class.
  }

  // the sharedInstance class method can be reached from ObjC. (From OP's answer.)
  class func sharedInstance() -> Singleton {
    return Singleton._singletonInstance
  }

  // Some testing
  func testTheSingleton() -> String {
    return "Hello World"
  }
}

SomeObjCFile.m

Singleton *singleton = [Singleton sharedInstance];
NSString *testing = [singleton testTheSingleton];
NSLog(@"Testing---> %@",testing);
Gerriet
  • 1,302
  • 14
  • 20
rohan-patel
  • 5,772
  • 5
  • 45
  • 68
  • This approach is not working for me and crashing app while accessing from ObjC file (In your answer this line : Singleton *signleton = [Singleton sharedInstance];) – Yuvrajsinh Nov 15 '16 at 08:14
  • 8
    In Swift 4.1 you don't even need the sharedInstance() method. You can just annotate your static 'singletonInstance' with @objc, and access the property directly from ObjC code. (This may have started earlier than 4.1; just toyed with it today.) – Darren Black Jun 24 '18 at 18:28
  • @DarrenBlack is correct, but needs to be marked as public – heqingbao Feb 20 '21 at 09:20
12

Swift 5 and above

final class Singleton: NSObject {

    @objc static let shared = Singleton()

    @objc var string: String = "Hello World"

    private override init() {}   
}

use in Objective-C

#import <ProjectName-Swift.h> // change ProjectName to actual project name 

NSLog("Singleton String = %@", [Singleton shared].string);
Suhit Patil
  • 11,748
  • 3
  • 50
  • 60
9

For now I have the following solution. Maybe I am overlooking something that would enable me to access "swiftSharedInstance" directly?

@objc class SingletonTest: NSObject {

    // swiftSharedInstance is not accessible from ObjC
    class var swiftSharedInstance: SingletonTest {
    struct Singleton {
        static let instance = SingletonTest()
        }
        return Singleton.instance
    }

    // the sharedInstance class method can be reached from ObjC
    class func sharedInstance() -> SingletonTest {
        return SingletonTest.swiftSharedInstance
    }

    // Some testing
    func testTheSingleton() -> String {
        return "Hello World"
    }

}

Then in ObjC I can get the sharedInstance class method (after importing the xcode generated swift header bindings)

SingletonTest *aTest = [SingletonTest sharedInstance];
NSLog(@"Singleton says: %@", [aTest testTheSingleton]);
ekinsol
  • 501
  • 1
  • 3
  • 9
  • 2
    Try adding `@objc` in front of the class variable declaration and see what errors it gives you. – Jack Jun 30 '14 at 16:25
  • the main reason u have to add method for shared instance becouse u can't use swift structure in objective-C – Shial Dec 02 '14 at 13:14
  • @Jack Annotating the class variable with `@objc` works for me too. – Darren Black Jun 24 '18 at 18:33
  • 1
    ... as in I don't need a wrapper struct or a sharedInstance() method. I can access the class / static variable directly from Objective-C this way. – Darren Black Jun 24 '18 at 19:14
2

To make members of the SingletonTest class accessible (swiftSharedInstance is a member of this class), use @objcMembers modifier on the class, or add @objc modifier directly on the swiftSharedInstance:

@objc @objcMembers class SingletonTest: NSObject {

    // swiftSharedInstance is not accessible from ObjC
    class var swiftSharedInstance: SingletonTest {
        struct Singleton {
            static let instance = SingletonTest()
        }
        return Singleton.instance
    }        
}

Or:

@objc class SingletonTest: NSObject {

    // swiftSharedInstance is not accessible from ObjC
    @objc class var swiftSharedInstance: SingletonTest {
        struct Singleton {
            static let instance = SingletonTest()
        }
        return Singleton.instance
    }        
}
Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
  • 1
    I didn't know about `@objcMembers` which did the trick! However, I'm not sure about bridging performance which could be completely impacted. – Antoine Rucquoy Jan 18 '19 at 13:55
2

After creating the Bridging header, be sure to have the Objective-C Generated Interface Header Name set in your Build Settings from your app target. If the value is empty, add the following value:

$(SWIFT_MODULE_NAME)-Swift.h

You need add @objc property wrapper to your singleton:

@objc final class Singleton: NSObject {

    @objc static let sharedInstance = Singleton()

    @objc func foo() { }
}

Then, in the Objective-C class, import the following:

// Replace the "App" with your Target name.
#import "App-Swift.h"

Finally, after compiling the project, you will be able to use your singleton from Swift inside your Objective-C class:

[[Singleton sharedInstance]foo];
pableiros
  • 14,932
  • 12
  • 99
  • 105
0

You pretty much have it. To use Swift classes in Obj-C you both need to #import "SingletonTest-Swift.h the generated header or forward declaration with @class MySwiftClass.

Additionally the class needs to inherit from an Obj-C class like you have don here with NSObject or be marked with @objc to expose it. You don't need to do both though, @objc is there to be a more granular option when choosing things to expose.

Apple has some good documentation on all of this and there are two different WWDC sessions you can watch on the topic of Obj-C interoperability as well.

macshome
  • 939
  • 6
  • 11
  • The problem with the code is that the instance does not get exposed. See my answer for a possible solution. – ekinsol Jun 30 '14 at 14:47
0

Don't forget to set sharedInstance as public

public final class TestSwiftMain: NSObject {
    @objc public static let sharedInstance = TestSwiftMain()
    private override init() {}

    @objc public func test() {
        print("testing swift framework")
    }
}

Using it in Objc

[[testSwiftMain sharedInstance] test];
Reinier Melian
  • 20,519
  • 3
  • 38
  • 55
0

Update 12 Oct 2022

ObcMember So you won't have to write objC behind every function

Swift Class

@objcMembers class SwiftHelpingExtentions: NSObject {

    static let instanceShared = SwiftHelpingExtentions()
        func testingMethod() {     
            print("testing")    
        }
    }
}

On objective C View Controller

  1. import : #import "App-Swift.h"

  2. Call The method: [SwiftHelpingExtentions.instanceShared testingMethod];

Cedan Misquith
  • 1,134
  • 9
  • 20