iOS 单例类详解 ( 一 )

一、单例是什么?(aplɪˈkeɪʃ(ə)n 申请)

在 Foundation 和 Application Kit 框架中的一些类只允许创建单个对象,
即这些类在当前进程中的只有唯一一个实例。
举例来说,NSFileManager (faɪl mænɪdʒə)和 NSWorkspace 类 在使用时都是基于进程进行单个对象的实例化。
当向这些类请求实例的时候,它们会向您传递单一实例的一个引用,
如果该实例还不存在,则首先进行对实例的分配内存和初始化。
单件对象充当控制中心的角色,负责指引或协调类的各种服务。
如果类在概念上只有一个实例(如:NSFileManager),就应该产生一个单件实例,而不是多个实例;

二、为什么使用单例设计?简单描述下对单利模式设计的理解?

1>单例设计是用来 限制一个类只能创建一个对象,
那么此对象中的属性可以存储全局共享的数据,
所有的类都可以访问 —->设置此单例对象中的属性数据
2>如果一个类创建的时候非常耗费性能,那么此类可以设置为单例节约性能,从而达到节省内存资源,一个类就一个对象。

三、详细介绍单例:

在iOS开发中,有很多地方都选择使用单例模式。Singleton(sɪŋɡ(ə)lt(ə)n 单例模式)也叫单子模式,是一种常用的软件设计模式。
有很多时候必须要创建一个对象,并且不能创建多个,用单例就为了防止创建多个对象。
单例模式的意思就是某一个类有且只有一个实例。在应用这个模式时,单例对象的类必须保证只有一个实例存在。而且 自行实例化 并向整个系统提供这个实例。而这个类称为单例类。
!!!一个单例类可以实现在不同的窗口之间传递数据!!!

综上所述单例模式的三要点:

  1. 该类有且只有一个实例;
  2. 该类必须能够自行创建这个实例;
  3. 该类必须能够自行向整个系统提供这个实例。

单例模式的优点与缺点:

  1. 内存占用与运行时间
    对比使用单例模式和非单例模式的例子,在内存占用与运行时间存在以下差距:
    (1) 单例模式:单例模式每次获取实例时都会先进行判断,看该实例是否存在:如果存在,则返回;否则,则创建实例。因此,会浪费一些判断的时间。但是,如果一直没有人使用这个实例的话,那么就不会创建实例,节约了内存空间。
    (2) 非单例模式:当类加载的时候就会创建类的实例,不管你是否使用它。然后当每次调用的时候就不需要判断该实例是否存在了,节省了运行的时间。但是如果该实例没有使用的话,就浪费了内存。
  1. 线程的安全性
    (1) 从线程的安全性上来讲,不加同步(@synchronized)单例模式是不安全的。比如,有两个线程,一个是线程A,另外一个是线程B,如果它们同时调用某一个方法,那就可能会导致并发问题。在这种情况下,会创建出两个实例来,也就是单例的控制在并发情况下失效了。
    (2) 非单例模式的线程是安全的,因为程序保证只加载一次,在加载的时候不会发生并发情况。
    (3) 单例模式如果要实现线程安全,只需要加上@synchronized即可。但是这样一来,就会减低整个程序的访问速度,而且每次都要判断,比较麻烦。
    (4) 双重检查加锁:为了解决(3)的繁琐问题,可以使用“双重检查加锁”的方式来实现,这样,就可以既实现线程安全,又能使得程序性能不受太大的影响。
    (4.1) 双重检查加锁机制——并不是每次进入要调用的方法都需要同步,而是先不同步,等进入了方法之后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。当进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次,从而减少了多次在同步情况下进行判断所浪费的时间。
    (4.2) 双重检查加锁机制的实现,会使用一个关键字volatile(vɒlətʌɪl)。它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存的,从而确保了多个线程能正确的处理该变量。这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是在第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
    3.实例控制:单例模式(Singleton) 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例。
    4.灵活性:因为单例模式的类控制了实例化的过程,所以类可以更加灵活修改实例化过程。

四、iOS中的单例模式 如何实现一个单例类?

1. 在objective-c中要实现一个单例类,至少需要做以下四个步骤:

