iOS單例模式

1 單例模式

它是一種設(shè)計(jì)模式(常見(jiàn)的設(shè)計(jì)模式有:觀察者模式、工廠模式、門面模式等)。單例設(shè)計(jì)模式中,一個(gè)類只有一個(gè)實(shí)例,只分配一次內(nèi)存空間,節(jié)約內(nèi)存等,特別適合在移動(dòng)端使用。

實(shí)現(xiàn)單例的思路:

1 只能分配一次內(nèi)存----要攔截 alloc 方法
2 alloc 方法的底層是 allocWithZone 方法
3 每個(gè)類只有一個(gè)對(duì)象,需要有一個(gè)全局變量來(lái)存儲(chǔ)這個(gè)對(duì)象
4 需要考慮線程安全

1.1 單例基本形式(ARC)--懶漢模式

1.1.1 .h文件

@interface MusicTool : NSObject
//給外界快速生成單例對(duì)象使用
+(instancetype)sharedMusicTool;
@end

1.1.2 .m文件

@implementation MusicTool

//①定義全局靜態(tài)變量,用來(lái)存儲(chǔ)創(chuàng)建好的單例對(duì)象,當(dāng)外界需要時(shí),返回
static id _instance;

//②實(shí)現(xiàn)頭文件中的方法
+(instancetype)sharedMusicTool
{
    //避免每次線程過(guò)來(lái)都加鎖,首先判斷一次,如果為空才會(huì)繼續(xù)加鎖并創(chuàng)建對(duì)象
    if(_instance == nil)
    {
        //避免出現(xiàn)多個(gè)線程同時(shí)創(chuàng)建_instance,加鎖
        @synchronized(self)
        {   
            //使用懶加載,確保_instance 只創(chuàng)建一次
            if(_instance == nil)
            {   
                _instance = [[self alloc]init];
            }
        }
    }
    return _instance;
}

//③重寫 allocWithZone:方法---內(nèi)存與 sharedMusicTool方法體基本相同
+(instancetype)allocWithZone:(struct NSZone *)zone
{
    //避免每次線程過(guò)來(lái)都加鎖,首先判斷一次,如果為空才會(huì)繼續(xù)加鎖并創(chuàng)建對(duì)象
    if(_instance == nil)
    {
        //避免出現(xiàn)多個(gè)線程同時(shí)創(chuàng)建_instance,加鎖
        @synchronized(self)
        {   
            //使用懶加載,確保_instance 只創(chuàng)建一次
            if(_instance == nil)
            {   
                //調(diào)用父類方法,分配空間
                _instance = [super allocWithZone:zone];
            }
        }
    }
    return _instance;
}

//④重寫 copyWithZone:方法,避免實(shí)例對(duì)象的 copy 操作導(dǎo)致創(chuàng)建新的對(duì)象
-(instancetype)copyWithZone:(NSZone *)zone
{
    //由于是對(duì)象方法,說(shuō)明可能存在_instance對(duì)象,直接返回即可
    return _instance;
}

@end

1.1.3 代碼解釋

(1)為什么全局變量要使用 static?

① static修飾局部變量

  • 其生命周期與全局變量相同,直到程序結(jié)束,只有一份內(nèi)存空間
  • 作用域不變
    ② static修飾全局變量
  • 只有一份內(nèi)存空間
  • 全局變量,在其他文件中,可以通過(guò) extern id _instance來(lái)聲明,然后直接在其他文件中調(diào)用。用 static 修飾后 在其他文件不能通過(guò) extern id _instance 聲明后 引用
(2)加鎖且懶加載的原理

