Swift 和 Objective-C 單例模式詳解

單例模式要求一個(gè)類有一個(gè)實(shí)例,有公開(kāi)接口可以訪問(wèn)這個(gè)實(shí)例。單例模式分為以下兩種模式:

  • 嚴(yán)格單例模式

    嚴(yán)格單例模式,要求一個(gè)類只有一個(gè)實(shí)例。

  • 不嚴(yán)格單例模式

    不嚴(yán)格單例模式,可以創(chuàng)建多個(gè)實(shí)例。

有的類只能有一個(gè)實(shí)例,例如 UIApplication 類,通過(guò) shared 屬性訪問(wèn)唯一的實(shí)例,屬于嚴(yán)格單例模式。廢話不多說(shuō),接下來(lái)看看 Swift 和 Objective-C 的每種單例模式的具體實(shí)現(xiàn)。

Swift 實(shí)現(xiàn)

嚴(yán)格單例模式

大多數(shù) Objective-C 的類都繼承自 NSObject,而 Swift 的類可以繼承自 NSObject 類或者不繼承。

  • 繼承自 NSObject 類
  1. 寫法一
open class DYFStore: NSObject {
    
    public static let `default` = DYFStore()

    /// Overrides default constructor.
    private override init() {
        super.init()
    }
    
    /// Make sure the class has only one instance.
    open override func copy() -> Any {
        return self
    }
    
    /// Make sure the class has only one instance.
    open override func mutableCopy() -> Any {
        return self
    }
}

2.寫法二

open class DYFStore: NSObject {
    
    /// A struct named "Inner".
    private struct Inner {
        static var instance: DYFStore? = nil
    }
   
    public class var `default`: DYFStore {
        
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        
        guard let instance = Inner.instance else {
            let store = DYFStore()
            Inner.instance = store
            return store
        }
        
        return instance
    }
    
    /// Overrides default constructor.
    private override init() {
        super.init()
    }
     
    /// Make sure the class has only one instance.
    open override func copy() -> Any {
        return self
    }
    
    /// Make sure the class has only one instance.
    open override func mutableCopy() -> Any {
        return self
    }
}
  1. 寫法三
open class DYFStore: NSObject {
    
    /// A struct named "Inner".
    private struct Inner {
        static var instance: DYFStore? = nil
    }
   
    public class var `default`: DYFStore {
        
        DispatchQueue.once(token: "com.storekit.DYFStore") {
            if Inner.instance == nil {
                Inner.instance = DYFStore()
            }
        }
        
        return Inner.instance!
    }

    /// Constructs a store singleton with class method.
    ///
    /// - Returns: A store singleton.
    public class func defaultStore() -> DYFStore {
        return DYFStore.self.default
    }

    /// Overrides default constructor.
    private override init() {
        super.init()
    }
     
    /// Make sure the class has only one instance.
    open override func copy() -> Any {
        return self
    }
    
    /// Make sure the class has only one instance.
    open override func mutableCopy() -> Any {
        return self
    }
}

// MARK: - Extends the properties and method for the dispatch queue.
extension DispatchQueue {
    
    /// Declares an array of string to record the token.
    private static var _onceTracker = [String]()
    
    /// Executes a block of code associated with a given token, only once. The code is thread safe and will only execute the code once even in the presence of multi-thread calls.
    ///
    /// - Parameters:
    ///   - token: A unique idetifier.
    ///   - block: A block to execute once.
    public class func once(token: String, block: () -> Void) {
        
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        
        if _onceTracker.contains(token) {
            return
        }
        
        _onceTracker.append(token)
        
        block()
    }
    
    /// Submits a task to a dispatch queue for asynchronous execution.
    ///
    /// - Parameter block: The block to be invoked on the queue.
    public func asyncTask(block: @escaping () -> Void) {
        self.async(execute: block)
    }
    
    /// Submits a task to a dispatch queue for asynchronous execution after a specified time.
    ///
    /// - Parameters:
    ///   - time: The block should be executed after a few time delay.
    ///   - block: The block to be invoked on the queue.
    public func asyncAfter(delay time: Double, block: @escaping () -> Void) {
        self.asyncAfter(deadline: .now() + time, execute: block)
    }
}

DYFStore (In-App Purchase in Swift for iOS) 屬性 default 持有唯一的實(shí)例,對(duì)外公開(kāi)。

重載 init() 方法,使其對(duì)外不可見(jiàn),不可以在外部調(diào)用,防止在外部創(chuàng)建實(shí)例。

重載 copy()、mutableCopy() 方法,返回 self,防止在外部復(fù)制實(shí)例。這里也可以返回 DYFStore.default,效果是一樣的,因?yàn)橹挥幸粋€(gè)實(shí)例。只有屬性 default 能調(diào)用 copy()、mutableCopy() 方法,那么 self 就是屬性 default。寫 self,代碼比較簡(jiǎn)潔。

  • 不繼承自 NSObject 類
open class DYFStore {
    
    public class let `default` = DYFStore()

