iOS單例設(shè)計(jì)模式詳細(xì)講解(單例設(shè)計(jì)模式不斷完善的過程)

在iOS中有很多的設(shè)計(jì)模式,有一本書《Elements of Reusable Object-Oriented Software》(中文名字為《設(shè)計(jì)模式》)講述了23種軟件設(shè)計(jì)模式,這本書中的設(shè)計(jì)模式都是面向?qū)ο蟮模芏嗾Z言都有廣泛的應(yīng)用,在蘋果的開發(fā)中,當(dāng)然也會(huì)存在這些設(shè)計(jì)模式,我們所使用的無論是開發(fā)Mac OX系統(tǒng)的Cocoa框架還是開發(fā)iOS系統(tǒng)的Cocoa Touch框架,里面的設(shè)計(jì)模式也是由這23種設(shè)計(jì)模式演變而來。本文著重詳細(xì)介紹在開發(fā)iOS時(shí)采用的單例模式,從設(shè)計(jì)過程的演變和細(xì)節(jié)的完善進(jìn)行分析,相信大家能夠從中獲得重要的思路原理而不是僅僅知道應(yīng)該這么寫單例模式卻不知為何這么寫,當(dāng)然,理解透徹后,為了我們的開發(fā)效率,我們可以將單例模式的代碼封裝到一個(gè)類中然后定義成宏,適配于ARC和MRC模式,讓開發(fā)效率大大提高。這些操作在本文中都會(huì)一一講到,接下來就進(jìn)入正題。

在講述之前,先說明本文的層次結(jié)構(gòu),本文分成了5個(gè)部分,下面依次羅列
1、單例模式中懶漢式的實(shí)現(xiàn)
2、單例模式中餓漢式的實(shí)現(xiàn)
3、使用GCD代替手動(dòng)加鎖判斷處理
4、非ARC情況的單例模式
5、單例模式的代碼實(shí)用化(封裝便于開發(fā)直接使用)

前言:

所謂的單例模式,就是要實(shí)現(xiàn)在一個(gè)應(yīng)用程序中,對(duì)應(yīng)的一個(gè)類只會(huì)有一個(gè)實(shí)例,無論創(chuàng)建多少次,都是同一個(gè)對(duì)象。大家在開發(fā)過程中也見過不少的單例,比如UIApplication、UIAccelerometer(重力加速)、NSUserDefaults、NSNotificationCenter,當(dāng)然,這些是開發(fā)Cocoa Touch框架中的,在Cocoa框架中還有NSFileManager、NSBundle等。在iOS中,懶加載幾乎是無處不在的,其實(shí),懶加載在某種意義上也是采用了單例模式的思想(如果對(duì)象存在就直接返回,對(duì)象不存在就創(chuàng)建對(duì)象),那么本文就從大家熟悉的懶加載入手進(jìn)行講解(整個(gè)過程都用實(shí)際的代碼進(jìn)行說明)。

一、單例模式中懶漢式的實(shí)現(xiàn)

新建一個(gè)工程(本文是single view工程),創(chuàng)建一個(gè)繼承于NSObject的類,命名為NTMoviePlayer,首先我們嘗試下使用懶加載,在viewController里面導(dǎo)入NTMoviePlayer.h,定義一個(gè)NTMoviePlayer的對(duì)象,然后寫出懶加載代碼,這樣好像真的是可以做到在viewController里面只有一個(gè)NTMoviePlayer對(duì)象,但是如果又創(chuàng)建一個(gè)類,然后進(jìn)行同樣的操作,兩次創(chuàng)建的對(duì)象還會(huì)是一樣的嗎?答案很明顯,不一樣,我們可以從這個(gè)現(xiàn)象去推導(dǎo)問題發(fā)生的根本原因,那就是在不同的類中,創(chuàng)建NTMoviePlayer對(duì)象的時(shí)候都會(huì)進(jìn)行一個(gè)alloc操作,那么這個(gè)alloc實(shí)際上就是分配內(nèi)存空間的一個(gè)操作,分配了不同的內(nèi)存區(qū)域,那么當(dāng)然創(chuàng)建了不同的對(duì)象,所以,如果要保證應(yīng)用中就只有一個(gè)對(duì)象,就應(yīng)該讓NTMoviePlayer類的alloc方法只會(huì)進(jìn)行一次內(nèi)存空間的分配。這樣,找到了問題所在,就去實(shí)現(xiàn)代碼,重寫alloc方法,這里提供了兩種方法,一種是alloc,一種是allocWithZone方法,其實(shí)在alloc調(diào)用的底層也是allocWithZone方法,所以在此,我們需要重寫allocWithZone方法:

id moviePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    if (moviePlayer == nil) {
        // 調(diào)用super的allocWithZone方法來分配內(nèi)存空間
        moviePlayer = [super allocWithZone:zone];
    }
    return moviePlayer;
}

在這里我們初步使用懶加載來控制保證只有一個(gè)單例,但是這種僅僅適合在單一線程中使用的情況,要是涉及到了多線程的話,那么就會(huì)出現(xiàn)這樣的情況,當(dāng)一個(gè)線程走到了if判斷時(shí),判斷為空,然后進(jìn)入其中去創(chuàng)建對(duì)象,在還沒有返回的時(shí)候,另外一條線程又到了if判斷,判斷仍然為空,于是又進(jìn)入進(jìn)行對(duì)象的創(chuàng)建,所以這樣的話就保證不了只有一個(gè)單例對(duì)象。于是,我們對(duì)代碼進(jìn)行手動(dòng)加鎖。

id moviePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    // 在這里加一把鎖(利用本類為鎖)進(jìn)行多線程問題的解決
    @synchronized(self){
        if (moviePlayer == nil) {
            // 調(diào)用super的allocWithZone方法來分配內(nèi)存空間
            moviePlayer = [super allocWithZone:zone];
        }
    }
    return moviePlayer;
}

這樣的話,就可以解決上述問題,但是,每一次進(jìn)行alloc的時(shí)候都會(huì)加鎖和判斷鎖的存在,這一點(diǎn)是可以進(jìn)行優(yōu)化的(在java中也有對(duì)于這種情況的處理),于是在加鎖之前再次進(jìn)行判斷,修改代碼如下:

id moviePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    // 在這里判斷,為了優(yōu)化資源,防止多次加鎖和判斷鎖
    if (moviePlayer == nil) {
        // 在這里加一把鎖(利用本類為鎖)進(jìn)行多線程問題的解決
        @synchronized(self){
            if (moviePlayer == nil) {
                // 調(diào)用super的allocWithZone方法來分配內(nèi)存空間
                moviePlayer = [super allocWithZone:zone];
            }
        }
    }
    return moviePlayer;
}

到此,在allocWithZone方法中的代碼基本完善,接著,在我們進(jìn)行開發(fā)中,也時(shí)常會(huì)使用到很多單例,我們?cè)趧?chuàng)建單例的時(shí)候都不是使用的alloc和init,而是使用的shared加上變量名這種創(chuàng)建方式,所以,我們自己寫單例的話,也應(yīng)該向外界暴露這個(gè)方法。在.h文件中先聲明下方法

+ (instancetype)sharedMoviePlayer;
然后在.m文件中實(shí)現(xiàn),邏輯上和allocWithZone方法是一樣的
+ (instancetype)sharedMoviePlayer
{
    if (moviePlayer == nil) {
        @synchronized(self){
            if (moviePlayer == nil) {
                // 在這里寫self和寫本類名是一樣的
                moviePlayer = [[self alloc]init];
            }
        }
    }
    return moviePlayer;
}

這個(gè)對(duì)外暴露的方法完成之后,我們還需要注意一點(diǎn),在使用copy這個(gè)語法的時(shí)候,是能夠創(chuàng)建新的對(duì)象的,如果使用copy創(chuàng)建出新的對(duì)象的話,那么就不能夠保證單例的存在了,所以我們需要重寫copyWithZone方法,如果直接在.m文件中敲的話,會(huì)發(fā)現(xiàn)沒有提示,這是沒有聲明協(xié)議的原因,可以在.h文件中聲明NSCopying協(xié)議,然后重寫copyWithZone方法:

- (id)copyWithZone:(NSZone *)zone
{
    return moviePlayer;
}

在這里沒有像上面兩個(gè)方法一樣實(shí)現(xiàn)邏輯是因?yàn)椋菏褂胏opy的前提是必須現(xiàn)有一個(gè)對(duì)象,然后再使用,所以既然都已經(jīng)創(chuàng)建了一個(gè)對(duì)象了,那么全局變量所代表的對(duì)象也就是這個(gè)單例,那么在copyWithZone方法中直接返回就好了
到這里,基本的代碼差不多都寫好了,還需要處理一些細(xì)節(jié),首先,我們所聲明的全局變量是沒有使用static來修飾的,大家在開發(fā)過程中所遇見到的全局變量很多都是使用了static來修飾的,這里進(jìn)行一個(gè)小插曲,簡(jiǎn)要說明下static的使用,有兩種用法
1、static修飾局部變量:
簡(jiǎn)要來說,如果修飾了局部變量的話,那么這個(gè)局部變量的生命周期就和不加static的全局變量一樣了(也就是只有一塊內(nèi)存區(qū)域,無論這個(gè)方法執(zhí)行多少次,都不會(huì)進(jìn)行內(nèi)存的分配),不同的在于作用域仍然沒有改變
2、static修飾全局變量(這點(diǎn)是我們應(yīng)該注意的):
如果不適用static的全局變量,我們可以在其他的類中使用extern關(guān)鍵字直接獲取到這個(gè)對(duì)象,可想而知,在我們所做的單例模式中,如果在其他類中利用extern拿到了這個(gè)對(duì)象,進(jìn)行一個(gè)對(duì)象銷毀,例如:

extern id moviePlayer;
moviePlayer = nil;

這時(shí)候在這句代碼之前創(chuàng)建的單例就銷毀了,再次創(chuàng)建的對(duì)象就不是同一個(gè)了,這樣就無法保證單例的存在

所以對(duì)于全局變量的定義,需要加上static修飾符,到此,懶漢式的單例模式就寫好了(非ARC和GCD模式在后面討論),下面給出整合代碼

#import "NTMoviePlayer.h"
@implementation NTMoviePlayer
static id moviePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    // 在這里判斷,為了優(yōu)化資源,防止多次加鎖和判斷鎖
    if (moviePlayer == nil) {
        // 在這里加一把鎖(利用本類為鎖)進(jìn)行多線程問題的解決
        @synchronized(self){
            if (moviePlayer == nil) {
                // 調(diào)用super的allocWithZone方法來分配內(nèi)存空間
                moviePlayer = [super allocWithZone:zone];
            }
        }
    }
    return moviePlayer;
}
+ (instancetype)sharedMoviePlayer
{
    if (moviePlayer == nil) {
        @synchronized(self){
            if (moviePlayer == nil) {
                // 在這里寫self和寫本類名是一樣的
                moviePlayer = [[self alloc]init];
            }
        }
    }
    return moviePlayer;
}
- (id)copyWithZone:(NSZone *)zone
{
    return moviePlayer;
}
@end