(1) 为单例对象创建一个静态实例,可以写成全局的,也可以在类方法里面实现,并初始化,然后设置成nil;
(2) 实现一个实例构造方法,检查上面声明的静态实例是否为nil,如果是,则创建并返回一个本类的实例;
(3)重写allocWithZone方法,用来保证其他人直接使用alloc和init试图获得一个新实例的时候不产生一个新实例,
(4)适当实现allocWitheZone,copyWithZone,release和autorelease。

2.怎样实现一个单例模式的类,给出思路,不写代码?

1·首先必须创建一个全局实例,通常存放在一个全局变量中,此全局变量设置为nil
2·提供工厂方法对该全局实例进行访问,检查该变量是否为nil,如果nil就创建一个新的实例,最后返回全局实例
3·全局变量的初始化在第一次调用工厂方法时会在+allocWithZone:中进行,所以需要重写该方法,防止通过标准的alloc方式创建新的实例
4·为了防止通过copy方法得到新的实例,需要实现-copyWithZone方法
5·只需在此方法中返回本身对象即可,引用计数也不需要进行改变,因为单例模式下的对象是不允许销毁的,所以也就不用保留
6·因为全局实例不允许释放,所以在MRC 的项目里retain,release,autorelease方法均需重写

五、单例设计模式的代码具体实现:

(1)写一个简单单例

//static关键字的作用有两个,显然在此的作用是下面作用的第一个。
//1. static作用于变量时,该变量只会定义一次,以后在使用时不会重新定义,
当static作用于全局变量时说明: 该变量只能在当前文件可以访问,其他文件中不能访问;
//2. static作用于函数时与作用于全局变量类时,
表示声明或定义该函数是内部函数(又叫静态函数),
在该函数所在文件外的其他文件中无法访问此函数;

