Nice programing

Swift에서 dispatch_once 싱글 톤 모델 사용

nicepro 2020. 10. 3. 11:52
반응형

Swift에서 dispatch_once 싱글 톤 모델 사용


Swift에서 사용하기위한 적절한 싱글 톤 모델을 찾고 있습니다. 지금까지 스레드가 아닌 안전한 모델을 다음과 같이 작동 할 수있었습니다.

class var sharedInstance:TPScopeManager {
    get {
        struct Static {
            static var instance : TPScopeManager? = nil
        }

        if !Static.instance {
            Static.instance = TPScopeManager()
        }

        return Static.instance!
    }
}

정적 구조체에서 싱글 톤 인스턴스를 래핑하면 복잡한 이름 지정 체계없이 싱글 톤 인스턴스와 충돌하지 않는 단일 인스턴스를 허용해야하며,이를 상당히 비공개로 만들어야합니다. 하지만이 모델은 스레드로부터 안전하지 않으므로 전체에 dispatch_once를 추가하려고했습니다.

class var sharedInstance:TPScopeManager {
    get {
        struct Static {
            static var instance : TPScopeManager? = nil
            static var token : dispatch_once_t = 0
        }

        dispatch_once(Static.token) { Static.instance = TPScopeManager() }

        return Static.instance!
    }
}

하지만 dispatch_once에 컴파일러 오류가 발생 합니다.

식의 유형 'Void'를 '()'유형으로 변환 할 수 없습니다.

여러 가지 구문 변형을 시도했지만 모두 동일한 결과를 나타내는 것 같습니다.

dispatch_once(Static.token, { Static.instance = TPScopeManager() })

dispatch_onceSwift 사용의 올바른 사용법은 무엇입니까 ? 처음에는 ()오류 메시지 로 인해 블록에 문제가 있다고 생각 했지만 더 많이 볼수록 dispatch_once_t올바르게 정의 되는 문제라고 생각합니다 .


tl; dr : Swift 1.2 이상을 사용하는 경우 클래스 상수 접근 방식을 사용하고 이전 버전을 지원해야하는 경우 중첩 된 구조체 접근 방식을 사용합니다.

Swift에 대한 내 경험에서 지연 초기화 및 스레드 안전성을 지원하는 Singleton 패턴을 구현하는 세 가지 접근 방식이 있습니다.

클래스 상수

class Singleton  {
   static let sharedInstance = Singleton()
}

이 접근 방식은 Swift가 클래스 상수 (및 변수)를 느리게 초기화하기 때문에 지연 초기화를 지원하고 let. 이것은 이제 싱글 톤을 인스턴스화하는 공식적으로 권장되는 방법 입니다.

클래스 상수는 Swift 1.2에서 도입되었습니다. 이전 버전의 Swift를 지원해야하는 경우 아래의 중첩 된 구조체 접근 방식 또는 전역 상수를 사용하십시오.

중첩 된 구조체

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static let instance: Singleton = Singleton()
        }
        return Static.instance
    }
}

여기에서는 중첩 구조체의 정적 상수를 클래스 상수로 사용합니다. 이것은 Swift 1.1 및 이전 버전의 정적 클래스 상수 부족에 대한 해결 방법이며, 함수에 정적 상수 및 변수 부족에 대한 해결 방법으로 여전히 작동합니다.

dispatch_once

전통적인 Objective-C 접근 방식은 Swift로 포팅되었습니다. 중첩 된 구조체 접근 방식에 비해 이점이 없다고 확신하지만 구문의 차이점이 흥미 롭다는 것을 알기 때문에 어쨌든 여기에 넣겠습니다.

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: Singleton? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = Singleton()
        }
        return Static.instance!
    }
}

단위 테스트는 GitHub 프로젝트를 참조하세요 .


Apple은 이제 정적 구조체 변수가 지연되고 dispatch_once에 래핑되어 초기화된다는 것을 명확히했기 때문에 (게시물 끝에있는 참고 사항 참조) 내 최종 솔루션은 다음과 같을 것이라고 생각합니다.

class WithSingleton {
    class var sharedInstance :WithSingleton {
        struct Singleton {
            static let instance = WithSingleton()
        }

        return Singleton.instance
    }
}

이는 정적 구조체 요소의 자동 지연, 스레드 안전 초기화를 활용하고, 소비자로부터 실제 구현을 안전하게 숨기고, 가독성을 위해 모든 것을 간결하게 구분하고, 가시적 인 전역 변수를 제거합니다.

