• iOS的单例设计模式,你真的了解吗?

    发布:51Code 时间: 2016-11-07 16:02

  • 单例就是确保在程序运行过程中只创建一个对象实例。而且该对象易于被外界访问。从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模...

  • 单例就是确保在程序运行过程中只创建一个对象实例。而且该对象易于被外界访问。从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。比如我们常见的网络请求类、工具类以及其它管理类等。比如我iOS开发中常见的系统单例[UIApplication sharedApplication]、[NSUserDefaults  standardUserDefaults]等。在iOS开发中,单例模式是非常有用的一种设计模式。接下来我们向大家介绍一下,在ARC环境下如何使用的单例模式。
    一、使用单例模式的作用
    它可以保证某个类在程序运行过程中最多只有一个实例,也就是对象实例只占用一份内存资源。

    二、单例模式的三个要点:
    1. 该类有且只有一个实例; 
    2. 该类必须能够自行创建这个实例; 
    3. 该类必须能够自行向整个系统提供这个实例。
    三、单例使用的情况
    1.如果创建一个对象会耗费很多系统资源,那么此时采用单例模式,只需要创建一个实例,会节省alloc时间。
    2.在iOS开发中,如果很多模块都使用同一个变量,此时如果把该变量放入到单例类,则所有访问该变量的调用会变得很容易,否则,只能通过一个模块传递给另一个模块,这样会正价风险和复杂度。
    四、单例模式优缺点
    优点
    1、提供了对唯一实例的受控访问。
    2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    3.因为单例模式的类控制了实例化的过程,所以类可以更加灵活修改实例化过程。
    缺点
    1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
        注意:只有确实需要唯一使用的对象才需要考虑单例模式,不要滥用单例。
    五、单例的实现
    基本步骤:
    (1) 为单例对象创建一个静态实例,可以写成全局的,也可以在类方法里面实现,并初始化为nil;
    (2)  实现一个实例构造方法,检查上面声明的静态实例是否为nil,如果是,则创建并返回一个本类的实例;
    (3) 重写allocWithZone方法,确保用户直接使用alloc和init试图获得一个新实例的时候不产生一个新实例;
    (4)  适当实现 copyWithZone,mutableC opyWithZone ,非arc下还需要实现release 和 autorelease方法 。
    六、单例的写法
    单例的写法常用的有两种方式:一种是不考虑线程的,一种是考虑线程安全的。在这里我们通过这两种不同的写法来学习如何在实际的考法中使用单例。
    新建一个单例类名为Manager。继承NSObject
    在Manager.h文件,定义shareManager 方法。代码如下:

      1 |  +(Manager*)shareManager;

       在Singleton.m文件,实现该单例方法:
    写法一:不考虑线程

      1 |  +(Manager*)shareManager{
      2 |      if (!sharemanager){
      3 |          sharemanager = [[Manager alloc]init];
      4 |      }
      5 |      return sharemanager;
      6 |  }

    当考虑为到多线程安全,我们通常采用第二种写法
    写法二:考虑线程安全

      1 |  +(Manager*)shareManager{
      2 |      static Manager* sharesManager = nil;
      3 |      static dispatch_once_t predicate;
      4 |      dispatch_once(&predicate, ^{
      5 |          sharemanager = [[Manager alloc]init];
      6 |      });
      7 |      return sharesManager;
      8 |  }

    dispatch_once是线程安全的,即使在多个线程中同时调用,也只有一个块被执行,其它dispatch_once块的调用被阻塞,直到执行的那个块运行结束,所以在整个程序运行周期内,dispatch_once块只会运行一次,可以确定,下一行代码执行前,整个dispatch_once块是执行完毕的,不管当前工作线程是哪个。如果已执行,dispatch_once会被快速跳过,在类似循环体中调用这种场合,也无需担心执行它的额外性能开销。如果一个程序包含多个同一调用类的实例,只有其中一个实例会执行dispatch_once块[1]。
    为了测试上述代码是否实现了单例模式,在主函数中我们可以创建两个不同的Manager实例来进行测试。

      1 |  int main(int argc, const char * argv[]) {
      2 |      @autoreleasepool {
      3 |          Manager *manager1 = [Manager shareManager];
      4 |          Manager *manager2 = [Manager shareManager];
      5 |          Manager *manager3 = [[Manager alloc]init];
      6 |          NSLog(@"第一个单例队像地址:%p,第二个单例对象地址:%p,第三个单例对象地址:%p",manager1,manager2,manager3);
      7 |      }
      8 |      return 0;
      9 |  }

      上述两种写法的运行结果是一致的,都为
    第一个单例队像地址:0x0,
    第二个单例对象地址:0x0,
    第三个单例对象地址:0x100204cf0
    在主函数中,第4行代码和第5行代码使用代理方法shareManager创建了Manager的实例对象manager1和manager2。但由运行结果可以看出,第一个单例对象和第二个单例对象的地址是完全相同的,这说明通过上述单例方法只能创建一个实例对象。但是但是对象是可以实例化的,在第6行代码中我们通过alloc实例方法创建了Manager的实例对象manager3。从运行结果可以看出,第3个实例对象的内存地址和前2个不一样,说明第3个实例对象和前2个实例对象不是同一个实例。
    此时我们需要动手改造以下改成的方法,让实例化对象的出来的地址也是一样的,这个时候需要重写allocWithZone方法:

      1 |  +(Manager*)shareManager{
      2 |      if (!sharemanager){
      3 |          sharemanager = [[super allocWithZone:NULL]init];
      4 |      }
      5 |      return sharemanager;
      6 |  }
      7 |  +(instancetype)allocWithZone:(struct _NSZone *)zone{
      8 |      return  [self shareManager];
      9 |  }

      如果对象拷贝的时候也需要是同一个对象的话,可以添加以下方法。

      1 |  +(id)copyWithZone:(struct _NSZone *)zone{
      2 |      return [self shareManager];
      3 |  }
      4 |  - (id)copyWithZone:(NSZone *)zone
      5 |  {
      6 |      return self;
      7 |  }

    运行结果:
    第一个单例队像地址:0x1002012e0,第二个单例对象地址:0x1002012e0,第三个单例对象地址:0x1002012e0这样实现的两个实例的地址是一致的,所以是同一个实例对象,这就表明了我们通过上述方法实现了单例。
    如果为了确保多线程情况下,仍然确保实体的唯一性,这个时候可以加上@synchronized,@synchronized的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改。

      1 |  +(Manager*)shareManager{
      2 |      @synchronized(self){
      3 |          if (!sharemanager){
      4 |              sharemanager = [[super allocWithZone:NULL]init];
      5 |          }
      6 |      }
      7 |      return sharemanager;
      8 |  }

    这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其它线程访问,起到线程的保护作用。单例模式或者操作类的static变量中使用比较多。当两个并发线程访问同一个对象@synchronized(self)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
    在苹果引入GCD,我们可以 利用GCD和ARC实现单例,为了多线程考虑,可以通过dispatch_once简单的实现

      1 |  +(instancetype)shareInstance{
      2 |      static Manager *shareManager = nil;
      3 |      static dispatch_once_t onceToken;
      4 |      dispatch_once(&onceToken, ^{
      5 |          shareManager = [[Manager allocWithZone:NULL]init];
      6 |      });
      7 |      return shareManager;
      8 |  }

    同时将+(instancetype)allocWithZone:(struct _NSZone *)zone方法进行修改,代码如下:

      1 |  +(instancetype)allocWithZone:(struct _NSZone *)zone{
      2 |      return  [self shareInstance];
      3 |  }

        大家按照上面的操作。就可以实现在ARC环境下实现单例模式。
     

  • 上一篇:iOS开发者跳槽,你准备好了吗?

    下一篇:iOS的内存缓存机制

网站导航
Copyright(C)51Code软件开发网 2003-2018 , 沪ICP备16012939号-1