二、單例模式中餓漢式的實(shí)現(xiàn)
在第一個(gè)模塊中,我們進(jìn)行了單例模式中懶漢式的詳細(xì)說明,也從懶漢式的模式中知道了實(shí)現(xiàn)單例模式的思路,但懶漢式和餓漢式還是有很大的區(qū)別,不過是從實(shí)現(xiàn)原理,和代碼操作上。在這里先介紹懶漢式和餓漢式和特點(diǎn)(其實(shí)這兩個(gè)名字都是很形象的)
1、懶漢式:實(shí)現(xiàn)原理和懶加載其實(shí)很像,如果在程序中不使用這個(gè)對(duì)象,那么就不會(huì)創(chuàng)建,只有在你使用代碼創(chuàng)建這個(gè)對(duì)象,才會(huì)創(chuàng)建。這種實(shí)現(xiàn)思想或者說是原理都是iOS開發(fā)中非常重要的,所以,懶漢式的單例模式也是最為重要的,是開發(fā)中最常見的。
2、餓漢式:在沒有使用代碼去創(chuàng)建對(duì)象之前,這個(gè)對(duì)象已經(jīng)加載好了,并且分配了內(nèi)存空間,當(dāng)你去使用代碼創(chuàng)建的時(shí)候,實(shí)際上只是將這個(gè)原本創(chuàng)建好的對(duì)象拿出來而已。
接下來介紹的就是餓漢式:
剛剛在分析餓漢式和懶漢式的特點(diǎn)時(shí)提到過,餓漢式是在使用代碼去創(chuàng)建對(duì)象之前就已經(jīng)創(chuàng)建好了對(duì)象,這里提到的使用代碼去創(chuàng)建對(duì)象實(shí)際上就是用alloc或者是對(duì)外暴露的shared方法,最根本上是調(diào)用了alloc方法,所以,換句話說,餓漢式也就是在我們手動(dòng)寫代碼去alloc之前就已經(jīng)將對(duì)象創(chuàng)建完畢了。此時(shí)我們就要思考了,什么方法能夠?qū)崿F(xiàn)這樣的效果呢?這里介紹兩個(gè)方法,第一個(gè)是load方法,第二個(gè)是initialize方法
1、load方法:當(dāng)類加載到運(yùn)行環(huán)境中的時(shí)候就會(huì)調(diào)用且僅調(diào)用一次,同時(shí)注意一個(gè)類只會(huì)加載一次(類加載有別于引用類,可以這么說,所有類都會(huì)在程序啟動(dòng)的時(shí)候加載一次,不管有沒有在目前顯示的視圖類中引用到)
2、initialize方法:當(dāng)?shù)谝淮问褂妙惖臅r(shí)候加載且僅加載一次
我們以load方法作為示范,在工程中再次創(chuàng)建一個(gè)新的類NTMusicPlayer,做一些基本的相同操作,在.h文件中暴露出sharedMusicPlayer方法,在.m文件中利用static定義一個(gè)全局變量musicPlayer,接著我們需要寫出load方法

+ (void)load
{
    musicPlayer = [[self alloc]init];
}

接著我們?nèi)匀恍枰貙慳llocWithZone方法,因?yàn)樵趌oad方法中是用alloc來創(chuàng)建對(duì)象,分配內(nèi)存空間的,但是在餓漢式中的邏輯就和在懶漢式中的邏輯有所區(qū)別了

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    if (musicPlayer == nil) {
        musicPlayer = [super allocWithZone:zone];
    }
    return musicPlayer;
}

在這里,我們可以發(fā)現(xiàn)有簡(jiǎn)潔了很多,去掉了多線程的問題的加鎖方案,我們來分析下原因,首先,在類被加載的時(shí)候會(huì)調(diào)用且僅調(diào)用一次load方法,而load方法里面又調(diào)用了alloc方法,所以,第一次調(diào)用肯定是創(chuàng)建好了對(duì)象,而且這時(shí)候不會(huì)存在多線程問題。當(dāng)我們手動(dòng)去使用alloc的時(shí)候,無論如何都過不了判斷,所以也不會(huì)存在多線程的問題了。接下來需要實(shí)現(xiàn)shareMusicPlayer方法和copy方法

+ (instancetype)sharedMusicPlayer
{
    return musicPlayer;
}
- (id)copyWithZone:(NSZone *)zone
{
    return musicPlayer;
}

代碼又變簡(jiǎn)單,這里連判斷都不用加,是因?yàn)槲覀兪褂胹hareMusicPlayer方法和copy的時(shí)候必然全局變量是有值的,而alloc方法中不直接返回是因?yàn)樵趌oad方法中調(diào)用了它,需要去創(chuàng)建一個(gè)對(duì)象
到這里,餓漢式的講解也完成,下面是整合代碼