Apple은 지연 이니셜 라이저가 스레드로부터 안전하므로 dispatch_once이와 유사한 보호 가 필요하지 않다고 명시했습니다.

전역 변수 (구조체 및 열거 형의 정적 멤버 용)에 대한 지연 이니셜 라이저는 전역에 처음 액세스 할 때 실행되고 초기화가 원 자성인지 확인하기 위해 dispatch_once로 시작됩니다. 이렇게하면 코드에서 dispatch_once를 사용할 수있는 멋진 방법이 있습니다. 초기화 프로그램으로 전역 변수를 선언하고 비공개로 표시하기 만하면됩니다.

에서 여기


Swift 1.2 이상 :

class Singleton  {
   static let sharedInstance = Singleton()
}

정확성 증명 (모든 크레딧이 여기에 있음 )을 사용하면 싱글 톤에 대해 이전 방법을 사용할 이유가 거의 없습니다.

업데이트 : 이제 공식 문서에 설명 된대로 싱글 톤을 정의 하는 공식적인 방법입니다 !

사용에 대한 문제에 관해서는 staticclass. 변수 static를 사용할 수있는 경우에도 class사용할 수 있어야합니다. 싱글 톤은 기본 싱글 톤의 여러 인스턴스를 생성하므로 하위 클래스로 분류되지 않습니다. 를 사용 static하면 아름답고 신속한 방식으로 이를 적용 할 수 있습니다.

Swift 1.0 및 1.1의 경우 :

최근 Swift의 변경, 대부분 새로운 접근 제어 방법으로, 저는 이제 싱글 톤에 전역 변수를 사용하는 더 깨끗한 방법으로 기울고 있습니다.

private let _singletonInstance = SingletonClass()
class SingletonClass {
  class var sharedInstance: SingletonClass {
    return _singletonInstance
  }
}

여기 Swift 블로그 기사에서 언급했듯이 :

전역 변수 (구조체 및 열거 형의 정적 멤버 용)에 대한 지연 이니셜 라이저는 전역에 처음 액세스 할 때 실행되고 초기화가 원 자성인지 확인하기 위해 dispatch_once로 시작됩니다. 이렇게하면 코드에서 dispatch_once를 사용할 수있는 멋진 방법이 있습니다. 초기화 프로그램으로 전역 변수를 선언하고 비공개로 표시하기 만하면됩니다.

싱글 톤을 만드는이 방법은 스레드로부터 안전하고 빠르며 게으 르며 ObjC에 무료로 연결됩니다.


Swift 1.2 이상은 이제 클래스에서 정적 변수 / 상수를 지원합니다. 따라서 정적 상수를 사용할 수 있습니다.

class MySingleton {

    static let sharedMySingleton = MySingleton()

    private init() {
        // ...
    }
}

더 나은 방법이 있습니다. 클래스 선언 위의 클래스에서 전역 변수를 다음과 같이 선언 할 수 있습니다.

var tpScopeManagerSharedInstance = TPScopeManager()

이것은 기본 init 또는 Swift에서 기본적으로 dispatch_once 인 init 및 전역 변수를 호출합니다. 그런 다음 참조를 얻으려는 클래스에서 다음을 수행하십시오.

var refrence = tpScopeManagerSharedInstance
// or you can just access properties and call methods directly
tpScopeManagerSharedInstance.someMethod()

따라서 기본적으로 공유 인스턴스 코드의 전체 블록을 제거 할 수 있습니다.


Swift 싱글 톤은 Cocoa 프레임 워크에서 클래스 함수 (예 : NSFileManager.defaultManager(),) 로 노출 NSNotificationCenter.defaultCenter()되므로 다른 솔루션에서 사용하는 클래스 변수보다는이 동작을 미러링하는 클래스 함수로 더 합리적이라고 생각합니다.

class MyClass {

    private static let _sharedInstance = MyClass()

    class func sharedInstance() -> MyClass {
        return _sharedInstance
    }
}

를 통해 싱글 톤을 검색합니다 MyClass.sharedInstance().


Apple 문서에 따르면 Swift에서이를 수행하는 가장 쉬운 방법은 정적 유형 속성을 사용하는 것임을 여러 번 반복했습니다.

class Singleton {
    static let sharedInstance = Singleton()
}

그러나 간단한 생성자 호출 이상의 추가 설정을 수행하는 방법을 찾고 있다면 즉시 호출 된 클로저를 사용하는 것이 비결입니다.

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

이것은 스레드로부터 안전하고 한 번만 느리게 초기화됩니다.


Swift 4+

protocol Singleton: class {
    static var sharedInstance: Self { get }
}