#import " File.h ";
 static File * instance  = nil;
 @implementation File
     //实现一个实例构造方法检查上面声明的静态实例是否为nil,
     //如果 '是' 则  '' 新建''  并 '' 返回 ''  一个本类的实例     
 +(id)shareInstance  {
      @synchronized(self){
          if(instance == nil)  {
             instance = [[File alloc]init];
           }
       }
      return instance;
  }
     ```
    
 #####(2) 写一个单例 ( 里面有一个属性)
     .h 文件
     @interface DataModel : NSObject
     @property (strong, nonatomic) NSString* imageUrl;
     +(DataModel*)sharedModel;
     @end
     .m文件
     #import "DataModel.h"
     @implementation DataModel
     //为单例对象实现一个静态实例,并初始化,然后设置成nil,
     static DataModel* dataModel = nil;
     +(DataModel*)sharedModel
     {
         if (dataModel == nil) {
          dataModel = [[DataModel alloc] init];
         }
         return dataModel;
     }
     
     -(id)init
     {
         if (self = [super init]) {
             //往往放一些要初始化的变量
             self.imageUrl = [[NSString alloc] init];
         }
         return self;
     } @end
>之后都需要 重写allocWithZone方法、用来保证其他人直接使用alloc和init试图获得一个新实例子的时候不产生一个新实例,目的是限制这个类只创建一个对象。并在需要的时候重写copyWithZone、retain、authorelease 等方法.

#####(3)以下是不同的创建方式,其实代码都是大同小异:

//静态的该类的实例
static ClassA * classA = nil;
@implementation ClassA
+ (ClassA *)sharedManager

@synchronized(self) {
if (!classA) {
classA = [[super allocWithZone:NULL]init];
}
return classA;
}
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedManager] retain];
}

######(3.1)

//第一步:静态实例,并初始化。
static MySingleton *sharedObj = nil;
@implementation MySingleton
//第二步:实例构造检查静态实例是否为nil

  • (MySingleton*) sharedInstance {
    @synchronized (self) {
    if (sharedObj == nil) {
    sharedObj = [[self alloc] init];
    }
    }
    return sharedObj;
    }
    //第三步:重写allocWithZone方法
  • (id) allocWithZone:(NSZone *)zone {
    @synchronized (self) {
    if (sharedObj == nil) {
    sharedObj = [super allocWithZone:zone];
    return sharedObj;
    }
    }
    return nil;
    }
  • (id)init {
    @synchronized(self) {
    [super init];
    return self;
    }
    }

//第四步在需要的时候重写copyWithZone

  • (id) copyWithZone:(NSZone *)zone {
    return self;
    }

//下面的是在MRC中重写的,ARC 不考虑 具体问什么要重写请看下一章
- (id) retain
{
return self;
}

 - (unsigned) retainCount
 {
     return UINT_MAX;
    // return NSUIntgerMax;
 }
 
 - (oneway void) release
 {
 }
 
 - (id) autorelease
 {
     return self;
 }
 
 -(void)dealloc {
 }
 @end
#####  六简单介绍下GCD实现单例模式(具体请看下一章)
iOS的单例模式有两种官方写法,如下:
 //不使用GCD作对比:

import "ServiceManager.h"

static ServiceManager *defaultManager;
@implementation ServiceManager
+(ServiceManager *)defaultManager {
if(!defaultManager)
defaultManager=[[self allocWithZone:NULL] init];
return defaultManager;
}
@end

//使用GCD:在iOS4之后的另外一种写法:
     

import "ServiceManager.h"

 @implementation ServiceManager
 static ServiceManager * sharedManager =nil ;
 +(ServiceManager *)sharedManager {
     static dispatch_once_t predicate;
     //static ServiceManager * sharedManager =nil ;
     dispatch_once(&predicate, ^{
         sharedManager = [[ServiceManager alloc] init];
     });
     return sharedManager;
 }
 @end
 /*当用户使用alloc init方法创建实体类时,
 也可以保证所创建的事例对象是同一个。*/
 //用类方法创建类的实体,方便外界使用。
  • (instancetype)allocWithZone:(struct _NSZone *)zone
    {
    static dispatch_once_t onceToken;
    //static ServiceManager * sharedManager =nil ;
    dispatch_once(&onceToken, ^{
    sharedManager = [super allocWithZone:zone];
    });
    return sharedManager;
    }
    //重写copyWithZone方法,可以保证用户在使用copy关键字时,创建的类的实例是同一个。
  • (id)copyWithZone:(NSZone *)zone
    {
    return sharedManager;
    }

当然在xxx.h文件中需要
+(ServiceManager *)sharedManager; 接口。
而 dispatch_once_t 这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次!
// (instance ɪnst(ə)ns, 例子 share ʃɛː, 共用 )
该写法来自 objcolumnist,文中提到,该写法具有以下几个特性:

  1. 线程安全。 2. 满足静态分析器的要求 3. 兼容了ARC
 关于dispatch_once,这个函数,下面是官方文档介绍:
> dispatch_once
     Executes a block object once and only once for the lifetime of an application.
       void dispatch_once(
         dispatch_once_t *predicate,
         dispatch_block_t block);
     Parameters
     predicate
     A pointer to a dispatch_once_t structure that is used to test whether the block has completed or not.
     block
     The block object to execute once.
     Discussion
     This function is useful for initialization of global data (singletons) in an application. Always call this function before using or testing any variables that are initialized by the block.
     If called simultaneously from multiple threads, this function waits synchronously until the block has completed.
     The predicate must point to a variable stored in global or static scope. The result of using a predicate with automatic or dynamic storage is undefined.
     Availability
     Available in iOS 4.0 and later.
     Declared In
     dispatch/once.h
     
    > 我们看到,该方法的作用就是执行且在整个程序的声明周期中,仅执行一次某一个block对象。简直就是为单例而生的嘛。而且,有些我们需要在程序开头初始化的动作,如果为了保证其,仅执行一次,也可以放到这个dispatch_once来执行。
     然后我们看到它需要一个断言来确定这个代码块是否执行,这个断言的指针要保存起来,相对于第一种方法而言,还需要多保存一个指针。
     方法简介中就说的很清楚了:对于在应用中创建一个初始化一个全局的数据对象(单例模式),这个函数很有用。
     如果同时在多线程中调用它,这个函数将等待同步等待,直至该block调用结束。
     这个断言的指针必须要全局化的保存,或者放在静态区内。使用存放在自动分配区域或者动态区域的断言,dispatch_once执行的结果是不可预知的。
     总结:1.这个方法可以在创建单例或者某些初始化动作时使用,以保证其唯一性。2.该方法是线程安全的,所以请放心大胆的在子线程中使用。(前提是你的dispatch_once_t *predicate对象必须是全局或者静态对象。这一点很重要,如果不能保证这一点,也就不能保证该方法只会被执行一次。)


你可能感兴趣的:(iOS 单例类详解 ( 一 ))