iOS 单例模式

单例模式,由于其简单好用容易理解、同时在出问题时也容易定位的特点,在开发中经常用到的一个设计模式,本文主要分享我在自己的代码中是如何使用单例模式的。

1、什么是单例模式

单例模式的定义

简单的来说,一个单例类,在整个程序中只有一个实例,并且提供一个类方法供全局调用,在编译时初始化这个类,然后一直保存在内存中,到程序(APP)退出时由系统自动释放这部分内存。

系统为我们提供的单例类有哪些?

UIApplication(应用程序实例类)
NSNotificationCenter(消息中心类)
NSFileManager(文件管理类)
NSUserDefaults(应用程序设置)
NSURLCache(请求缓存类)
NSHTTPCookieStorage(应用程序cookies池)

在哪些地方会用到单例模式

一般在我的程序中,经常调用的类,如工具类、公共跳转类等,我都会采用单例模式;

重复初始化单例类会怎样?

请看下面的例子,我在我的工程中,初始化一次UIApplication,

  [[UIApplication alloc]init];

最后运行的结果是,程序直接崩溃,并报了下面的错,

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'There can only be one UIApplication instance.'
初始化

所以,由此可以确定,一个单例类只能初始化一次。

2、单例类的生命周期

单例实例在存储器的中位置

请看下面的表格展示了程序中中不同的变量在手机存储器中的存储位置;

位置 存放的变量
临时变量(由编译器管理自动创建/分配/释放的,栈中的内存被调用时处于存储空间中,调用完毕后由系统系统自动释放内存)
通过alloc、calloc、malloc或new申请内存,由开发者手动在调用之后通过free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存,在ARC模式下,由系统自动管理。
全局区域 静态变量(编译时分配,APP结束时由系统释放)
常量 常量(编译时分配,APP结束时由系统释放)
代码区 存放代码

在程序中,一个单例类在程序中只能初始化一次,为了保证在使用中始终都是存在的,所以单例是在存储器的全局区域,在编译时分配内存,只要程序还在运行就会一直占用内存,在APP结束后由系统释放这部分内存内存。

3、新建一个单例类

(1)、单例模式的创建方式;
同步锁 :NSLock
@synchronized(self) {}
信号量控制并发:dispatch_semaphore_t 
条件锁:NSConditionLock
dispatch_once_t

考虑数据和线程问题,苹果官方推荐开发者使用dispatch_once_t来创建单例,那么我就采用dispatch_once_t方法来创建一个单例,类名为OneTimeClass

static OneTimeClass *__onetimeClass;
+ (OneTimeClass *)sharedOneTimeClass {
static dispatch_once_t oneToken;

    dispatch_once(&oneToken, ^{

        __onetimeClass = [[OneTimeClass alloc]init];

    });
    return __onetimeClass;
}

4、单例模式的优缺点

先说优点:

(1)、在整个程序中只会实例化一次,所以在程序如果出了问题,可以快速的定位问题所在;
(2)、由于在整个程序中只存在一个对象,节省了系统内存资源,提高了程序的运行效率;

再说缺点

(1)、不能被继承,不能有子类;

(2)、不易被重写或扩展(可以使用分类);

(3)、同时,由于单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置时并不能销毁,在闲置时也消耗了系统内存资源;

5、单例模式详解

(1)、重写单例类的alloc方法保证这个类只会被初始化一次

我在viewDidLoad方法中调用单例类的allocinit方法:

[[OneTimeClass alloc]init];

此时只是报黄点,但是并没有报错,Run程序也可以成功,这样的话,就不符合我们最开始使用单例模式的初衷来,这个类也可以随便初始化类,为什么呢?因为我们并没有获取OneTimeClass类的使用实例,改进代码:

[OneTimeClass sharedOneTimeClass];
 [[OneTimeClass alloc]init];

这是改进后的,但是在多人开发时,还是没办法保证,我们会先调用alloc方法,这样我们就没办法控制了,但是我们控制OneTimeClass类,此时我们可以重写OneTimeClass类的alloc方法,此处在重写alloc方法的处理可以采用断言或者系统为开发者提供的NSException类来告诉其他的同事这个类是单例类,不能多次初始化。

//断言
+ (instancetype)alloc {
  NSCAssert(!__onetimeClass, @"OneTimeClass类只能初始化一次");
  return [super alloc];
}
//NSException
+ (instancetype)alloc {
   //如果已经初始化了
    if (__onetimeClass) {
      NSException *exception = [NSException exceptionWithName:@"提示" reason:@"OneTimeClass类只能初始化一次" userInfo:nil];
      [exception raise];
   }
 return [super alloc];
}

此时在run一次,可以看到程序直接崩到main函数上了,并按照我之前给的提示报错。


初始化

但是,如果我们的程序直接就崩溃了,这样的做法与开发者开发APP的初衷是不是又相悖了,作为一个程序员的目的要给用户一个交互友好的APP,而不是一点小问题就崩溃,当然咯,如果想和测试的妹纸多交流交流,那就。。。。。
对于这种情况,可以用到NSObect类提供的load方法和initialize方法来控制,
这两个方法的调用时机:

load方法是在整个文件被加载到运行时,在main函数调用之前调用;

initialize方法是在该类第一次调用该类时调用;

为了验证load方法和initialize方法的调用时机,我在 Main函数中打印:

printf("\n\n\n\nmain()");

OneTimeClass类的load方法中打印:

+ (void)load {
    printf("\n\nOneTimeClass load()");
}

OneTimeClass类的initialize方法中打印:

+ (void)initialize {
    printf("\n\nOneTimeClass initialize()");
}

运行程序,最后的结果是,load方法先打印出来,所以可以确定的是load的确是在在main函数调用之前调用的。

load和initialize

这样的话,如果我在单例类的load方法或者initialize方法中初始化这个类,是不是就保证了这个类在整个程序中调用一次呢?

+ (void)load {
  printf("\n\nOneTimeClass load()");
}
+ (void)initialize {
  printf("\nOneTimeClass initialize()\n\n\n");
  [OneTimeClass sharedOneTimeClass];
}

这样就可以保证sharedOneTimeClass方法是最早调用的。同时,再次对alloc方法修改,无论在何时调用OneTimeClass已经初始化了,如果再次调用alloc可直接返回__onetimeClass实例。

+ (instancetype)alloc {
 if (__onetimeClass) {
        return  __onetimeClass;
    }
 return [super alloc];
}

最后在ViewController中打印调用OneTimeClasssharedOneTimeClassalloc方法,可以看到Log出来的内存地址是相同的,这就说明此时我的OneTimeClass类就只初始化了一次。

   OneTimeClass *onetime1 = [OneTimeClass sharedOneTimeClass];
    NSLog(@"shared:============%@",onetime1);
    OneTimeClass *onetime2 = [[OneTimeClass alloc] init];
    NSLog(@"new:============%@",onetime2);
内存地址
(2)、对newcopymutableCopy的处理

方案一:重写这几个方法,当调用时提示或者返回OneTimeClass类实例,请参考alloc方法的处理;

方案二:直接禁用这个方法,禁止调用这几个方法,否则就报错,编译不过;

+(instancetype) new __attribute__((unavailable("OneTimeClass类只能初始化一次")));
-(instancetype) copy __attribute__((unavailable("OneTimeClass类只能初始化一次")));
-(instancetype) mutableCopy  __attribute__((unavailable("OneTimeClass类只能初始化一次")));

此时我在viewDidLoad中调用new,然后Build,编译器会直接给出错误警告,如下图:

OneTimeClass类只能初始化一次

这样就解决了单例类被多次初始化的问题;

(3)、分类Category的使用

如果在程序中某个模块的业务逻辑比较多,此时可以选择分类Category的方式,这样做的好处是:
(1)、减少Controller代码行数,使代码逻辑更清晰;
(2)、把同一个功能业务区分开,利于后期的维护;
(3)、遇到BUG能快速定位到相关代码;
原则上分类Category只能增加和实现方法,而不能增加属性,此处请参考美团技术团队的博客:深入理解Objective-C:Category

例如,在我们的APP中,用到了Socket技术,我在客户端Socket部分的代码使用了单例模式。由于和服务器的交互比较多,此时采用分类Category的方式,把Socket异常处理,给服务器发送的协议,和接受到服务器的协议 用三个分类Category来实现。在以后的维护中如果业务复杂度增加,或者加了新的业务或功能,可继续新建一个分类。这样既不影响之前的代码,同时又可以保证新的代码逻辑清晰。

以上是我在单例模式使用上的一些总结,如果有错误的地方,请指出。

本文demo:戳这里
本文参考:细说@synchronized和dispatch_once

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

推荐阅读更多精彩内容

  • 单例模式作用 可以保证在程序运行过程中,一个类只有一个实例,而且该实例易于供外界使用 从而方便地控制了实例个数,并...
    珍此良辰阅读 1,284评论 3 8
  • 1 单例模式 它是一种设计模式(常见的设计模式有:观察者模式、工厂模式、门面模式等)。单例设计模式中,一个类只有一...
    岁与禾阅读 897评论 5 9
  • 简介: 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统...
    RunnerFL阅读 612评论 0 0
  • 感觉浑身发冷, 是不是要倒下了?
    十八呆阅读 223评论 0 0
  • 不论你是直接npm koa还是npm koa-generator这样的第三方脚手架,如果发现运行最简单的demo都...
    哪吒闹海全靠浪阅读 248评论 0 0