final class Kraken: Singleton {
    static let sharedInstance = Kraken()
    private init() {}
}

Apple의 샘플 코드를 보면이 패턴을 발견했습니다. Swift가 정적을 어떻게 처리하는지 잘 모르겠지만 이것은 C #에서 스레드로부터 안전합니다. Objective-C interop의 속성과 방법을 모두 포함합니다.

struct StaticRank {
    static let shared = RankMapping()
}

class func sharedInstance() -> RankMapping {
    return StaticRank.shared
}

class var shared:RankMapping {
    return StaticRank.shared
}

간단히 말해서

class Manager {
    static let sharedInstance = Manager()
    private init() {}
}

파일 및 초기화 를 읽을 수 있습니다.

전역 변수 (구조체 및 열거 형의 정적 멤버)에 대한 지연 이니셜 라이저는 전역에 처음 액세스 할 때 실행 dispatch_once되며 초기화가 원 자성인지 확인하기 위해 시작됩니다 .


Objective-C에서 Swift 싱글 톤 클래스를 사용하려는 경우이 설정은 컴파일러가 적절한 Objective-C 유사 헤더를 생성하도록합니다.

class func sharedStore() -> ImageStore {
struct Static {
    static let instance : ImageStore = ImageStore()
    }
    return Static.instance
}

그런 다음 Objective-C 클래스에서 스위프트 이전에했던 방식으로 싱글 톤을 호출 할 수 있습니다.

[ImageStore sharedStore];

이것은 단지 나의 간단한 구현입니다.


첫 번째 솔루션

let SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

나중에 코드에서 :

func someFunction() {        
    var socketManager = SocketManager        
}

두 번째 솔루션

func SocketManager() -> SocketManagerSingleton {
    return _SocketManager
}
let _SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

나중에 코드에서 혼동을 줄이기 위해 중괄호를 유지할 수 있습니다.

func someFunction() {        
    var socketManager = SocketManager()        
}

final class MySingleton {
     private init() {}
     static let shared = MySingleton()
}

그런 다음 전화하십시오.

let shared = MySingleton.shared

사용하다:

class UtilSingleton: NSObject {

    var iVal: Int = 0

    class var shareInstance: UtilSingleton {
        get {
            struct Static {
                static var instance: UtilSingleton? = nil
                static var token: dispatch_once_t = 0
            }
            dispatch_once(&Static.token, {
                Static.instance = UtilSingleton()
            })
            return Static.instance!
        }
    }
}

사용하는 방법:

UtilSingleton.shareInstance.iVal++
println("singleton new iVal = \(UtilSingleton.shareInstance.iVal)")

1.2 이상의 Swift에서 가장 좋은 접근 방식은 다음과 같이 단선 싱글 톤입니다.

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

이 방법에 대한 자세한 내용을 보려면이 링크를 방문 하십시오 .


Java에서 사용하는 것과 같은 Enum을 제안합니다. 예 :

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}

Apple Docs (Swift 3.0.1)에서

여러 스레드에서 동시에 액세스하는 경우에도 한 번만 지연 초기화되도록 보장되는 정적 유형 속성을 간단히 사용할 수 있습니다.

class Singleton {
    static let sharedInstance = Singleton()
}

초기화 이후 추가 설정을 수행해야하는 경우 클로저 호출 결과를 전역 상수에 할당 할 수 있습니다.

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

참고로 Jack Wu / hpique의 Nested Struct 구현의 Singleton 구현 예가 있습니다. 구현은 또한 아카이브가 작동하는 방법과 일부 수반되는 기능을 보여줍니다. 이 완전한 예를 찾을 수 없으므로 누군가에게 도움이되기를 바랍니다.

import Foundation

class ItemStore: NSObject {

    class var sharedStore : ItemStore {
        struct Singleton {
            // lazily initiated, thread-safe from "let"
            static let instance = ItemStore()
        }
        return Singleton.instance
    }

    var _privateItems = Item[]()
    // The allItems property can't be changed by other objects
    var allItems: Item[] {
        return _privateItems
    }

    init() {
        super.init()
        let path = itemArchivePath
        // Returns "nil" if there is no file at the path
        let unarchivedItems : AnyObject! = NSKeyedUnarchiver.unarchiveObjectWithFile(path)

        // If there were archived items saved, set _privateItems for the shared store equal to that
        if unarchivedItems {
            _privateItems = unarchivedItems as Array<Item>
        } 

        delayOnMainQueueFor(numberOfSeconds: 0.1, action: {
            assert(self === ItemStore.sharedStore, "Only one instance of ItemStore allowed!")
        })
    }

