使用設(shè)計模式中的Singleton單例模式來開發(fā)iOS應(yīng)用程序
單例設(shè)計模式確切的說就是一個類只有一個實例,有一個全局的接口來訪問這個實例。當(dāng)?shù)谝淮屋d入的時候,它通常使用延時加載的方法創(chuàng)建單一實例。
提示:蘋果大量的使用了這種方法。例子:[NSUserDefaults standerUserDefaults], [UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager] 都返回一個單一對象。
你可能想知道你為什么要關(guān)心一個類有多個的實例。代碼和內(nèi)存都很便宜,不是嗎?
在一些情況下,一個類只有一個實例是有意義的。例如,這里沒有必要有多個登錄實例,除非你一次想寫入多個日志文件。或者,一個全局的配置類文件:它可以很容易的很安全的執(zhí)行一個公共資源,這樣的一個配置文件,要比同時修改多個配置類文件好很多。
如何使用單例模式
請看下面的圖片
上面的圖片顯示的是一個登錄類,它有一個屬性(這個單一實例),有兩個方法:sharedInstance 和 init。
首先一個客戶端(client)發(fā)送 sharedInstance 信息,但是屬性 instance 還沒有初始化,所以你要先給這個類創(chuàng)建一個實例。
然后你調(diào)用 sharedInstance,instance 會馬上返回初始化的值。這個邏輯最終只會返回一個實例。
你需要執(zhí)行這個模式來創(chuàng)建單例類來管理所有的專輯數(shù)據(jù)。
你需要注意在項目里有一個叫 API 文件夾,給你的 APP 提供服務(wù)的所有類都需要放在這里。在這個文件夾里用 iOS\Cocoa Touch\Object-C class 創(chuàng)建一個新類。類的名字叫 LibraryAPI,子類選擇 NSObject。
打開 LibraryAPI.h 文件用下面的代碼替換里面的內(nèi)容:
@interface LibraryAPI: NSObject
+ (LibraryAPI*)sharedInstance;
@end
現(xiàn)在打開 LibraryAPI.m 文件,在 @implentation 后面添加如下方法:
+ (LibraryAPI*)sharedInstance
{
// 1
static LibraryAPI *_sharedInstance = nil;
// 2
static dispatch_once_t oncePredicate;
// 3
dispatch_once(&nocePredicate, ^{
_sharedInstance = [[LibraryAPI alloc] init];
});
return _sharedInstance;
}
在這個短方法中做了這些事情:
在這個類中,聲明一個靜態(tài)變量來保存這個實例,保證它是一個全局可用的變量。
聲明一個靜態(tài)這是 dispatch_one_t,確保這些初始化代碼只能被執(zhí)行一次。
使用 Grand Central Dispatch(GCD)執(zhí)行一個 block 來初始化 LibraryAPI 實例。這是單例設(shè)計模式的關(guān)鍵所在:一個類只能被實例化一次。
接下來執(zhí)行 sharedInstance,在 dispatch_once block 里的代碼是不會被執(zhí)行的(當(dāng)它已經(jīng)被執(zhí)行過一次后),它會返回之前創(chuàng)建的 LibraryAPI 實例。
提示:想了解更多關(guān)于 GCD 和使用它,請點擊這里的教程 Multithreading and Grand Central Dispatch,如何使用 Blocks 在這里。
你現(xiàn)在有一個單例對象來管理專輯了。下一步就是創(chuàng)建一個類用來保存你的專輯數(shù)據(jù)了。
用 iOS\Cocoa Touch\Object-C class 在 API 文件夾下創(chuàng)建一個新的類,名字叫 PersistencyManager,子類選擇 NSObject。
打開 PersistencyManager.h,在頂部引入面文件:
#import "Album.h"
然后在 @interface 后面加入下面代碼:
- (NSArray *)getAlbums;
- (void)addAlbums:(Album*)album atIndex:(int)index;
- (void)deleteAlbumAtIndex:(int)index;
上面的三個方法都需要跟專輯的數(shù)據(jù)相結(jié)合。
打開 PersistencyManager.m,在 @implementation 上面添加如下代碼:
@interface PersistencyManager () {
NSMutableArray *albums;
}
上面的代碼是給類添加了一個擴展,這是另一種給類添加私有方法和私有屬性的方法,類外面的成員是看不到這些的。這里,你聲明了一個 NSMutableArray 來保存專輯的數(shù)據(jù)。這是一個可變數(shù)組,你可以很容易的添加和刪除專輯。
現(xiàn)在在 @implementation 下面添加實現(xiàn)代碼:
- (id)init {
self = [super init];
if (self) {
albums = [NSMutableArray arrayWithArray:@[[[Album alloc] initWithTitle:@"Best of Bowie" artist:@"David Bowie" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png" year:@"1992"],
[[Album alloc] initWithTitle:@"It's My Life" artist:@"No Doubt" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png" year:@"2003"],
[[Album alloc] initWithTitle:@"Nothing Like The Sun" artist:@"Sting" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png" year:@"1999"],
[[Album alloc] initWithTitle:@"Staring at the Sun" artist:@"U2" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png" year:@"2000"],
[[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png" year:@"2000"]]];
}
return self;
}
在 init 里你在數(shù)組中加入了 5 張專輯。如果上面的專輯你不喜歡,你可以隨意替換成你喜歡的。:]
現(xiàn)存在 PersistencyManager.m 添加下面三個方法:
- (NSArray*)getAlbums
{
return albums;
}
- (void)addAlbum:(Album*)album atIndex:(int)index
{
if (albums.count >= index)
[albums insertObject:album atIndex:index];
else
[albums addObject:album];
}
- (void)deleteAlbumAtIndex:(int)index
{
[albums removeObjectAtIndex:index];
}
這些方法是獲取,添加,刪除專輯。
Build 你的項目,確保所有的代碼都能正確編譯。
單例模式的使用場合
類只能有一個實例,并且必須從一個為人數(shù)值的訪問點對其訪問。
這個唯一的實例只能通過子類化進行拓展,并且拓展的對象不會破壞客戶端代碼。
在Objective-C中方法都是公有的,而且OC的語言本身是動態(tài)類型的,因此所有類都可以相互發(fā)送對方的消息。,并且Cocoa框架使用計數(shù)的內(nèi)存管理方式來維護對象的內(nèi)存中的生存期。
下面讓我們看一下OC當(dāng)中的單例模式的寫法,首先單例模式在ARC\MRC環(huán)境下的寫法有所不同,需要編寫2套不同的代碼
可以用宏判斷是否為ARC環(huán)境#if _has_feature(objc_arc)
#else
//MRC
#endif
單例模式- ARC -方法一
ARC中單例模式的實現(xiàn)
在 .m中保留一個全局的static的實例
static id _instance;
//重寫allocWithZone:方法,在這里創(chuàng)建唯一的實例(注意線程安全)
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized(self) {
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
提供1個類方法讓外界訪問唯一的實例
+ (instancetype)sharedInstanceTool{
@synchronized(self){
if(_instance == nil){
_instance = [[self alloc] init];
}
}
return _instance;
}
實現(xiàn)copyWithZone:方法
-(id)copyWithZone:(struct _NSZone *)zone{
return _instance;
}
我們在sharedInstanceTool,首先檢查類的唯一實例是否已經(jīng)創(chuàng)建,如果就會創(chuàng)建實例并將其返回。而之所以調(diào)用super而不是self,是因為已經(jīng)在self中重載了基本的對象分配的方法,需要借用父類的功能來幫助處理底層內(nèi)存的分配。
在allocWithZone:(struct _NSZone*)zone方法中,只是返回從sharedInstanceTool方法返回的類實例。而同樣的在Cocoa框架中調(diào)用allocWithZone:(struct _NSZone*)zone會分配內(nèi)存,引用計數(shù)會設(shè)置為1,然后返回實例。同樣的重寫(id)copyWithZone:(struct _NSZone *)zone方法,也是為了保證不會返回實例的副本,而是返回self.返回同一個實例。
方法二:
+(instancetype)sharedInstance {
static WMSingleton *singleton = nil;
if (! singleton) {
singleton = [[self alloc] initPrivate];
}
return singleton;
}
- (instancetype)init {
@throw [NSException exceptionWithName:@"這個是個單例"
reason:@"應(yīng)該這樣調(diào)用 [WMSingleton sharedInstance]"
userInfo:nil];
return nil;
}
//實現(xiàn)自己真正的私有初始化方法
- (instancetype)initPrivate {
self = [super init];
return self;
}
上面這段代碼中將singleton指針聲明為靜態(tài)變量。當(dāng)某個定義了靜態(tài)變量的方法返回時,程序不會釋放相應(yīng)的變量。####singleton變量的初始值是nil,當(dāng)程序第一次執(zhí)行sharedInstance方法時會創(chuàng)建一個對象,并將新創(chuàng)建的對象的地址賦值給singleton變量。當(dāng)徐成再次執(zhí)行sharedInstance方法時,無論多少次singleton變量仍然會指向最初那個創(chuàng)建的對象。因為指向?qū)ο蟮膕ingleton變量是強引用的,并且程序永遠(yuǎn)不會釋放該變量,所以singleton變量指向的對象也不會釋放。
線程安全。
上面的實例中我們通過@synchronized來添加了一個互斥鎖,以此來保證線程安全。而現(xiàn)在我們開始嘗試用線程的方式來實現(xiàn)一個加單的單例。
static WMObject *_instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
從上面的代碼我們可以看到,實現(xiàn)的思路基本上也是一致的我們在sharedInstanceTool,首先檢查類的唯一實例是否已經(jīng)創(chuàng)建,如果就會創(chuàng)建實例并將其返回。而略有不同的地方就是我們這次通過dispatch_once_t來保證線程的安全性。至于dispatch_once_t的用法這里就一一贅述了,線程的相關(guān)教程都會有其相關(guān)的描述。
到了這里一個簡單的單例模式基本實現(xiàn)完成了,那么我們可以嘗試著把它封裝到一個宏里,然后方便其以后的調(diào)用
創(chuàng)建一個WMSingleton.h
// .h文件
#define WMSingletonH(name) + (instancetype)shared##name;
// .m文件
#define WMSingletonM(name) \
static id _instance; \
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
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; \
}
使用方法
//.h類
//引入這個宏文件
#import "WMSingleton.h"
@interface WMObject : NSObject
WMSingletonH(object)
@end
//.m類
@implementation WMObject
WMSingletonM(Car)
@end
相關(guān)文章
iOS應(yīng)用中UITableView左滑自定義選項及批量刪除的實現(xiàn)
這篇文章主要介紹了iOS應(yīng)用中UITableView左滑自定義選項及批量刪除的實現(xiàn),UITableView列表中即通訊錄左滑呼出選項的那種效果在刪除時能夠?qū)崿F(xiàn)多行刪除將更加方便,需要的朋友可以參考下2016-03-03IOS AFNetworking的Post失敗及requestSerializer的正確使用
這篇文章主要介紹了IOS AFNetworking的Post失敗及requestSerializer的正確使用的相關(guān)資料,需要的朋友可以參考下2017-05-05iOS編程學(xué)習(xí)中關(guān)于throttle的那些事
這篇文章主要給大家介紹了關(guān)于iOS編程學(xué)習(xí)中throttle的那些事,文中通過示例代碼介紹的非常詳細(xì),對各位iOS的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12IOS 網(wǎng)絡(luò)請求中設(shè)置cookie
這篇文章主要介紹了IOS 網(wǎng)絡(luò)請求中設(shè)置cookie的相關(guān)資料,需要的朋友可以參考下2017-06-06詳解IOS的Automatically Sign在設(shè)備上打包
本篇教程主要給大家分享了IOS的Automatically Sign如何在設(shè)備上直接打包,有需要的朋友參考學(xué)習(xí)下。2018-01-01