單例模式作用
- 可以保證在程序運(yùn)行過程中,一個類只有一個實(shí)例鳖目,而且該實(shí)例易于供外界使用
- 從而方便地控制了實(shí)例個數(shù)领迈,并節(jié)約系統(tǒng)資源
單例模式使用場合
- 在整個引用程序中,共享一份資源(這份資源只需要創(chuàng)建初始化1次衷蜓,只分配一次存儲空間)
- 例如:背景音樂磁浇,音頻調(diào)節(jié)器等
單例的簡單使用
- 使用單例的目的就是為了要在程序運(yùn)行過程中朽褪,共享一份資源缔赠,且這份資源只會初始化一次嗤堰,只分配一次存儲空間,節(jié)約系統(tǒng)資源告匠;先來看一下平時我們創(chuàng)建對象時,內(nèi)存地址的變化情況:
創(chuàng)建對象內(nèi)存分配地址演示
1.這里用SJTools這個類來演示
// 創(chuàng)建SJTools類
SJTools *tool1 = [[SJTools alloc] init];
SJTools *tool2 = [SJTools new];
SJTools *tool3 = [[SJTools alloc] init];
NSLog(@"\ntool1%@:\ntool2%@:\ntool3%@:",tool1, tool2, tool3);
執(zhí)行結(jié)果:從打印的內(nèi)存地址可以看出——每次創(chuàng)建同一個類,都會分配新的內(nèi)存地址行贪,這在某些場合是沒有必要的(比如播放音樂建瘫,一般我們不會有在同一個播放器同時播放多首歌的情況出現(xiàn))啰脚,而且系統(tǒng)的資源是有限的,特別是移動設(shè)備粒梦,所以前輩們總結(jié)出新的模式——單例
單例創(chuàng)建演示
1.這里依舊使用SJTools這個類演示
- 現(xiàn)在我們的目的是類無論創(chuàng)建多少次匀们,都
只分配一次存儲空間
泄朴,而分配存儲空間的操作是在alloc:
中執(zhí)行祖灰,所以我們要重寫類中的alloc:
類方法 - 那么我們通過以下幾種思路來實(shí)現(xiàn)單例
+ (instancetype)alloc
+ (instancetype)allocWithZone:(struct _NSZone *)zone
在重寫alloc:
過程中畔规,我們發(fā)現(xiàn)有個allocWithZone:
方法,這個和alloc:
方法有什么區(qū)別呢叁扫?——其實(shí),alloc:
方法內(nèi)部最終會去調(diào)用allocWithZone:
方法來分配存儲空間沈跨,所以為了能更深層控制,我們放棄重寫alloc:
方法软驰,直接重寫allocWithZone:
方法。
思路一(ARC模式下單例模式的實(shí)現(xiàn))
1.因?yàn)槭穷惙椒ň牢猓也幌胪饷娅@取到這個變量戴已,所以我們先定義一個靜態(tài)變量
// 聲明一個靜態(tài)變量(不然外界獲取到)
static SJTools *_instance;
1.2 重寫allocWithZone:
方法
1.2.1 第一種方式 —— 懶加載
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 考慮到使用時可能在多條線程同時執(zhí)行此方法任務(wù)糖儡,那么就可能引發(fā)線程安全問題怔匣,所以我們需要對線程進(jìn)行加鎖操作
@synchronized(self) {
if (_instance == nil) { // 如果為nil就執(zhí)行以下操作
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
1.2.2 第二種方式 —— GCD一次性代碼
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 我們也可以使用GCD提供給我們的一次性代碼函數(shù)來實(shí)現(xiàn)每瞒,因?yàn)樗旧砭褪蔷€程安全的
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
到這里剿骨,簡單的單例就實(shí)現(xiàn)了,但是還有一些問題需要解決挤庇,先不管罚随,先Run來試一下是不是管用
執(zhí)行結(jié)果:從結(jié)果可以看出淘菩,確實(shí)所有創(chuàng)建的操作只分配了一次內(nèi)存
- 為了讓我們的單例嚴(yán)謹(jǐn)一點(diǎn)潮改,我們還要考慮copy這種情況腹暖,所以我們還要重寫copy和mutableCopy
1.1 要重寫copy和mutableCopy方法,必須先遵守協(xié)議
@interface SJTools()<NSCopying, NSMutableCopying>
1.2 重寫copy和mutableCopy方法
- (id)copyWithZone:(NSZone *)zone
{
// 直接返回變量即可锹杈,因?yàn)橹挥袆?chuàng)建了對象惊暴,才能使用copy方法
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}
- 為了供外界方便訪問饮六,我們還需要提供相應(yīng)的調(diào)用方法,一般我們會提供給外界一個類方法供外界使用辛蚊,這也牽扯到命名的問題真仲,怎樣命名才規(guī)范袒餐,這里順便提一下
- 一般常見的命名形式有這幾種 —— share 灸眼、share + 類名 、default霉囚、default + 類名
- 優(yōu)點(diǎn):
- 減少溝通成本
- 方便外界訪問
- 優(yōu)點(diǎn):
- 一般常見的命名形式有這幾種 —— share 灸眼、share + 類名 、default霉囚、default + 類名
1.在.h文件中聲明
/**
* 獲取單例對象
*/
+ (instancetype)shareSJTools;
2.在.m文件中實(shí)現(xiàn)
+ (instancetype)shareSJTools
{
// 返回實(shí)例對象
return [[self alloc] init];
}
- 到這來我們的單例模式就實(shí)現(xiàn)了盈罐,但是上面的方式在MRC環(huán)境下就不好用了盅粪,那有沒有同時在ARC和MRC中都可使用的方法呢票顾?下面我們就來解決這樣的問題帆调。
思路二(MRC下單例模式的實(shí)現(xiàn))
首先番刊,需要MRC的環(huán)境,需要先修改一下XCode配置
接下來需要修改下前面創(chuàng)建對象的代碼鸭廷,讓其符合MRC規(guī)則,并運(yùn)行
// 創(chuàng)建SJTools類
SJTools *tool1 = [[SJTools alloc] init];
[tool1 release];
SJTools *tool2 = [SJTools new];
[tool2 release];
SJTools *tool3 = [[SJTools alloc] init];
[tool3 release];
NSLog(@"\ntool1%@:\ntool2%@:\ntool3%@:",tool1, tool2, tool3);
執(zhí)行結(jié)果:編譯器提示我們消息發(fā)送給已經(jīng)釋放的對象靴姿,這是因?yàn)槲覀冊趧?chuàng)建對象后磁滚,對其進(jìn)行了release操作垂攘,這樣對象的計數(shù)器為0晒他,系統(tǒng)就將會其釋放逸贾,所以在設(shè)計單例時我們還需要考慮MRC的環(huán)境。
- 因?yàn)閱卫纳芷谑菑谋粍?chuàng)建的那一刻起铝侵,到程序運(yùn)行結(jié)束灼伤,也就是說,我們可以不用理會MRC中的規(guī)則咪鲜,但是又不想破壞使用者的使用習(xí)慣狐赡,那么我們可以在類中重寫
retain
,release
疟丙,retainCount
方法颖侄,使其不受外界影響。
- (oneway void)release
{
// 因?yàn)槲覀儾焕頃﨧RC規(guī)則享郊,所以在release方法中可以什么都不做
}
- (instancetype)retain
{
// 因?yàn)閞etain方法要求返回一個instancetype返回值,我們直接返回變量
return _instance;
}
- (NSUInteger)retainCount
{
// 在之前的一些早期的MRC項(xiàng)目中發(fā)現(xiàn)炊琉,單例中普遍會直接給retaiCount一個MAXFLOAT作為返回值展蒂,這樣做應(yīng)該是為了讓外界認(rèn)為這個類是單例,進(jìn)一步減少溝通成本
return MAXFLOAT;
}
執(zhí)行結(jié)果:這樣程序就能正常運(yùn)行了
為了讓單例在ARC
和MRC
模式下都能愉快使用温自,我們可以使用宏來判斷當(dāng)前系統(tǒng)環(huán)境玄货,并做出對應(yīng)處理
#ifdef __has_feature(objc_arc)
// ARC環(huán)境
#else
// MRC環(huán)境
- (oneway void)release
{
// 因?yàn)槲覀儾焕頃﨧RC規(guī)則,所以在release方法中可以什么都不做
}
- (instancetype)retain
{
// 因?yàn)閞etain方法要求返回一個instancetype返回值悼泌,我們直接返回變量
return _instance;
}
- (NSUInteger)retainCount
{
// 在之前的一些早期的MRC項(xiàng)目中發(fā)現(xiàn)松捉,單例中普遍會直接給retaiCount一個MAXFLOAT作為返回值,這樣做應(yīng)該是為了讓外界認(rèn)為這個類是單例馆里,進(jìn)一步減少溝通成本
return MAXFLOAT;
}
#endif
到此隘世,我們的單例就可以歡快地使用了可柿。
單例模式使用注意
- 單例是不可繼承的,我們可以來演示一下
首先丙者,新建一個SJNewTools
類复斥,并讓他繼承SJTools
,然后打印一下創(chuàng)建SJTools對象和SJNewTools對象后的內(nèi)存地址
// 創(chuàng)建SJTools類
SJTools *tool1 = [SJTools shareSJTools];
// 創(chuàng)建SJNewTools類
SJNewTools *newTool1 = [SJNewTools shareSJTools];
NSLog(@"\ntool1%@:\nnewTool1%@:\n",tool1, newTool1);
執(zhí)行結(jié)果:打印出來的內(nèi)存地址是相同的械媒,而且他們的真實(shí)類型都是SJTools
為什么會這樣目锭?
其實(shí)是因?yàn)?code>alloc:方法內(nèi)部會調(diào)用allocWithZone:
方法,在allocWithZone:
內(nèi)會對_instance這個變量進(jìn)行初始化纷捞,給它分配存儲空間痢虹;在這個階段的時候,系統(tǒng)會去判斷這個變量的真實(shí)類型主儡,因?yàn)楫?dāng)前變量的類型是SJTools類型奖唯,所以_instance變量就是SJTools類型,后面SJNewTools繼承了SJTools糜值,但是內(nèi)部使用的是同一個變量丰捷,就造成了上面的情況,所以寂汇,單例是不能繼承的病往。
懶人福音 —— 單例的復(fù)用
- 在開發(fā)中,一般出現(xiàn)像這樣可能復(fù)用的代碼健无,而又不能繼承的方式荣恐,那簡直對我們這種
懶人
的晴天霹靂!難道每次要使用單例都要敲著這樣一大串惡心的代碼么累贤?其實(shí)叠穆,我們還可以用宏來解決這樣的事情嘛O(∩_∩)O
首先,因?yàn)閱卫梢苑譃?部分臼膏,一部分是.h文件硼被,一部分是.m文件,所以渗磅,宏要分開來寫 —— 先創(chuàng)建一個.h文件
Singleton.h文件
1.因?yàn)槲覀冊谥恍枰峁┮粋€方法給外界使用嚷硫,而且為了讓其用起來更加清晰,我們給其加入?yún)?shù)
宏內(nèi)怎么接收和使用參數(shù)呢始鱼?
- 格式:
#define 宏名(參數(shù)名) ##參數(shù)名
#define SingletonH(name) +(instancetype)share##name;
2.將之前我們在SJTools類的.m文件所做的操作拷貝過來仔掸,但會發(fā)現(xiàn)除了第一行以外的所有代碼段都是沒有列入宏的范圍內(nèi)(顏色不同);這時候需要使用""符來進(jìn)行連接医清,同樣起暮,我們要讓外界傳入?yún)?shù)來改變方法名,讓其與.h文件相對應(yīng)会烙;同時變量的類型直接修改為id
類型即可负懦。
#define SingletonW(name) static id _instance;\
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
\
return _instance;\
}\
\
+ (instancetype)share##name\
{\
return [[self alloc] init];\
}\
\
- (id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}
這樣宏就已經(jīng)做好了筒捺,我們只需要在要使用單例的類中先引入頭文件,然后在.h文件中這樣使用
#import <Foundation/Foundation.h>
#import "Singleton.h"
@interface SJTools : NSObject
SingletonH(SJTools)
@end
.m文件中這樣使用就可以了
@implementation SJTools
SingletonW(SJTools)
@end
為了使我們的宏能同時ARC和MRC纸厉,我們需要將宏判斷部分也加入到單例宏中系吭,但是宏判斷是不能內(nèi)嵌的,所以只能先判斷環(huán)境颗品,再分別進(jìn)行相應(yīng)操作
#define SingletonH(name) +(instancetype)share##name;
#if __has_feature(objc_arc)
// ARC環(huán)境
#define SingletonW(name) static id _instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
\
return _instance;\
}\
+ (instancetype)share##name\
{\
return [[self alloc] init];\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
- (id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}
#else
// MRC環(huán)境
#define SingletonW(name) static id _instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
\
return _instance;\
}\
+ (instancetype)share##name\
{\
return [[self alloc] init];\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
- (id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
- (oneway void)release\
{\
}\
- (instancetype)retain\
{\
return _instance;\
}\
- (NSUInteger)retainCount\
{\
return MAXFLOAT;\
}
#endif
好了肯尺,這樣我們的單例宏就完成了,只要在需要的項(xiàng)目中導(dǎo)入這個宏抛猫,然后在需要的地方使用宏就可以了蟆盹!