哥的單例是偽的還是真的有圖為證(Singleton)

前言


前段時間和同事交流單例模式,感覺自己對單例了解還有很多不足。上網查了下發現,有些帖子太坑,拿著偽單例大書特書,看著眼疼。
單例不是簡單的編程技巧,它是編程模式,能上升到模式級別的東西,不是說幾行簡單代碼能搞定的,每次聽到“單例模式是設計模式中最簡單的形式”這句話時,都有種想拿人撞墻的感覺。

什么是單例模式


提到單例,如果在大學里上Java課的時候,你忘了睡覺,那么你肯定知道單例有很多種寫法,比如:餓漢式、懶漢式等。單例類的寫法也是很多公司,筆試必考題之一。在iOS系統為我們提供了許多單例類,如,UIApplication、NSUserDefaults等。

單例模式是一個Class在系統中只允許生成一個Instance Object,以共享內存的方式供系統中其他實例對象進行訪問。在iOS開發中,單例模式是非常有用的一種設計模式。

提到單例,很多人都會舉一個例子,
具體場景:一臺機器多個可打印的應用Pro1、 Pro2、Pro3......,一臺打印機Print;
在多個應用連接打印機的時候,Pro1、 Pro2、Pro3連接打印機Print的時候,生成的Print實例是同一個實例。

單例的作用


  1. 單例類保證了應用程序的生命周期中有且僅有一個該類的實例對象,而且易于外界訪問。
  2. 共享內存,存儲用戶數據等
  3. 無交互類傳值時,充當中間人

寫法分類:


  1. 懶漢式 : 首次用到單例對象的時,創建實例對象;
  2. 餓漢式 : 進入程序就創建實例對象

提到這兩種方式,首先說個知識點:NSObject的load和initialize方法,點開Apple官方關于NSObject的API會發現,在NSObject.h文件中有兩個類方法load、initialize,

@interface NSObject <NSObject> 
{
    Class isa  OBJC_ISA_AVAILABILITY;
}
+ (void)load;
+ (void)initialize;

The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.
The order of initialization is as follows:
All initializers in any framework you link to.
All +load methods in your image.
All C++ static initializers and C/C++ attribute(constructor) functions in your image.
All initializers in frameworks that link to you.

The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses. The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize].

Apple的官方文檔清楚地說明了initialize和load的區別:
load:只要類所在文件被引用就會被調用,即文件的.m文件引入,系統運行,load就會被執行;
initialize:在類或者其子類的方法被調用前被調用,它屬于懶漢的,自己的方法沒被用到,它是不會執行的。

相同點:兩種方法都只會被調用一次。

為了防止不停的if -->( _instance == nil) else ....這種令人作嘔的判斷,下面實現使用線程方式(記得實現NSCopying, NSMutableCopying協議方法):

先驗證下,哥的單例是偽的,還是真的,有圖為證:

有朋友問到,使用alloc創建實例為什么會地址相同,主要是因為重寫了allocWithZone,返回同一個實例。后面mutableCopyWithZone和copyWithZone方法的重寫,是即便是使用copy修飾,也同樣返回同一個實例。

@property (nonatomic, copy) EVNHelper *helper;

懶漢式單例

.h文件:

//
//  EVNHelper.h
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright ? 2016年 仁伯安. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface EVNHelper : NSObject<NSCopying, NSMutableCopying>

+ (instancetype)shareInstance;

@end

.m文件:

//
//  EVNHelper.m
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright ? 2016年 仁伯安. All rights reserved.
//

#import "EVNHelper.h"

static id _instance;

@implementation EVNHelper
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}
+ (instancetype)shareInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
    return _instance;
}
@end

餓漢式單例

.h文件:

//
//  EVNHelper.h
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright ? 2016年 仁伯安. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface EVNHelper : NSObject<NSCopying, NSMutableCopying>

+ (instancetype)shareInstance;

@end

.m文件:

//
//  EVNHelper.m
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright ? 2016年 仁伯安. All rights reserved.
//

#import "EVNHelper.h"

static id _instance;

@implementation EVNHelper

 /**
 *  只要系統中引用了該類,程序運行,就會主動調用load(不用手動調用,而且只會加載1次)
 */
+ (void)load
{
    _instance = [[EVNHelper alloc] init]; //[[self alloc] init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}
+ (instancetype)shareInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
    return _instance;
}
@end

上面的寫法是在ARC情況下,MRC還需在.m文件中添加如下方法:(鄙人不太懂MRC,只為擴充下,有問題,請回復,指教)

(1) 重寫release方法為空
(2) 重寫retain方法返回self
(3) 重寫retainCount返回 1
(4) 重寫autorelease返回self

- (oneway void)release
{ 
    NSLog(@"這里空語句就行");
}
- (id)retain 
{
    return self;
}
- (NSUInteger)retainCount
{
     return 1;
}
- (id)autorelease 
{
   return self;
}

本文已在版權印備案,如需轉載請在版權印獲取授權。
獲取版權

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容