    func createItem() -> Item {
        let item = Item.randomItem()
        _privateItems.append(item)
        return item
    }

    func removeItem(item: Item) {
        for (index, element) in enumerate(_privateItems) {
            if element === item {
                _privateItems.removeAtIndex(index)
                // Delete an items image from the image store when the item is 
                // getting deleted
                ImageStore.sharedStore.deleteImageForKey(item.itemKey)
            }
        }
    }

    func moveItemAtIndex(fromIndex: Int, toIndex: Int) {
        _privateItems.moveObjectAtIndex(fromIndex, toIndex: toIndex)
    }

    var itemArchivePath: String {
        // Create a filepath for archiving
        let documentDirectories = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
        // Get the one document directory from that list
        let documentDirectory = documentDirectories[0] as String
        // append with the items.archive file name, then return
        return documentDirectory.stringByAppendingPathComponent("items.archive")
    }

    func saveChanges() -> Bool {
        let path = itemArchivePath
        // Return "true" on success
        return NSKeyedArchiver.archiveRootObject(_privateItems, toFile: path)
    }
}

그리고 이러한 기능 중 일부를 인식하지 못했다면 여기에 제가 사용해온 살아있는 Swift 유틸리티 파일이 있습니다.

import Foundation
import UIKit

typealias completionBlock = () -> ()

extension Array {
    func contains(#object:AnyObject) -> Bool {
        return self.bridgeToObjectiveC().containsObject(object)
    }

    func indexOf(#object:AnyObject) -> Int {
        return self.bridgeToObjectiveC().indexOfObject(object)
    }

    mutating func moveObjectAtIndex(fromIndex: Int, toIndex: Int) {
        if ((fromIndex == toIndex) || (fromIndex > self.count) ||
            (toIndex > self.count)) {
                return
        }
        // Get object being moved so it can be re-inserted
        let object = self[fromIndex]

        // Remove object from array
        self.removeAtIndex(fromIndex)

        // Insert object in array at new location
        self.insert(object, atIndex: toIndex)
    }
}

func delayOnMainQueueFor(numberOfSeconds delay:Double, action closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue()) {
            closure()
    }
}

유일한 올바른 접근 방식은 다음과 같습니다.

final class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code if anything
        return instance
    }()

    private init() {}
}

액세스

let signleton = Singleton.sharedInstance

원인:

  • 정적 유형 속성은 여러 스레드에서 동시에 액세스하더라도 한 번만 지연 초기화되므로 dispatch_once를 사용할 필요가 없습니다.
  • 다른 클래스에서 인스턴스를 만들 수 없도록 init 메서드를 전용합니다.
  • 다른 클래스가 Singleton 클래스를 상속하지 않기를 원하는 최종 클래스

신속하게 다음과 같은 방법으로 싱글 톤 클래스를 만들 수 있습니다.

class AppSingleton: NSObject {

    //Shared instance of class
    static let sharedInstance = AppSingleton()

    override init() {
        super.init()
    }
}

이 구현을 선호합니다.

class APIClient {

}

var sharedAPIClient: APIClient = {
    return APIClient()
}()

extension APIClient {
    class func sharedClient() -> APIClient {
        return sharedAPIClient
    }
}

Swift에서 구현하는 방법 ...

ConfigurationManager.swift