    /// Privatizes default constructor.
    private init() {}
}

不繼承自 NSObject 的類沒(méi)有 copy()、mutableCopy() 方法,不需要重載。其他同上。

不嚴(yán)格單例模式

把重載的 init() 方法去掉,或者把 private 去掉,即可創(chuàng)建多個(gè)實(shí)例。如果繼承自 NSObject,重載 copy()、mutableCopy() 方法:創(chuàng)建新實(shí)例,傳遞數(shù)據(jù)給新實(shí)例,返回新實(shí)例。其他與嚴(yán)格單例模式相同。

open class DYFStore {
    
    public static let `default` = DYFStore()

    init() {}
}

Objective-C 實(shí)現(xiàn)

Objective-C 創(chuàng)建對(duì)象的步驟分為以下兩步:

  • 1、申請(qǐng)內(nèi)存(alloc)
  • 2、初始化(init)

我們要確保對(duì)象的唯一性,因此在第一步階段時(shí)我們就要攔截它。

當(dāng)調(diào)用 alloc 方法時(shí),OC 內(nèi)部會(huì)調(diào)用 allocWithZone 方法來(lái)申請(qǐng)內(nèi)存,我們覆寫這個(gè)方法,然后在這個(gè)方法中賦值 _instance 并返回單例對(duì)象,這樣就可以達(dá)到我們的目的。

拷貝對(duì)象也是同樣的原理,覆寫copyWithZone方法,然后在這個(gè)方法中調(diào)用 _instance 返回單例對(duì)象,或者禁用 copy 和 mutableCopy 方法 。

嚴(yán)格單例模式

.h 文件

@interface DYFStore : NSObject

/** Constructs a store singleton with class method.
 
 @return A store singleton.
 */
+ (instancetype)defaultStore;

/** Disable this method to make sure the class has only one instance.
 */
+ (instancetype)new NS_UNAVAILABLE;

/** Disable this method to make sure the class has only one instance.
 */
- (id)copy NS_UNAVAILABLE;

/** Disable this method to make sure the class has only one instance.
 */
- (id)mutableCopy NS_UNAVAILABLE;

@end

.m 文件

@implementation DYFStore

// Provides a global static variable.
static DYFStore *_instance = nil;

+ (instancetype)defaultStore {
    return [[self.class alloc] init];
}

/** Returns a new instance of the receiving class.
 */
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    
    if (_instance == nil) {
        static dispatch_once_t onceToken;
        
        dispatch_once(&onceToken, ^{
            _instance = [super allocWithZone:zone];
        });
    }
    
    return _instance;
}

- (instancetype)init {
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        _instance = [super init];
        [_instance setup];
    });
    
    return _instance;
}

/** Sets initial value for some member variables.
 */
- (void)setup {

}

@end

在 .h 文件中,用 NS_UNAVAILABLE 禁用初始化和拷貝方法,只允許用 defaultStore 方法訪問(wèn)唯一實(shí)例。

靜態(tài)變量 _instance 持有唯一的實(shí)例,通過(guò) defaultStore 方法對(duì)外公開(kāi)。由 dispatch_once 保證 _instance 只初始化一次。方法返回值的 nonnull 表示返回值不為空,這樣寫方便 Swift 調(diào)用。不加 nonnull,defaultStore 方法在 Swift 中的返回值是 optional 類型 (DYFStore?),不方便使用;若加上 nonnull,則為 DYFStore (In-App Purchase in Objective-C for iOS) 類型。

NSObject 的類方法 new 相當(dāng)于 alloc 和 init 方法。

不嚴(yán)格單例模式

.h 文件

@interface DYFStore : NSObject

/** Constructs a store singleton with class method.
 
 @return A store singleton.
 */
+ (instancetype)defaultStore;

@end

.m 文件

@implementation DYFStore

+ (instancetype)defaultStore {

    static DYFStore *_instance = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    
    return _instance;
}

@end

公開(kāi)的 defaultStore 方法與嚴(yán)格單例模式相同。外部可以通過(guò) init 方法創(chuàng)建與 _instance 不同的實(shí)例。

如果重載 copyWithZone: 和 mutableCopyWithZone: 方法,就在里面創(chuàng)建新實(shí)例,傳遞數(shù)據(jù)給新實(shí)例,返回新實(shí)例。外部可以通過(guò) copy 或 mutableCopy 方法復(fù)制實(shí)例。

- (id)copyWithZone:(NSZone *)zone {
    DYFStore *store = [[self.class allocWithZone:zone] init];
    // Copy data to store
    return store;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    DYFStore *store = [[self.class allocWithZone:zone] init];
    // Copy data to store
    return store;
}

總結(jié):

寫的有不好的地方希望大家指出,我會(huì)更正,大家有什么看不明白的,也可以在評(píng)論里面提問(wèn),我會(huì)盡力解答。


點(diǎn)贊+關(guān)注,第一時(shí)間獲取技術(shù)干貨和最新知識(shí)點(diǎn),謝謝你的支持!

最后祝大家生活愉快~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。