设计模式系列14--单例模式

作者: 西木柚子 | 来源:发表于2016-06-07 17:34 被阅读3189次

    在开发中经常会用到单例设计模式,目的就是为了在程序的整个生命周期内,只会创建一个类的实例对象,而且只要程序不被杀死,该实例对象就不会被释放。下面我们来看看单例的概念、用途、如何创建,以便加深理解。

    定义

    保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    作用

    • 在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在APP开发中我们可能在任何地方都要使用用户的信息,那么可以在登录的时候就把用户信息存放在一个文件里面,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
    • 有的情况下,某个类可能只能有一个实例。比如说你写了一个类用来播放音乐,那么不管任何时候只能有一个该类的实例来播放声音。再比如,一台计算机上可以连好几个打印机,但是这个计算机上的打印程序只能有一个,这里就可以通过单例模式来避免两个打印任务同时输出到打印机中,即在整个的打印过程中我只有一个打印程序的实例。

    创建单例

    有两种方法来创建单例,下面分别介绍

    1、GCD方式创建单例

    static id _instance;   
       
    + (instancetype)allocWithZone:(struct _NSZone *)zone  
    {   
        static dispatch_once_t onceToken;   
        dispatch_once(&onceToken, ^{   
            _instance = [super allocWithZone:zone];   
        });   
        return _instance;   
    }   
       
    + (instancetype)sharedInstance  
    {   
        static dispatch_once_t onceToken;   
        dispatch_once(&onceToken, ^{   
            _instance = [[self alloc] init];   
        });   
        return _instance;   
    }   
       
    - (id)copyWithZone:(NSZone *)zone   
    {   
        return _instance;   
    }  
      
    - (id)mutableCopyWithZone:(NSZone *)zone {   
        return _instance;   
    }
    
    

    2、互斥锁方式

    static id _instance;
    + (instancetype)allocWithZone:(struct _NSZone *)zone
    {
        @synchronized(self) {
            if (_instance == nil) {
                _instance = [super allocWithZone:zone];
            }
        }
        return _instance;
    }
    + (instancetype)sharedInstance
    {
        @synchronized(self) {
            if (_instance == nil) {
                _instance = [[self alloc] init];
            }
        }
        return _instance;
    }
    - (id)copyWithZone:(NSZone *)zone
    {
        return _instance;
    }
    

    上面两种方式都可以创建单例,而且保证了用户不管是通过shareInstance方法,还是alloc、copy方法得到的实例都是一样的。

    上面代码的关键之处就在于如何在多线程情况下保证创建的单例还是同一个。

    我们先看看在GCD情况下,如果不使用dispatch_once和同步锁创建单例会出现什么问题,去掉两者后创建单例的代码如下

    + (instancetype)sharedInstance  
    {   
     if (_instance == nil) {
         _instance = [[self alloc] init];
      }
    } 
    

    假设此时有两条线程:线程1和线程2,都在调用shareInstance方法来创建单例,那么线程1运行到if (_instance == nil)出发现_instance = nil,那么就会初始化一个_instance,假设此时线程2也运行到if的判断处了,此时线程1还没有创建完成实例_instance,所以此时_instance = nil还是成立的,那么线程2又会创建一个_instace。

    此时就创建了两个实例对象,导致问题。

    解决办法1、使用dispatch_once

    dispatch_once保证程序在运行过程中只会被运行一次,那么假设此时线程1先执行shareInstance方法,创建了一个实例对象,线程2就不会再去执行dispatch_once的代码了。从而保证了只会创建一个实例对象。

    解决办法2、使用互斥锁

    假设此时线程1在执行shareInstance方法,那么synchronize大括号内创建单例的代码,如下所示:

    if (_instance == nil) {
                _instance = [[self alloc] init];
            }
    

    就会被当做一个任务被加上了一把锁。此时假设线程2也想执行shareInstance方法创建单例,但是看到了线程1加的互斥锁,就会进入睡眠模式。等到线程1执行完毕,才会被唤醒,然后去执行上面所示的创建单例的代码,但是此时_instance !=nil,所以不会再创建新的实例对象了。从而保证只会创建一个实例对象。

    但是互斥锁会影响性能,所以最好还是使用GCD方式创建单例。


    宏创建单例

    如果我们需要在程序中创建多个单例,那么需要在每个类中都写上一次上述代码,非常繁琐。

    我们可以使用宏来封装单例的创建,这样任何类需要创建单例,只需要一行代码就搞定了。

    实现代码

    Singleton.h文件
    ==================================
    
    #define SingletonH(name) + (instancetype)shared##name;
    
    #define SingletonM(name) \
    static id _instance; \
     \
    + (instancetype)allocWithZone:(struct _NSZone *)zone \
    { \
        static dispatch_once_t onceToken; \
        dispatch_once(&onceToken, ^{ \
            _instance = [super allocWithZone:zone]; \
        }); \
        return _instance; \
    } \
     \
    + (instancetype)shared##name \
    { \
        static dispatch_once_t onceToken; \
        dispatch_once(&onceToken, ^{ \
            _instance = [[self alloc] init]; \
        }); \
        return _instance; \
    } \
     \
    - (id)copyWithZone:(NSZone *)zone \
    { \
        return _instance; \
    }\
    \
    - (id)mutableCopyWithZone:(NSZone *)zone { \
        return _instance; \
    }
    
    

    如何调用

    假设我们要在类viewcontroller中使用,调用方法如下:

    viewcontroller.h文件
    ===========================
    
    #import <UIKit/UIKit.h>
    #import "Singleton.h"
    
    @interface ViewController : UIViewController
    SingletonH(viewController)
    @end
    
    
    
    viewcontroller.m文件
    ===========================
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    SingletonM(ViewController)
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"%@ %@ %@ %@", [ViewController sharedViewController],[ViewController sharedViewController], [[ViewController alloc] init],[[ViewController alloc] init]);
        
    }
    
    @end
    
    
    

    输出结果

     <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00>
    
    

    可以看到四个对象的内存地址完全一样,说明是同一个对象

    相关文章

      网友评论

      • 729680a369b4:又是李明杰学生
      • Charles___:还有一种方式可以创建单例 可以看下我的博文
      • zczb:写的不错,不过有的地方的注意大小写SingletonH(viewController),应该是SingletonH(ViewController)吧。
      • R0b1n_L33:写得很好 考虑到了OC本身特性导致单例可能泄漏的各种方面
      • GeekFounder:还是不明白平时开发中什么时候用到哦~楼主能写个demo么
        西木柚子:@SteveZhong 这个必须在项目中才能体会出来单利的用法
      • FengxinLi:请问 #define SingletonH(name) + (instancetype)shared##name;

        #define SingletonM(name) 这个声明是放在哪个地方的 我放在h文件里面。但是sharedViewController 这个方法调用不起
        R0b1n_L33:@Fengxinliju 如果是按照博主的代码来写demo调用sharedViewController自然是调用不到的 因为#define SingletonH(name) + (instancetype)shared##name;这个宏里name是viewController不是ViewController(注意大小写) ##连接符可不会帮你驼峰标记哦 :joy:
        西木柚子:@Fengxinliju 可以啊,我不是写明了,这些都放在.h文件吗
      • FengxinLi:请问下 楼主 为什么 + (instancetype)allocWithZone:(struct _NSZone *)zone 里面是调用的 _instance = [super allocWithZone:zone];
        R0b1n_L33:@Fengxinliju 因为分配空间这件事是由NSObject的allocWithZone来做的 而当前类的父类有可能做了自己的alloc逻辑 所以只需要调用直接父类的alloc方法即可链式传递从NSObject的alloc开始自顶向下调用alloc方法做内存分配
        西木柚子:@Fengxinliju 我没这么写啊
      • 44d3387e09f3:哈哈,黑马
      • 9岁就很6:不错!
      • a775e3933ce8:学习一下

      本文标题:设计模式系列14--单例模式

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