單例在我們開發中是最常用的設計模式,在iOS中也是如此。單例可以保證某個類的實例在程序中是唯一的,便于進行資源和數據的共享。使用的設計原則是單一職責原則。我們來看看在iOS中本身自帶的類或者方法哪些使用了單例的模式:
(1)UIAccelerometer類和sharedAccelerometer方法,一般如果方法名中有shared這樣的詞,就可以認為這是一個可以整個應用程序共享的實例變量,一般是使用了單例。
(2)UIApplication類和sharedApplication方法,我們一般使用該方法來創建全局變量。
(3)NSBundle類和mainBundle方法。
(4)NSFileManager類和defaultManager方法。
(5)NSNotificationCenter類和defaultManager方法。其中NSNotificationCenter也實現了觀察者模式。
(6)NSUserDefaults類和defaultUser方法。
示例代碼上傳至:[https://github.com/chenyufeng1991/iOS-Singleton](https://github.com/chenyufeng1991/iOS-Singleton) 。
【單例實現】
(1)新建一個普通的類,假設名字為Singleton. 在Singleton.h中聲明一個類方法,到時候使用該類方法(注意:一定是類方法,而不是實例方法)可以創建該類的唯一的一個實例:
~~~
[objc] view plaincopyprint?
#import <Foundation/Foundation.h>
@class Singleton;
@interface Singleton : NSObject
// "+" 表示類的方法,由類調用
+(Singleton *)sharedInstance;
@end
(2)在Singleton.m中需要實現sharedInstance方法和你其他的業務邏輯:
[objc] view plaincopyprint?
#import "Singleton.h"
// 用static聲明一個類的靜態實例;
static Singleton *_sharedInstance = nil;
@implementation Singleton
/**
* 1.使用類方法生成這個類唯一的實例;
*/
+(Singleton *)sharedInstance{
if (!_sharedInstance) {
_sharedInstance =[[self alloc]init];
}
return _sharedInstance;
}
@end
~~~
注意:一定要聲明一個static的靜態變量。以后創建類的唯一實例就使用sharedInstance方法,而不是使用alloc ,init.
(3)我們使用一個簡單的demo來演示一下單例:
~~~
[objc] view plaincopyprint?
#import "RootVC.h"
#import "Singleton.h"
@interface RootVC ()
@end
@implementation RootVC
- (void)viewDidLoad
{
[super viewDidLoad];
[self testSigleTon];
}
-(void)testSigleTon
{
//單例的結果就是,調用類方法,只返回一個共有的對象
/**
* single和single2是同一個對象;
因為返回的數據是一個靜態變量,全局唯一;
*/
Singleton *single = [Singleton sharedInstance];
Singleton *single2 = [Singleton sharedInstance];
if (single == single2) {
NSLog(@"single == single2");
}
NSLog(@"single地址:%@",single);
NSLog(@"single2地址:%@",single2);
}
@end
~~~

可以看到,兩個對象的內存地址是一樣的,表示這兩個對象其實是同一個對象,單例也就實現了。這是單例最普遍也是最簡單的實現方式,在項目中會經常用到,在不涉及多線程的情況下是完全正確的。但是,我們再多想一想,在多線程開發中,這種實現方式是否安全呢?那么應該如何實現。
#### 【單例架構】
在項目開發中,如果我們像上述實現方法一樣,在每個類中都使用這樣寫一個方法來生成單例,會不會顯得很麻煩,很冗余。這樣重復在每個類中重復寫代碼不利于開發與架構,那么我們應該使用什么方法來進行代碼抽取呢?解決方案就是使用類別(Category)。關于Category類別的簡要介紹,請參考《Objective-C——類別(Category)詳解》。具體的實現如下:
(1)新建一個Category,作為對NSObject類的擴展。因為NSObject類是大部分iOS類的基類,如果使用Category為NSObject增加額外方法(shareInstance方法),那么所有繼承自NSObject的類都可以使用該方法。我們常用的UIViewController和UIView都是從NSObject繼承的,這樣就會很方便。
需要在NSObject+Singleton.h頭文件中對外暴露一個生成實例的方法,供其他類調用。
~~~
[objc] view plaincopyprint?
#import <Foundation/Foundation.h>
@interface NSObject (Singleton)
// "+" 表示類的方法,由類調用
+ (instancetype)sharedInstance;
@end
~~~
(3)在NSObject+Singleton.m中需要實現上述方法,用來生成某一個類的唯一實例。我這里使用字典來存儲某一個類和該類的實例,也就是鍵值對的形式:鍵是類名,值是對象。根據類名去檢索該類的對象是否已經被創建,如果檢索到類名,表示已經被創建,則直接返回對象;如果沒有檢索到類名,則需要創建,創建完成后也存儲到字典中;
~~~
[objc] view plaincopyprint?
#import "NSObject+Singleton.h"
@implementation NSObject (Singleton)
//使用可變字典存儲每個類的單一實例,鍵為類名,值為該類的對象;
//聲明為靜態變量,可以保存上次的值;
static NSMutableDictionary *instanceDict;
id instance;
+ (instancetype)sharedInstance {
@synchronized(self)
{
//初始化字典;
if (instanceDict == nil) {
instanceDict = [[NSMutableDictionary alloc] init];
}
//獲取類名;
NSString *className = NSStringFromClass([self class]);
if (className) {
//查找字典中該類的對象,使用類名去進行查找,可以確保一個類只被存儲一次;
instance = instanceDict[className];
if (instance == nil) {
//該類的對象還沒實例化,就進行初始化,并根據鍵值對的形式存儲;
instance = [[self.class alloc] init];
[instanceDict setValue:instance forKey:className];
}else{
//該類對象已經存儲在字典中,直接返回instance即可;
}
}else{
//沒有獲取類名,所以確保sharedInstance是一個類方法,用類進行調用;
}
return instance;
}
}
@end
~~~
(4)單例的category已經寫完,下面將要進行測試。我這里的測試方法如下:兩個界面之間進行跳轉并返回,使用相同的代碼生成類對象;同時新建一個Person類和StudentModel類來測試,兩個類都分別繼承自NSObject,里面沒有任何實現,只用來創建對象。別忘了導入頭文件#import “NSObject+Singleton.h”.
第一個界面ViewController.m實現如下:
~~~
[objc] view plaincopyprint?
#import "ViewController.h"
#import "NSObject+Singleton.h"
#import "Person.h"
#import "StudentModel.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:true];
//使用sharedInstance創建類對象;
ViewController *vc1 = [ViewController sharedInstance];
ViewController *vc2 = [ViewController sharedInstance];
NSLog(@"ViewController---vc1地址:%@",vc1);
NSLog(@"ViewController---vc2地址:%@",vc2);
if (vc1 == vc2) {
NSLog(@"ViewController---vc1 == vc2");
}
//循環創建5個Person對象,5個對象都相同;
for (int i = 0; i < 5; i++) {
Person *per1 = [Person sharedInstance];
NSLog(@"ViewController---per1地址:%@",per1);
}
//使用alloc創建對象,每個對象都是不同的;
for (int i = 0; i < 5; i++) {
StudentModel *stud = [[StudentModel alloc] init];
NSLog(@"ViewController---stud地址:%@",stud);
}
}
@end
~~~
第二個界面SecondViewController.m實現如下:
~~~
[objc] view plaincopyprint?
#import "SecondViewController.h"
#import "NSObject+Singleton.h"
#import "Person.h"
#import "StudentModel.h"
#import "ViewController.h"
@interface SecondViewController ()
@end
@implementation SecondViewController
/**
* 在另一個界面中做同樣的測試;
*/
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
SecondViewController *secondVc1 = [SecondViewController sharedInstance];
SecondViewController *secondVc2 = [SecondViewController sharedInstance];
NSLog(@"SecondViewController---secondVc1地址:%@",secondVc1);
NSLog(@"SecondViewController---secondVc2地址:%@",secondVc2);
if (secondVc1 == secondVc2) {
NSLog(@"SecondViewController---secondVc1 == secondVc2");
}
for (int i = 0; i < 5; i++) {
Person *per1 = [Person sharedInstance];
NSLog(@"SecondViewController---per1地址:%@",per1);
}
for (int i = 0; i < 5; i++) {
StudentModel *stud = [[StudentModel alloc] init];
NSLog(@"SecondViewController---stud地址:%@",stud);
}
}
/**
* 返回上一界面,再次生成對象查看;
*
* @param sender <#sender description#>
*/
- (IBAction)back:(id)sender {
[self dismissViewControllerAnimated:true completion:nil];
}
@end
~~~
下面分別是三個步驟打印log:啟動第一個界面、跳轉到第二個界面、返回第一個界面。
啟動第一個界面的輸出:

跳轉到第二個界面的輸出:

再次返回到第一個界面的輸出:

### 總結
通過以上打印輸出,使用sharedInstance創建單例后,無論在哪一個界面,每個類的對象是唯一的。而使用alloc創建的對象往往都是不同的。通過以上的設計,就不需要在每一個類中都去實現sharedInstance方法了。