iOS中的單例你用對了么?

Gary2192 8年前發布 | 20K 次閱讀 iOS開發 移動開發

單例模式怎么定義的,可能在不同的語言,不同的書中不完全一樣,但是概況開來都應該是:一個類有且僅有一個實例,并且自行實例化向整個系統提供。

因此,首先你可能需要確定你是真的需要一個單例類,還是說僅僅是需要一個方便調用的實例化方法。如果你是真的需要一個單例類,那么你就應該確保這個單例類,有且僅有一個實例(不管怎么操作都只能獲取到這個實例)。

最近看到一些github上的單例使用,別人的用法,有一些思考,然后寫demo測試了下,就這個簡單的單例也有一些坑呢,希望能給他人一些提醒。

Objective-C中的單例

我們通常在OC中實現一個單例方法都是這樣:

static HLTestObject *instance = nil;
+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[[self class] alloc] init];
    });
    return instance;
}

可是這樣就可以了么?我做了如下測試:

HLTestObject *objct1 = [HLTestObject sharedInstance];
NSLog(@"%@",objct1);
HLTestObject *objc2 = [[HLTestObject alloc] init];
NSLog(@"%@",objc2);
HLTestObject *objc3 = [HLTestObject new];
NSLog(@"%@",objc3);

看到這個測試,你想到打印結果了么?結果是這樣的:

2016-05-23 12:52:57.095 PractiseProject[3579:81998] 






2016-05-23 12:52:57.095 PractiseProject[3579:81998] 



     2016-05-23 12:52:57.095 PractiseProject[3579:81998] 




  

很明顯,通過三種方式創建出來的是不同的實例對象,這就違背了單例類有且僅有一個實例的定義。

為了防止別人不小心利用alloc/init方式創建示例,也為了防止別人故意為之,我們要保證不管用什么方式創建都只能是同一個實例對象,這就得重寫另一個方法,實現如下:

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

再次用上面的測試代碼,結果是這樣的:

2016-05-23 12:57:37.396 PractiseProject[3618:83975] 






2016-05-23 12:57:37.396 PractiseProject[3618:83975] 



     2016-05-23 12:57:37.396 PractiseProject[3618:83975] 




  

好像用不同的構造方法,獲取的都是同一個對象,你以為這樣就完了?還早著呢!

一般我們的類里肯定都會有一些屬性,然后我就添加了兩個property:

@property (assign, nonatomic)   int  height;
@property (strong, nonatomic)   NSObject  *object;
@property (strong, nonatomic)   NSMutableArray  *arrayM;

而一些對象類的初始化,或者基礎類型的默認值設置都是在init方法里,就像這樣:

- (instancetype)init
{
    self = [super init];
    if (self) {
        _height = 10;
        _object = [[NSObject alloc] init];
        _arrayM = [[NSMutableArray alloc] init];
    }
    return self;
}

我重寫了HLTestObject類的description方法:

- (NSString *)description
{
    NSString *result = @"";
    result = [result stringByAppendingFormat:@"<%@: %p>",[self class], self];
    result = [result stringByAppendingFormat:@" height = %d,",self.height];
    result = [result stringByAppendingFormat:@" arrayM = %p,",self.arrayM];
    result = [result stringByAppendingFormat:@" object = %p,",self.object];
    return result;
}

還是用上面的測試代碼,測試結果是這樣的:

2016-05-23 13:14:43.684 PractiseProject[3781:92758] 





  height = 20, arrayM = 0x7f8a5b422940, object = 0x7f8a5b4544e0,
2016-05-23 13:14:43.684 PractiseProject[3781:92758] 



     height = 10, arrayM = 0x7f8a5b4552e0, object = 0x7f8a5b45a710, 2016-05-23 13:14:43.684 PractiseProject[3781:92758] 

      height = 10, arrayM = 0x7f8a5b459770, object = 0x7f8a5b4544e0, 




  

可以看到,盡管使用的是同一個示例,可是他們的property值卻不一樣。

因為盡管沒有為示例重新分配內存空間,但是因為又執行了init方法,會導致property被重新初始化。

所以我們需要修改單例的實現。

第一種:

可以將property的初始化或者默認值設置放到dispatch_once 的block內部:

static HLTestObject *instance = nil;
+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[[self class] alloc] init];
        instance.height = 10;
        instance.object = [[NSObject alloc] init];
        instance.arrayM = [[NSMutableArray alloc] init];
    });
    return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}
- (NSString *)description
{
    NSString *result = @"";
    result = [result stringByAppendingFormat:@"<%@: %p>",[self class], self];
    result = [result stringByAppendingFormat:@" height = %d,",self.height];
    result = [result stringByAppendingFormat:@" arrayM = %p,",self.arrayM];
    result = [result stringByAppendingFormat:@" object = %p,",self.object];
    return result;
}

來看看測試結果:

2016-05-23 13:29:14.856 PractiseProject[3909:99058] 





  height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0,
2016-05-23 13:29:14.856 PractiseProject[3909:99058] 



     height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0, 2016-05-23 13:29:14.856 PractiseProject[3909:99058] 

      height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0, 




  

第二種:

static HLTestObject *instance = nil;
+ (instancetype)sharedInstance
{
    return [[self alloc] init];
}
- (instancetype)init
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super init];
        instance.height = 10;
        instance.object = [[NSObject alloc] init];
        instance.arrayM = [[NSMutableArray alloc] init];
    });
    return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}
- (NSString *)description
{
    NSString *result = @"";
    result = [result stringByAppendingFormat:@"<%@: %p>",[self class], self];
    result = [result stringByAppendingFormat:@" height = %d,",self.height];
    result = [result stringByAppendingFormat:@" arrayM = %p,",self.arrayM];
    result = [result stringByAppendingFormat:@" object = %p,",self.object];
    return result;
}

測試結果:

2016-05-23 13:31:44.824 PractiseProject[3939:100662] 





  height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940,
2016-05-23 13:31:44.825 PractiseProject[3939:100662] 



     height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940, 2016-05-23 13:31:44.825 PractiseProject[3939:100662] 

      height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940, 




  

注意:

以上代碼均是使用ARC的方式管理內存,如果你還在使用MRC(這也太不與時俱進了)。那你還需要重寫 retain 和release方法,防止示例引用計數的改變。

Swift中的單例

利用Swift中的一些特性,Swift中的單例可以超級簡單,like this:

class HLTestObject: NSObject {
    static let sharedInstance = HLTestObject();
}

可是這樣就完了么?同樣寫一段測試代碼:

let object1 = HLTestObject.sharedInstance;
print(object1);
let object2 = HLTestObject();
print(object2);

打印結果卻是這樣的:

所以,我們必須禁用到構造方法:

class HLTestObject: NSObject {
    static let sharedInstance = HLTestObject();
    private override init() {
    }
}

如果有實例屬性需要初始化,就可以這樣:

class HLTestObject: NSObject {
    var height = 10;
    var arrayM: NSMutableArray
    var object: NSObject
    static let sharedInstance = HLTestObject();
    private override init() {
        object = NSObject()
        arrayM = NSMutableArray()
        super.init()
    }
}

當然,由于Swift的特性,在Swift中創建單例的方式也不止一種,需要注意的是要確保該類有且僅有一個實例就OK了。

Have Fun!

 

閱讀原文

 

 本文由用戶 Gary2192 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!