懶加載是為了,確保整個(gè)類只有一個(gè)_instance,做到單例
加鎖:多線程中,可能多個(gè)線程都發(fā)現(xiàn)當(dāng)前的_instance==nil,那么就會(huì)同時(shí)創(chuàng)建對(duì)象,不符合單例的原則,所以加鎖。但是加鎖容易引起效率降低,不能每次線程過(guò)來(lái)就加鎖,所以在加鎖之前首先判斷一次是否為空,不為空根本不需要?jiǎng)?chuàng)建,直接返回。為空則說(shuō)明可能需要?jiǎng)?chuàng)建對(duì)象,那么再加鎖。

1.2 GCD簡(jiǎn)化單例(ARC)

在allocWithZone方法和 sharedSoundTool中,每次需要判斷是否為空,然后加鎖,其目的是為了保證 [[self alloc]init]和[super allocWithZone:zone]代碼只執(zhí)行一次,那么可以使用 GCD 的一次性代碼解決,另外,GCD 一次性代碼是線程安全的,所以不需要我們自己來(lái)處理加鎖問(wèn)題。

//修改 sharedSoundTool 方法
+(instancetype)sharedSoundTool
{
    dispatch_once_t onceToken = NULL;
    dispatch_once(&onceToken)
    {
        _instance = [[self alloc]init];
    }
    return _instance;
}

//修改 allocWithZone 方法
+(instancetype)allocWithZone:(struct NSZone *)zone
{
    dispatch_once_t onceToken = NULL;
    dispatch_once(&onceToken)
    {
        _instance = [super allocWithZone:zone];
    }
    return _instance;
}

1.3 GCD 簡(jiǎn)化單例(MRC)

在 MRC 環(huán)境中,我們需要考慮如果創(chuàng)建出來(lái)的單例對(duì)象,被手動(dòng) release 了怎么辦?所以我們?cè)谠O(shè)計(jì)單例模式的時(shí)候,需要考慮這種情況。如下:

  • retain,單例對(duì)象創(chuàng)建后,全局只有一個(gè)對(duì)象,所以一定要保證 retain 后仍然是自身,且引用計(jì)數(shù)不變

  • release,由于只有一個(gè)對(duì)象,被 release 后不能被釋放掉,所以 release 操作需要攔截

  • autorelease,與 release 一樣

  • retainCount,始終保證引用計(jì)數(shù)器為1
    所以在 MRC 環(huán)境中,設(shè)計(jì)單例模式時(shí),還需要重寫下面四個(gè)方法

    //重寫 retain 方法,不作計(jì)數(shù)器加1的操作
    -(instancetype)retain
    {
    return _instance;
    }

    //重寫 release 方法,不做任何操作
    -(void)release
    {

    }

    //重寫 autorelease 方法,返回自身
    -(instancetype)autorelease
    {
    return _instance;
    }

    //重寫 retainCount 方法,返回1
    -(NSUInteger)retainCount
    {
    return 1;
    }

1.4 餓漢模式的單例(不常用)

單例通常被分為兩種模式:懶漢模式和餓漢模式。

懶漢模式

當(dāng)使用這個(gè)單例對(duì)象的時(shí)候,才創(chuàng)建對(duì)象,就是_instance 的懶加載形式。由于移動(dòng)設(shè)備內(nèi)存有限,所以這種方式最適合。

餓漢模式

當(dāng)類第一次加載的時(shí)候,就創(chuàng)建單例對(duì)象,并保存在_instance 中。由于第一次加載就創(chuàng)建,內(nèi)存從程序開(kāi)始運(yùn)行的時(shí)候就分配了,不適合移動(dòng)設(shè)備。

load和initilized 方法

load方法

①當(dāng)程序剛開(kāi)始運(yùn)行的時(shí)候,所有的類都會(huì)加載到內(nèi)存中(不管這個(gè)類有沒(méi)有使用),此時(shí)就會(huì)調(diào)用 load 方法            
②如果某種操作想要在程序運(yùn)行的過(guò)程中只執(zhí)行一次,那么這個(gè)操作就可以放到 load 方法中
③基于第二點(diǎn),我們的餓漢模式的單例對(duì)象創(chuàng)建就放在 load 方法中