#import "NTMusicPlayer.h"
@implementation NTMusicPlayer
static id musicPlayer;
+ (void)load
{
    musicPlayer = [[self alloc]init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    if (musicPlayer == nil) {
        musicPlayer = [super allocWithZone:zone];
    }
    return musicPlayer;
}
+ (instancetype)sharedMusicPlayer
{
    return musicPlayer;
}
- (id)copyWithZone:(NSZone *)zone
{
    return musicPlayer;
}
@end
三、使用GCD代替手動(dòng)加鎖判斷處理

再次新建一個(gè)類NTPicturePlayer,這里將詳細(xì)說明適用GCD中的方法來代替我們手動(dòng)加鎖的情況,還是依照慣例,在.h文件中聲明shared方法,然后在.m文件中使用static定義一個(gè)全局變量,首先,重寫alloc方法

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        picturePlayer = [[super alloc]init];
    });
    return picturePlayer;
}

dispatch_once方法是已經(jīng)在方法的內(nèi)部解決了多線程問題的,所以我們不用再去加鎖(開始定義了一個(gè)static常量,這句代碼不是自己寫的,敲dispatch_once有個(gè)提示的方法就會(huì)自動(dòng)生成),dispatch_once在宏觀上面表示內(nèi)部方法只會(huì)執(zhí)行一次。接著是sharedPicturePlayer方法

+ (instancetype)sharedPicturePlayer
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        picturePlayer = [[self alloc]init];
    });
    return picturePlayer;
}

最后是copy方法的重寫

- (id)copyWithZone:(NSZone *)zone
{
    return picturePlayer;
}

這樣的話,GCD版的單例模式(這里是懶漢模式為例)就做好了,下面是整合代碼:

#import "NTPicturePlayer.h"
@implementation NTPicturePlayer
static id picturePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        picturePlayer = [[super alloc]init];
    });
    return picturePlayer;
}
+ (instancetype)sharedPicturePlayer
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        picturePlayer = [[self alloc]init];
    });
    return picturePlayer;
}
- (id)copyWithZone:(NSZone *)zone
{
    return picturePlayer;
}
@end

可以看出,GCD版本的單例模式比我們之前手動(dòng)進(jìn)行加鎖的單例模式要簡(jiǎn)單很多,因此在實(shí)際開發(fā)中GCD版本的單例模式也是使用最多的

四、非ARC情況的單例模式

我們知道,在MRC模式也就是非ARC模式中,我們是需要手動(dòng)去管理內(nèi)存的,因此,我們可以使用release去將一個(gè)對(duì)象手動(dòng)銷毀,那么這樣的話,我們的創(chuàng)建出來的單例對(duì)象也可以被很輕易的銷毀。所以在非ARC情況下的單例模式,我們將著重將目光放到內(nèi)存管理的方法上去,首先我們可以先思考下,有哪些方法是用來進(jìn)行內(nèi)存管理的。這里就列舉出來了:release、retain、retainCount、autorelease。下面就分別進(jìn)行重寫并說明(以上述GCD版為例):
1、首先是release方法
我們是不希望將我們的單例對(duì)象進(jìn)行銷毀掉的,那么很簡(jiǎn)單,重寫release(需要將環(huán)境變?yōu)镸RC,不然使用這些方法會(huì)報(bào)錯(cuò))

- (oneway void)release
{
    
}

括號(hào)中的返回值是系統(tǒng)生成的,我們只需要將這個(gè)方法重寫,然后不在里面寫代碼就可以了
2、retain方法
在這里面只需要返回這個(gè)單利本身就好了,不對(duì)引用計(jì)數(shù)做任何處理

- (instancetype)retain
{
    return picturePlayer;
}

3、retainCount方法
這個(gè)方法返回的是對(duì)象的引用計(jì)數(shù),我們已經(jīng)重寫了retain方法,不希望改變單例對(duì)象的引用計(jì)數(shù),所以在這里返回1就好了

- (NSUInteger)retainCount
{
    return 1;
}

4、autorelease方法
對(duì)這個(gè)方法的處理和retain方法類似,我們只需要將對(duì)象本身返回,不需要進(jìn)行自動(dòng)釋放池的操作

- (instancetype)autorelease
{
    return picturePlayer;
}

這樣一來,在非ARC下的單例模式就寫好了,下面是整合代碼:

