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对象必须是全局或者静态对象。这一点很重要,如果不能保证这一点,也就不能保证该方法只会被执行一次。)


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,222评论 4 34
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,121评论 29 470
  • 1 场景问题# 1.1 读取配置文件的内容## 考虑这样一个应用,读取配置文件的内容。 很多应用项目,都有与应用相...
    七寸知架构阅读 6,630评论 12 68
  • 一共收集了62个 Android Studio 使用小技巧和快捷键。 根据这些小技巧的使用场景,本文将这62个小技...
    01427271c047阅读 466评论 0 3