initialized方法

①當(dāng)類第一次被使用的時(shí)候調(diào)用(比如,調(diào)用類的方法)。
②如果子類沒(méi)有重寫該方法,那么父類的initialized方法可能會(huì)被執(zhí)行多次。所以餓漢模式不能使用這個(gè)方法
.h文件
@interface SoundTool():NSObejct
//提供外界訪問(wèn)的方法
+(instancetype)sharedSoundTool;
@end
.m文件
@implementation SoundTool
//①定義靜態(tài)全局變量
static id _instance;
//②實(shí)現(xiàn)方法
+(instancetype)sharedSoundTool
{
    return _instance;
}
//③重寫load方法
+(void)load
{
    //不需要線程安全,類加載的時(shí)候線程還沒(méi)開(kāi)始呢
    _instance = [[self alloc]init];
}
//④重寫allocWithZone方法
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    if(_instance == nil)
    {
        _instance = [super allocWithZone:zone];
    }
    return _instance;
}

@end

1.5 ARC和MRC的適配

上面的單例的設(shè)計(jì),分為ARC和MRC環(huán)境。MRC較ARC多了關(guān)于retain、release相關(guān)的代碼。為了能夠用同一份代碼適配不同的環(huán)境,我們可以是用條件編譯指令。

#if __has_feature(objc_arc)
//ARC編譯環(huán)境

#else
//MRC編譯環(huán)境

-(instancetype)retain{return _instance;}
-(void)release{}
-(instancetype)autorelease{return _instance;}
-(NSUIngeter)retainCount{return 1;}

#endif

1.6 宏實(shí)現(xiàn)單例

由于單例的h文件和m文件一成不變,所以可以抽成宏定義。抽成宏定義需要注意
1 宏定義后面如果要替換字符,需要用##拼接

#define SoundToolH(name) +(instancetype)shared##name;
//調(diào)用宏定義SoundToolH(MusicTool)時(shí),就相當(dāng)于
+(instancetype)sharedMusicTool;

2 宏定義后邊如果出現(xiàn)換行,需要用符號(hào)“ \ ” 來(lái)標(biāo)記下一行也是宏定義的部分,但最后一行末尾不需要

#define SoundToolM(name) \
static id _instance;\
 +(instancetype)shared##name\
 {\
    dispatch_once_t onceToken = NULL;\
    dispatch_once(&onceToken)\
    {\
        _instance = [self alloc]init];\
    }\
 }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • (1)單例模式 在程序運(yùn)行過(guò)程,一個(gè)類只有一個(gè)實(shí)例 (2)使用場(chǎng)合 在整個(gè)應(yīng)用程序中,共享一份資源(這份資源只需要...
    奧斯卡先生閱讀 343評(píng)論 1 0
  • 單例模式作用 可以保證在程序運(yùn)行過(guò)程中,一個(gè)類只有一個(gè)實(shí)例,而且該實(shí)例易于供外界使用 從而方便地控制了實(shí)例個(gè)數(shù),并...
    珍此良辰閱讀 1,363評(píng)論 3 8
  • 一. 單例模式簡(jiǎn)介 單例模式的作用可以保證在程序運(yùn)行過(guò)程,一個(gè)類只有一個(gè)實(shí)例,而且該實(shí)例易于供外界訪問(wèn)從而方便地控...
    xx_cc閱讀 50,273評(píng)論 15 146
  • 原鏈接:http://www.lxweimin.com/p/4867dc92337e原作者:僅供我個(gè)人收藏學(xué)習(xí),原博...
    油菜花花花花閱讀 342評(píng)論 0 0
  • 2017年4月在朋友的推薦下,我結(jié)識(shí)了樊登讀書(shū)會(huì),它完全打破了我對(duì)“讀書(shū)”這兩個(gè)字的認(rèn)知。 我一直是一個(gè)超理想主義...
    丸物喪志閱讀 427評(píng)論 5 2