#import "NTPicturePlayer.h"
@implementation NTPicturePlayer
static id picturePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        picturePlayer = [[super alloc]init];
    });
    return picturePlayer;
}
+ (instancetype)sharedPicturePlayer
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        picturePlayer = [[self alloc]init];
    });
    return picturePlayer;
}
- (id)copyWithZone:(NSZone *)zone
{
    return picturePlayer;
}
- (oneway void)release
{
    
}
- (instancetype)retain
{
    return picturePlayer;
}
- (NSUInteger)retainCount
{
    return 1;
}
- (instancetype)autorelease
{
    return picturePlayer;
}
@end
五、單例模式的代碼實(shí)用化(封裝便于開發(fā)直接使用)

我們或許會(huì)有這樣的思路,將單例類放到工程中,然后讓需要實(shí)現(xiàn)單例的類都繼承于這個(gè)類,這個(gè)想法表面上是不錯(cuò)的,但是深入一點(diǎn)去研究的話,就會(huì)發(fā)現(xiàn),這個(gè)單例類的所有子類所創(chuàng)建出來的單例都是一樣的,這就未免不可行了,造成這個(gè)的原因是:在子類創(chuàng)建單例對(duì)象,實(shí)際上最根本上是調(diào)用了父類的alloc方法,而在父類中,只會(huì)存在一次創(chuàng)建對(duì)象,創(chuàng)建之后則是直接返回了創(chuàng)建好的那個(gè)單例。通俗來說,當(dāng)一個(gè)子類創(chuàng)建單例對(duì)象的時(shí)候,調(diào)用到了父類的創(chuàng)建方法,獲取到了這個(gè)單例對(duì)象,但如果第二個(gè)子類再創(chuàng)建單例對(duì)象,調(diào)用到父類的創(chuàng)建方法,這時(shí)候進(jìn)行的操作不再是創(chuàng)建新的對(duì)象,而是返回第一個(gè)子類創(chuàng)建的對(duì)象。所以,這種利用繼承關(guān)系來簡(jiǎn)化的方法是不可取的。
那么這個(gè)時(shí)候我們便可以考慮利用宏定義來進(jìn)行代碼的簡(jiǎn)化,因?yàn)槲覀儽容^剛剛寫的三個(gè)單例類來說,代碼有很大的相似度,我們可以抽取這些代碼將他們定義成宏。在工程中創(chuàng)建一個(gè)專門放置宏的.h文件,創(chuàng)建方法是,新建文件->在iOS模塊中選擇Other->Empty->在Save As中填寫類的名字,但是要記著加后綴.h->最后點(diǎn)擊Create
這時(shí)候我們需要去分析下應(yīng)該怎么去抽出代碼,畢竟從剛剛所寫的三個(gè)類還是有些差別,通過比較我們可以發(fā)現(xiàn),有這些地方是不同的
在.h文件中:shared后面的名字是不同的
在.m文件中:定義的全局變量名字是不同的,shared后面的名字是不同的
所以我們不能夠在宏中將這幾個(gè)地方固定下來,可以發(fā)現(xiàn)這幾個(gè)地方的名字都是和單例類的名字是有聯(lián)系的,這里我們可以使用括號(hào)和#的關(guān)聯(lián)作用來書寫宏定義

// .h文件代碼
#define NTSingletonH(name) + (instancetype)shared##name;
// .m文件代碼
#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        instance = [[super alloc]init];\
    });\
    return instance;\
}\
+ (instancetype)shared##name\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        instance = [[self alloc]init];\
    });\
    return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
    return instance;\
}

相比之前做了一些細(xì)節(jié)的優(yōu)化,首先將全局變量的名字改為了instance,這樣對(duì)于所有的類都是可以共用的,然后利用了括號(hào)和#號(hào)的聯(lián)系來使宏定義變的靈活,我們使用的時(shí)候在宏定義的括號(hào)中敲出我們的單例對(duì)象名字就好了(注意由于name這個(gè)屬性是直接拼接在了shared后面,所以我們?cè)诶ㄌ?hào)中寫單例的名字的時(shí)候應(yīng)該將首字母大寫),最后要注意一點(diǎn)細(xì)節(jié),對(duì)于很大一段代碼,直接放到宏中是不能夠識(shí)別的,所以這里我們需要使用 \ 這個(gè)符號(hào),這個(gè)符號(hào)表示后面的一段是屬于宏定義中的,所以我們?cè)诿織l代碼前面都添加上了這個(gè)符號(hào)。
這是ARC情況下的單例模式,那么在非ARC情況下的單例模式我們也要將其定義出來,再次用上述方法創(chuàng)建一個(gè).h文件NTSingleton_MRC.h

// .h文件代碼
#define NTSingletonH(name) + (instancetype)shared##name;

// .m文件代碼
#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        instance = [[super alloc]init];\
    });\
    return instance;\
}\
+ (instancetype)shared##name\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        instance = [[self alloc]init];\
    });\
    return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
    return instance;\
}\
- (oneway void)release\
{\
}\
- (instancetype)retain\
{\
    return instance;\
}\
- (NSUInteger)retainCount\
{\
    return 1;\
}\
- (instancetype)autorelease\
{\
    return instance;\
}

這樣,基本都做好的封裝,但是有些人仍然覺得帶上兩個(gè)類很麻煩,可不可以將兩個(gè)類封裝成一個(gè)類,當(dāng)然可以的,我們可以通過條件編譯來進(jìn)行處理,這里簡(jiǎn)要說明下條件編譯,條件編譯類似于if else的工作,但是原則上是有很大的不同,if else是在運(yùn)行時(shí)進(jìn)行處理,而條件編譯是在編譯時(shí)就進(jìn)行處理,也就是說,使用條件編譯,可以去在編譯的時(shí)候檢查環(huán)境是MRC還是ARC,然后跳轉(zhuǎn)到相應(yīng)的代碼進(jìn)行執(zhí)行.。
說到這里,可能會(huì)想到對(duì)于MRC和ARC兩個(gè)封裝類來說,不同的地方就只是在于MRC添加了4個(gè)方法而已,那么我們就可以這樣做,使用條件編譯將這四個(gè)方法包裝起來,檢測(cè)到ARC的時(shí)候就不執(zhí)行。這種想法是好的,但是在宏定義中卻不是那么實(shí)際,因?yàn)樵诤甓x中#是有特殊的作用的,如果隨意亂使用#,就會(huì)報(bào)錯(cuò),所以我們還是老老實(shí)實(shí)在判斷中寫完兩套代碼吧,下面給出整個(gè)代碼(整個(gè)代碼可以收集,自己做一個(gè)類)

// .h文件的代碼
#define NTSingletonH(name) + (instancetype)shared##name;
// .m文件中的代碼(使用條件編譯來區(qū)別ARC和MRC)
#if __has_feature(objc_arc)

#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[super alloc]init];\
});\
return instance;\
}\
+ (instancetype)shared##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[self alloc]init];\
});\
return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return instance;\
}

#else

#define NTSingletonM(name)\
static id instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[super alloc]init];\
});\
return instance;\
}\
+ (instancetype)shared##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance = [[self alloc]init];\
});\
return instance;\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return instance;\
}\
- (oneway void)release\
{\
}\
- (instancetype)retain\
{\
return instance;\
}\
- (NSUInteger)retainCount\
{\
return 1;\
}\
- (instancetype)autorelease\
{\
return instance;\
}

#endif

如果你看到了這里,我想,你對(duì)單例模式的掌握應(yīng)該更深了-

參考資料
  • 《大話設(shè)計(jì)模式》
  • 《Objective-C編程之道-iOS設(shè)計(jì)模式解析》
  • 官方文檔
最后編輯于
?著作權(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ù)。

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

  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡(jiǎn)單、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮?jiǎn)潔易懂,是項(xiàng)目中最...
    成熱了閱讀 4,298評(píng)論 4 34
  • 1 單例模式的動(dòng)機(jī) 對(duì)于一個(gè)軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個(gè)實(shí)例。舉個(gè)大家都熟知的例子——Windows任務(wù)...
    justCode_閱讀 1,477評(píng)論 2 9
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,212評(píng)論 30 472
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,973評(píng)論 19 139
  • Fresco學(xué)習(xí)中文地址:Fresco中文學(xué)習(xí) Fresco Javadoc地址:Javadoc Fresco初始...
    TragedyGo閱讀 4,524評(píng)論 2 10