import Foundation

    let ConfigurationManagerSharedInstance = ConfigurationManager()
 class ConfigurationManager : NSObject {
    var globalDic: NSMutableDictionary = NSMutableDictionary()

class var sharedInstance:ConfigurationManager {
    return ConfigurationManagerSharedInstance

}

init() {

    super.init()

    println ("Config Init been Initiated, this will be called only onece irrespective of many calls")   

}

아래의 응용 프로그램 화면에서 globalDic에 액세스하십시오.

읽다:

 println(ConfigurationManager.sharedInstance.globalDic)  

쓰다:

 ConfigurationManager.sharedInstance.globalDic = tmpDic // tmpDict is any value that to be shared among the application

David의 구현을 본 후에 let는 sharedInstance 클래스 메서드와 거의 동일한 작업을 수행 하므로 단일 클래스 함수 instanceMethod를 가질 필요가없는 것 같습니다 . 여러분이해야 할 일은 그것을 전역 상수로 선언하는 것뿐입니다.

let gScopeManagerSharedInstance = ScopeManager()

class ScopeManager {
 // No need for a class method to return the shared instance. Use the gScopeManagerSharedInstance directly. 
}

   func init() -> ClassA {
    struct Static {
        static var onceToken : dispatch_once_t = 0
        static var instance : ClassA? = nil
    }

    dispatch_once(&Static.onceToken) {
        Static.instance = ClassA()
    }

    return Static.instance!
}

과거에 싱글 톤을 실현하기위한 Swift는 전역 변수, 내부 변수 및 dispatch_once 방식의 세 가지 방법에 지나지 않습니다.

(참고 : 어떤 종류의 글을 작성하더라도 민영화의 init () 메소드에주의를 기울여야합니다. Swift에서는 모든 객체의 생성자 기본값이 public이므로 다시 작성해야합니다 ,이 클래스 '()'의 다른 객체는 기본 초기화 방법으로 객체를 생성하지 못하도록합니다.)

방법 1 :

class AppManager {
    private static let _sharedInstance = AppManager()

    class func getSharedInstance() -> AppManager {
       return _sharedInstance
    }

    private init() {} // Privatizing the init method
}

// How to use?
AppManager.getSharedInstance()

방법 2 :

class AppManager {
    static let sharedInstance = AppManager()

    private init() {} // Privatizing the init method
}

// How to use?
AppManager.sharedInstance

나는 방금 이것을 발견했지만 상속을 허용하기 위해 싱글 톤이 필요했으며 이러한 솔루션 중 어느 것도 실제로 허용하지 않았습니다.

그래서 나는 이것을 생각해 냈습니다.

public class Singleton {
  private static var sharedInstanceVar = Singleton()

  public class func sharedInstance()->Singleton {
    return sharedInstanceVar
  }
}


public class SubSingleton: Singleton {

  private static var sharedInstanceToken:dispatch_once_t = 0

  public class override func sharedInstance()->SubSingleton {
    dispatch_once(&sharedInstanceToken){
      sharedInstanceVar = SubSingleton()
    }
    return sharedInstanceVar as! SubSingleton
  }
}
  • 이렇게하면 Singleton.sharedInstance ()를 처음 수행 할 때 Singleton의 인스턴스를 반환합니다.
  • SubSingleton.sharedInstance ()를 먼저 수행하면 생성 된 SubSingleton의 인스턴스를 반환합니다.
  • 위의 작업이 완료되면 SubSingleton.sharedInstance ()는 Singleton이 true이고 동일한 인스턴스가 사용됩니다.

이 첫 번째 더러운 접근 방식의 문제는 하위 클래스가 dispatch_once_t를 구현하고 sharedInstanceVar가 클래스 당 한 번만 수정되도록 보장 할 수 없다는 것입니다.

나는 이것을 더 구체화하려고 노력할 것이지만 누군가 이것에 대해 강한 감정을 가지고 있는지 확인하는 것이 흥미로울 것입니다 (장황하고 수동으로 업데이트해야한다는 사실 외에도).


이것은 스레드 안전 기능이있는 가장 간단한 것입니다. 다른 스레드는 원하는 경우에도 동일한 싱글 톤 객체에 액세스 할 수 없습니다. 스위프트 3/4

struct DataService {

    private static var _instance : DataService?

    private init() {}   //cannot initialise from outer class

    public static var instance : DataService {
        get {
            if _instance == nil {
                DispatchQueue.global().sync(flags: .barrier) {
                    if _instance == nil {
                        _instance = DataService()
                    }
                }
            }
            return _instance!
        }
    }
}

정적 변수와 개인 이니셜 라이저를 사용하여 싱글 톤 클래스를 만듭니다.

class MySingletonClass {

    static let sharedSingleton = MySingletonClass()

    private init() {}
}

이것은 내 구현입니다. 또한 프로그래머가 새 인스턴스를 만들지 못하게합니다.

let TEST = Test()

class Test {

    private init() {
        // This is a private (!) constructor
    }
}

private var sharedURLCacheForRequestsKey:Void?
extension URLCache{
public static func sharedURLCacheForRequests()->URLCache{
    var cache = objc_getAssociatedObject(OperationQueue.main, &sharedURLCacheForRequestsKey)
    if cache is URLCache {

    }else{
        cache = URLCache(memoryCapacity: 0, diskCapacity: 1*1024*1024*1024, diskPath: "sharedURLCacheForRequestsKey")
        objc_setAssociatedObject(OperationQueue.main, &sharedURLCacheForRequestsKey, cache, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

    }
    return cache as! URLCache
}}

참고 URL : https://stackoverflow.com/questions/24024549/using-a-dispatch-once-singleton-model-in-swift

반응형