你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS單例模式

iOS單例模式

編輯:IOS開發綜合

1 單例模式

它是一種設計模式(常見的設計模式有:觀察者模式、工廠模式、門面模式等)。單例設計模式中,一個類只有一個實例,只分配一次內存空間,節約內存等,特別適合在移動端使用。

實現單例的思路:

1 只能分配一次內存—-要攔截 alloc 方法
2 alloc 方法的底層是 allocWithZone 方法
3 每個類只有一個對象,需要有一個全局變量來存儲這個對象
4 需要考慮線程安全

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

1.1.1 .h文件

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

1.1.2 .m文件

@implementation MusicTool

//①定義全局靜態變量,用來存儲創建好的單例對象,當外界需要時,返回
static id _instance;

//②實現頭文件中的方法
+(instancetype)sharedMusicTool
{
    //避免每次線程過來都加鎖,首先判斷一次,如果為空才會繼續加鎖並創建對象
    if(_instance == nil)
    {
        //避免出現多個線程同時創建_instance,加鎖
        @synchronized(self)
        {   
            //使用懶加載,確保_instance 只創建一次
            if(_instance == nil)
            {   
                _instance = [[self alloc]init];
            }
        }
    }
    return _instance;
}

//③重寫 allocWithZone:方法---內存與 sharedMusicTool方法體基本相同
+(instancetype)allocWithZone:(struct NSZone *)zone
{
    //避免每次線程過來都加鎖,首先判斷一次,如果為空才會繼續加鎖並創建對象
    if(_instance == nil)
    {
        //避免出現多個線程同時創建_instance,加鎖
        @synchronized(self)
        {   
            //使用懶加載,確保_instance 只創建一次
            if(_instance == nil)
            {   
                //調用父類方法,分配空間
                _instance = [super allocWithZone:zone];
            }
        }
    }
    return _instance;
}

//④重寫 copyWithZone:方法,避免實例對象的 copy 操作導致創建新的對象
-(instancetype)copyWithZone:(NSZone *)zone
{
    //由於是對象方法,說明可能存在_instance對象,直接返回即可
    return _instance;
}

@end

1.1.3 代碼解釋

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

① static修飾局部變量
* 其生命周期與全局變量相同,直到程序結束,只有一份內存空間
* 作用域不變
② static修飾全局變量
* 只有一份內存空間
* 全局變量,在其他文件中,可以通過 extern id _instance來聲明,然後直接在其他文件中調用。用 static 修飾後 在其他文件不能通過 extern id _instance 聲明後 引用

(2)加鎖且懶加載的原理

懶加載是為了,確保整個類只有一個_instance,做到單例
加鎖:多線程中,可能多個線程都發現當前的_instance==nil,那麼就會同時創建對象,不符合單例的原則,所以加鎖。但是加鎖容易引起效率降低,不能每次線程過來就加鎖,所以在加鎖之前首先判斷一次是否為空,不為空根本不需要創建,直接返回。為空則說明可能需要創建對象,那麼再加鎖。

1.2 GCD簡化單例(ARC)

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

//修改 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 簡化單例(MRC)

在 MRC 環境中,我們需要考慮如果創建出來的單例對象,被手動 release 了怎麼辦?所以我們在設計單例模式的時候,需要考慮這種情況。如下:
* retain,單例對象創建後,全局只有一個對象,所以一定要保證 retain 後仍然是自身,且引用計數不變
* release,由於只有一個對象,被 release 後不能被釋放掉,所以 release 操作需要攔截
* autorelease,與 release 一樣
* retainCount,始終保證引用計數器為1
所以在 MRC 環境中,設計單例模式時,還需要重寫下面四個方法

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

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

}

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

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

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

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

懶漢模式

當使用這個單例對象的時候,才創建對象,就是_instance 的懶加載形式。由於移動設備內存有限,所以這種方式最適合。

餓漢模式

當類第一次加載的時候,就創建單例對象,並保存在_instance 中。由於第一次加載就創建,內存從程序開始運行的時候就分配了,不適合移動設備。

load和initilized 方法

load方法

①當程序剛開始運行的時候,所有的類都會加載到內存中(不管這個類有沒有使用),此時就會調用 load 方法            
②如果某種操作想要在程序運行的過程中只執行一次,那麼這個操作就可以放到 load 方法中
③基於第二點,我們的餓漢模式的單例對象創建就放在 load 方法中

initialized方法

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

@end

1.5 ARC和MRC的適配

上面的單例的設計,分為ARC和MRC環境。MRC較ARC多了關於retain、release相關的代碼。為了能夠用同一份代碼適配不同的環境,我們可以是用條件編譯指令。

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

#else
//MRC編譯環境

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

#endif

1.6 宏實現單例

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

#define SoundToolH(name) +(instancetype)shared##name;
//調用宏定義SoundToolH(MusicTool)時,就相當於
+(instancetype)sharedMusicTool;

2 宏定義後邊如果出現換行,需要用符號“ \ ” 來標記下一行也是宏定義的部分,但最後一行末尾不需要

#define SoundToolM(name) \
static id _instance;\
 +(instancetype)shared##name\
 {\
    dispatch_once_t onceToken = NULL;\
    dispatch_once(&onceToken)\
    {\
        _instance = [self alloc]init];\
    }\
 }
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved