单例的使用及避免对单例的滥用

作者: MrFire_ | 来源:发表于2016-04-08 11:06 被阅读3817次

    单例应该只用来保存全局的状态,并且不能和任何作用域绑定。如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。用一个单例来管理用户绑定的状态,是代码的坏味道,你应该认真的重新评估你的对象图的设计。

    1、首先说一下单例的使用

    单例就是保证整个系统只有一个实例对象,并且自行实例化,向整个系统提供这个实例。

    单例模式的出现为我们带来了很大的好处,我们可以将那些初始化比较耗费资源的操作用单例来设计,比如在我的项目中用到了蓝牙,而且在不同的界面都有用到,我就把蓝牙的Manager做成了一个单例,这样只有第一次初始化蓝牙模块的时候耗时一点,节省了时间。另外可以利用单例来传值等操作。那么我们该如何创建单例呢?

    • 方法1,比较传统的写法:
    +(instancetype)shared{
        static LaunchIntroductionView *singleInstance = nil;
        @synchronized(self) {
            if (singleInstance == nil) {
                singleInstance = [[LaunchIntroductionView alloc] init];
            }
        }
        return singleInstance;
    }
    

    synchronized的使用时为了线程安全,比如有两个线程A和B,加入他们同时调用shared方法,如果不加同步的话则很可能会导致并发,这样可能就创建出来了两个实例,而不是真正的单例模式了,所以需要加上同步(synchronized),来保证同一个时刻只有一个线程在访问这个实例。这样做是实现了单例,而且是线程安全的,但这样就没有一点问题了吗?问题还是有的,那就是时间的问题,因为这样写之后我们每次调用shared都会去判断singleInstance == nil是不是成立的,这样就避免不了浪费了判断的时间,有人或许会说就那么一丁点的时间还用考虑吗?是的,我觉得能节省的时间我们一点都不要浪费。如果你认可了我这一点,那么我们接着往下看第二种方法。

    • 方法2,双重检查加锁:
      所谓双重检查机制指的是每次进入这个方法时先去判断实例是不是为nil,如果部位nil则直接返回,反之则进入同步检查,然后再判断是不是为nil若不存在则创建一个实例,专业养的话就只需要同步一次就行了,从而减少了同步时的判断需要耗费的时间,代码更清晰:
    +(instancetype)shared{
        volatile static LaunchIntroductionView *singleInstance = nil;
        if (singleInstance == nil) {
            @synchronized(self) {
                if (singleInstance == nil) {
                    singleInstance = [[LaunchIntroductionView alloc] init];
                }
            }
        }
        return singleInstance;
    }
    

    关于这一点参考自IOS单例模式及单例模式的优缺点 ,非常感谢!之所以使用volatile修饰,是因为被volatile修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

    • 方法3,GCD:
    +(instancetype)shared{
        static LaunchIntroductionView *launch = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];
        });
        return launch;
    }
    

    这个就不过多介绍了,网上的资料实在是太多了。


    2、单例的滥用
    单例给我们带来方便的同时也有一定的副作用,因为单例对象一旦创建,对象指针是保存在静态区的,单例对象在堆中分配的内存空间只有在程序终止后才会释放,过多的单例必定会增大我们消耗的内存,所以只有当我们确实需要唯一的使用对象时才需要考虑单例模式,切勿滥用单例,引用开头的话:单例应该只用来保存全局的状态,并且不能和任何作用域绑定。如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。
    说起来是挺容易的,但现实中对单例的滥用到处都是,一不留神就埋下了“祸根”,我在一句代码搞定启动引导页中就对单例进行了滥用,所以我们在使用单例的时候一定要想清楚了,我们是不是真的有必要用单例,如果这个对象的创建不是那么的费时费力,或者这个对象没必要再应用的整个生命周期中一直存在,那么我们是不是考虑一下换用其他的方式,而非单例?


    相关文章

      网友评论

      • KuKuMan:{
        static manager *m = nil;
        @synchronized (self){
        if(!m){
        m=【manager alloc】init】;


        return m;

        这样写好么?
        MrFire_:@黑白程序猿 可以昂
      • ArchLL:我明白了,但是我觉得第一次里面的判断不能省,当单例对象为nil时,在同步锁中为了防止多次创建对象,需要先判断是否为nil。估计你打错了, :smiley:
      • ArchLL:如果外面加一层判断,那么里面的判断是不是多余了
        ArchLL:@hungryBoy 我的意思是如果在同步锁外面加判断,那么同步锁里面的判断是不是一开始就可以不写(虽然只执行一次)。但为什么通常都是把判断写在同步锁里面而不是同步锁外面
        MrFire_:@Forever丿 对于单例第一次创建的时候,里面那个确实是多余的,但当单例创建完成后,里面的synchronized将不再执行,这样以后每次调用的时候都不会经过synchronized,这样效率相对来说要好一些
      • Inlight先森:第二种跟第一种没区别吧,都是要判断一次!
        Inlight先森:@hungryBoy OK 知道了
        MrFire_:@Dalla尹 不一样的,一种是加了同步的判断,一种是不加同步的判断,加了同步后的判断比较耗费性能,如果同时有很多地方要去访问这个对象,效果就会明显不一样的

      本文标题:单例的使用及避免对单例的滥用

      本文链接:https://www.haomeiwen.com/subject/dydvlttx.html