书写一个完整的单例

前言

什么是单例?

一个类只允许有一个实例,在整个程序中需要多次使用,共享同一份资源的时候,就可以创建单例,一般封装成工具类使用,苹果封装成单例常用的有 UIApplication,NSUserDefaults,NSNotificationCenter,NSFIleManager等等。

单例的实现

我以前的写法 (不严谨的写法)

// Singleton.h

@interface Singleton : NSObject
+ (instancetype)sharedInstance;
@end

//  Singleton.m
static Singleton *_instance = nil;
@implementation Singleton
+(instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[Singleton alloc] init];
    });
    return _instance;
}
@end

这不算真正意义上的单例,不能保证全局的唯一性,因为类方法和 [ [Class alloc] init ] 方法创建的单例内存地址可能不一样,所有不严谨。

下面举个栗子🌰

Singleton *singOne = [Singleton sharedInstance];
Singleton *singTwo = [Singleton sharedInstance];
Singleton *singThere = [Singleton new];
Singleton *singFour = [[Singleton alloc] init];
NSLog(@"singOne:-> %@",singOne);
NSLog(@"singTwo:-> %@",singTwo);
NSLog(@"singThere:-> %@",singThere);
NSLog(@"singFour:-> %@",singFour);

这时我看查看一下控制台输出的信息

2018-02-07 10:46:25.024988+0800 Markdown[2056:67507] singOne:-> <Singleton: 0x604000001a00>
2018-02-07 10:46:25.025222+0800 Markdown[2056:67507] singTwo:-> <Singleton: 0x604000001a00>
2018-02-07 10:46:25.025372+0800 Markdown[2056:67507] singThere:-> <Singleton: 0x6040000019a0>
2018-02-07 10:46:25.025646+0800 Markdown[2056:67507] singFour:-> <Singleton: 0x604000001a10>

结果很明显, [Singleton sharedInstance] 类方法创建出来单例singOnesingTwo 的内纯地址是一样的,说明单例创建对了,但是看到newalloc init 创建的singTheresingFour 的内存地址不一样,同时也与singOnesingTwo的地址不一样.

下面怎么聊聊怎么解决这个问题

百度一下还真有这类的帖子和博客,我们主要的问题就是保证单例的唯一性,避免不小心用实例方法创建单例,所有应该保证alloc initnewcopy 方法创建的单例的的唯一性。

在创建对象的时候主要分这么两步 alloc (申请内存)init(初始化)

  1. 我们在第一步alloc的会后就要对其进行拦截。当我们去调用alloc的时候,OC内部会调用allocWithZone这个方法去申请内存,我们去覆写这个方法,然后在这个方法中调用之前的类方法,返单例对象,这样就能达到我们的目的了。

  2. 拷贝对象也是一样的,覆写copyWithZone方法,然后在方法中去调用类方法,返回单例对象。(在覆写copyWithZone方法之前别忘记了签署NSCopying协议)

  3. 参考:书写一个严谨的单例

下面修改一下 Singleton.m
参考:iOS-单例模式简单使用

// Singleton.m

static Singleton *_instance = nil;
@implementation Singleton
+(instancetype)sharedInstance
{
    if (_instance == nil) {
        _instance = [[super alloc]init];
    }
    return _instance;
}

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

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

- (id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}

看一下控制台输出的结果:

2018-02-07 11:20:46.993693+0800 Markdown[2374:88765] singOne:-> <Singleton: 0x604000001990>
2018-02-07 11:20:46.993906+0800 Markdown[2374:88765] singTwo:-> <Singleton: 0x604000001990>
2018-02-07 11:20:46.994040+0800 Markdown[2374:88765] singThere:-> <Singleton: 0x604000001990>
2018-02-07 11:20:46.994146+0800 Markdown[2374:88765] singFour:-> <Singleton: 0x604000001990>

很明显,改过 Singleton.mSingleton类方法的类方法sharedInstance 和 实例方法 alloc init 的方法创建的单例内存地址一样了,是不是有点小喜悦。

你以为这就完事了吗? NO、NO、NO

哪个单例里面没有几个属性,最少也得有一个吧,要不然我要这铁(dan)棒(li)有何用。
这就加几个属性试试。

Singleton *singOne = [Singleton sharedInstance];
SingOne.array = @[@"1",@"2",@"3"];
Singleton *singTwo = [Singleton sharedInstance];
SingOne.array = @[@"4",@"5",@"6"];
Singleton *singThere = [Singleton new];
SingThere.array = @[@"7",@"8",@"9"];
Singleton *singFour = [[Singleton alloc] init];
SingFour.array = @[@"0",@"0",@"0"];
NSLog(@"  singOne:-> %@ , %p , %@ ",singOne,singOne.array,singOne.array);
NSLog(@"  singTwo:-> %@ , %p , %@ ",singTwo,singTwo.array,singTwo.array);
NSLog(@"singThere:-> %@ , %p , %@ ",singThere,singThere.array,singThere.array);
NSLog(@" singFour:-> %@ , %p , %@ ",singFour,singFour.array,singFour.array);

现在看看结果
array 属性地址唯一,数组内容也唯一,满足标准。

2018-02-07 11:48:39.843225+0800 Markdown[2804:110175]   singOne:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
    0,
    0,
    0
)
2018-02-07 11:48:39.843439+0800 Markdown[2804:110175]   singTwo:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
    0,
    0,
    0
)
2018-02-07 11:48:39.843589+0800 Markdown[2804:110175] singThere:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
    0,
    0,
    0
)
2018-02-07 11:48:39.843713+0800 Markdown[2804:110175]  singFour:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
    0,
    0,
    0
)

声明

感谢以上两位博主的文章,借鉴做了一份单例的笔记,记录开发中的问题并解决问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容