Singleton with Swift 3.0

2020-03-30 04:38发布

I had this implementation with Swift 2.0 and the Xcode suggestion is not only baffling but causes compilation error as well. it's a library where users are passing callfunc closures.

Before

protocol MyProtocol {

}

class Main

private static var t: dispatch_once_t = 0
private static var singleton: MyProtocol?
public class func getSingleton(callfunc: () -> MyProtocol) -> MyProtocol {
    dispatch_once(&self.t) {
        self.singleton = callfunc()
    }
    return singleton!
}

After

    private static var __once: () = {
        MyProtocol.singleton = callfunc()
    }()

    open class func getSingleton(_ callfunc: () -> MyProtocol) -> MyProtocol {
        singleton = MyProtocol.__once()
        return singleton!
    }

I basically need to pass parameter to __once function.

USER:

class Test: MyProtocol {

}

Main.getSingleton({Test()});

It's not a duplicate of Using a dispatch_once singleton model in Swift, a closure is being passed, it's a .framework and closure is passed in by the user of the library.

标签: swift swift3
6条回答
女痞
2楼-- · 2020-03-30 05:07

This works (as in it won't call callfunc twice), if you don't mind the function becomes @escaping:

class Main {
    private static var CALLER: (() -> MyProtocol)?
    private static let GETTER: MyProtocol = CALLER!()

    public class func getSingleton(_ callfunc: @escaping () -> MyProtocol) -> MyProtocol {
        CALLER = callfunc
        return GETTER
    }
}

Note that this does not address thread safety (CALLER can be changed before reaching GETTER), and the CALLER will be overwritten every time getSingleton is used which may impose some performance penalty.

查看更多
劫难
3楼-- · 2020-03-30 05:08

I usually like this pattern:

final class MyClass { static let shared = MyClass() }

Then you can call MyClass.shared to get your singleton.

查看更多
Deceive 欺骗
4楼-- · 2020-03-30 05:08

Create a singleTon class as follows :

     class mySingleTon{

        //1. gives you SingleTon object which is created only once.
        static let sharedInstance = mySingleTon() 

        //2. make init private so it  prevents others from using the default '()' initializer for this class.    
        private init(){
        print("i am born")
        }

        func foo()
        {
        print("hello")
        }
    }

Example usage :

class A{
func bar()
{
  mySingleTon.sharedInstance.foo()

//3. Uncomment to get error : initializer is inaccessible due to 'private' protection level.
// let newSharedInstance = mySingleTon()
}

let a = A()
let b = A()
a.bar()
b.bar()

Output :

i am born

hello

hello

As you noticed Initializer is only called once. As per apple docs :

“The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as dispatch_once to make sure that the initialization is atomic. This enables a cool way to use dispatch_once in your code: just declare a global variable with an initializer and mark it private.”

Explaining Comments :

  1. Static member of class implicitly calls "dispatch_once" hence it is thread safe.

  2. Making init private prevents initialisation again.

  3. Test code line to prove initialisation is private.

查看更多
不美不萌又怎样
5楼-- · 2020-03-30 05:11

The answers to date are great, but I like to be able to test my classes that use singleton with unit tests. The existing solutions require that you get a real singleton, not one that you can Fake, Mock, and Stub. In order to make testing easier, I use something like this:

protocol ScheduleManaging {
    func add(_ schedule: TimerSchedule)
}

class ScheduleManager: ScheduleManaging {
    var schedule: TimerSchedule?

    static var sharedInstance: ScheduleManaging = {
        return ScheduleManager()
    }()

    private init() {
    }
}

typealias ScheduleManagingMethods = ScheduleManager
extension ScheduleManagingMethods {
    func add(_ schedule: TimerSchedule) {
        self.schedule = schedule
    }
}

In classes under test that use the singleton, I can do something like this:

class OtherTests: XCTestCase {

    class FakeSharedManager: ScheduleManaging {
        var addMethodCalled = false
        func add(_ schedule: TimerSchedule) {
            addMethodCalled = true
        }
    }

    func test_SomeMethodThatShouldCallAdd_CallsAdd() {
        ScheduleManager.sharedInstance = FakeSharedManager()
        // Test that some class that uses the ScheduleManager
        // calls the "add" method of the ScheduleManager
        let someClass = ManagerUsingClass()
        someClass.someMethodThatShouldCallAdd()
        XCTAssertTrue(ScheduleManager.sharedInstance. addMethodCalled)
    }
}
查看更多
Luminary・发光体
6楼-- · 2020-03-30 05:19
public class MyClass {

    static let sharedInstance = MyClass()

    private init() {
        print("MyClass Initialized")
    }

}
查看更多
一夜七次
7楼-- · 2020-03-30 05:21

Another way, for me which is good enough using init private

final class SingletonClass {

    // reachable from other classes
    static let sharedInstance: SingletonClass = SingletonClass()

    // properties
    var stringArray : [String] = []

    // not reachable from other classes
    private init() { }      

}
查看更多
登录 后发表回答