Home

dinghing

31 Aug 2016

C++与iOS的单例模式实现

单例模式:从C++去理解iOS

单例模式作为设计模式中比较常见的一种,在面试和工作中也会经常接触到。本文从C++角度来探讨单例模式几种典型实现,进一步了解iOS中的单例实现。

设计模式经典GoF定义的单例模式需要满足以下两个条件:

  • 保证一个类只创建一个实例。
  • 提供对该实例的全局访问点。

如果系统有类似的实体(有且只有一个,且需要全局访问),那么就可以将其实现为一个单例。实际工作中常见的应用举例:

  • 日志类:一个日志一般只需要一个实例就可以进行访问
  • 资源共享类:多次家在资源可能会影响性能,这时就可以使用单例来减少资源加载

C++中单例模式的实现方式有很多种,从最初的Lazy Singleton到线程安全,双检锁模式等等。

Lazy Singleton

class Singleton  
{
public:
    static Singleton& Instance()
    {
        if (instance_ == NULL)
        {
            instance_ = new Singleton;
        }
        return *instance;
     }
private:
    Singleton();
    ~Singleton();
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
private:
    static Singleton* instance;
};

以上的实现方法中,构造函数,成员变量等都被声明为私有变量防止生成新的实例。

同时需要注意Instance返回的是实例引用而非指针。原因很简单,指针都是在堆上面存在,而对堆的操作是没有什么限制的,也就是说实例可以被delete。而且直到Instance被访问到才被初始化,这也就不难理解为什么这种单例模式被称为Lazy Singleton了

这个模式有一个最大的隐患就是线程不安全。比如A,B线程同时创建实例的时候,单例模式的规则就被打破了。

Eager Singleton

class Singleton  
{
public:
    static Singleton& Instance()
    {
        return instance;
     }
private:
    Singleton();
    ~Singleton();
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
private:
    static Singleton instance;
};

最大的特点就是程序开始时实例就生成了(主要原因是不使用指针而实用静态变量) 虽然代码没有太大变但是这个模式是线程安全的,原因就在于初始化是在主函数之前进行的。 但是static对象的初始化顺序不确定导致生成为定义的实例的可能性也存在

Meyers Singleton

class Singleton  
{
public:
    static Singleton& Instance()
{
    static Singleton instance;
    return instance;
}
private:
    Singleton();
    ~Singleton();
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
};

一种简单优雅的单例模式(代码也是极其的简单) 单例模式中的实例也使用了Static声明,确保实例的有效性。当第一次访问Instance时实例得到创建

pthread_once

template<typename T>  
class Singleton : boost::noncopyable  
{
public:
    static T& instance()
    {
        pthread_once(&ponce_, &Singleton::init);
        return *value_;
    }

    static void init()
    {
        value_ = new T();
    }
private:
    static pthread_once_t ponce_;
   static T* value_;
};

多线程编程环境下,使用pthread_once会出现在多个线程中,但是初始化只会执行一次,也就确保了线程安全

iOS中的单例模式

@interface LibraryAPI: NSObject//.h文件中声明
+ (LibraryAPI*)sharedInstance;
@end//

+ (LibraryAPI*)sharedInstance //.m文件中实现
{
// 1
static LibraryAPI *_sharedInstance = nil;

// 2 
static dispatch_once_t oncePredicate; 

// 3
dispatch_once(&nocePredicate, ^{
    _sharedInstance = [[LibraryAPI alloc] init];
});
return _sharedInstance;
}

关注第三步,使用了dispatch_once_t来确保实例的初始化只执行一次。那么还原到C++中,有没有觉得和pthread_once模式有那么一丝相似

其实在很遥远的古代,iOS中也会使用pthread_once,因为 dispatch_once_t 更容易使用并且不易出错,所以你永远都不会再用到 pthread_once 了。

但是在C++中还没有找到合适的替换方法,也许最近被讨论的比较多的是双检测锁模式,但是由于种种坑的存在还没有找到一个万无一失的方法。细节可以参考这个资料

总结

本文主要从C++的几种单例实现开始,扩展到iOS中单例实现。并从iOS反馈C++的单例实现的改良。当然还有许多细节问题没有讨论到,以后有机会再补充。

see tomorrow
dinghing at 15:27

scribble