iOS [Objective-C] 单例模式

单例模式

单例模式,是一种常用的软件设计模式。

概念

  1. 通过单例可以保证系统中一个类只有一个对象实例;
  2. 单例的核心结构中只包含一个被称为单例的特殊类。

使用场合

在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)。
例如:登陆控制器,网络数据请求,音乐播放器等一个工程需要使用多次的控制器或方法。

优缺点

优点:

  1. 单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
  2. 如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
  3. 单例模式因为类控制了实例化过程,所以类可以更加灵活修改实例化过程。

缺点:

  1. 单例对象一旦建立,对象指针是保存在静态区的,单例对象在堆中分配的内存空间,会在应用程序终止后才会被释放。
  2. 单例类无法继承,因此很难进行类的扩展。
  3. 单例不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。

注意

  • 我们在使用单例类之前,一定要考虑好单例类是否适合和类以后的扩展性,避免盲目滥用单例
  • 例模式不可以使用继承,因为使用继承,同时也会继承静态变量,当子类和父类同时创建的时候只会创建一个先创建的实例对象。

在OC中的使用

  • NSNotificationCenter 中的 defaultCenter 负责全局的消息分发;
  • NSFileManager 的 defaultManager 统一负责物理文件的管理;
  • NSUserDefaults 的 standardUserDefaults 统一管理用户的配置文件;

单例在ARC中的实现

iOS [Objective-C] 单例模式 在ARC中的实现

ARC实现单例步骤

  1. 在类的内部提供一个 static修饰的全局变量
  2. 提供一个类方法,方便外界访问
  3. 重写+allocWithZone 方法(注意线程安全),保证永远都只为单例对象分配一次内存空间
  4. 严谨起见,重写-copyWithZone方法和-MutableCopyWithZone方法

注意: <NSCopying>的协议,并实现相应的方法,防止别人误调copy方法而崩溃

ARC 单例实现源码

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface CustomManager : NSObject

+ (instancetype)new NS_UNAVAILABLE; // 禁止使用

//命名规范 share+类名 | default+类名 |  类名
+ (instancetype)shareCustomManager;


@end

NS_ASSUME_NONNULL_END
@interface CustomManager ()<NSCopying>

@end

@implementation CustomManager

// 提供一个static修饰的全局变量,强引用着已经实例化的单例对象实例(防止外部访问)
static CustomManager *_manager;

// 类方法,返回一个单例对象
+(instancetype)shareCustomManager
{
    // return _manager;
    // 注意:这里建议使用self,而不是直接使用类名(考虑继承)
    return [[self alloc]init];
}

// new -> alloc -> allocWithZone 最终必须调用的方法
// 保证永远只分配一次存储空间
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    // 使用GCD中的一次性代码
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _manager = [super allocWithZone:zone];
    });
    return _manager;
}

// 让代码更加的严谨 ,也要重写copyWithZone 和 mutableCopyWithZone
- (id)copyWithZone:(nullable NSZone *)zone
{
    return [[self class] allocWithZone:zone];
    return _manager;
}

- (id)mutableCopyWithZone:(nullable NSZone *)zone
{
    return _manager;
}
@end

单利创建的几种方式

1、传统方法

+ (instancetype)traditionSingleton{
    static CustomManager *singleton = nil;
    if (singleton == nil){
        singleton = [[self alloc] init];
    }
    return singleton;
}

2、GCD方式

+ (instancetype)gcdSingleton{
    static CustomManager *singleton = nil;
    //给单例加了一个线程锁
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singleton = [[self alloc] init];
    });
    return singleton;
}

3、加锁方式
为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁。

+ (instancetype)singleton{
    static CustomManager *singleton = nil;
 // 使用加锁的方式,保证只分配一次存储空间
    if (singleton == nil) { //防止频繁加锁
        @synchronized(self) {
            if(singleton == nil) { //防止创建多次
                singleton = [[self alloc] init];
            }
        }
    }
    return singleton;
}

单例在MRC中的实现

MRC单例实现步骤

  1. 在类的内部提供一个static修饰的全局变量
  2. 提供一个类方法,方便外界访问
  3. 重写+allocWithZone方法(注意线程安全),保证永远都只为单例对象分配一次内存空间
  4. 严谨起见,重写-copyWithZone方法和-MutableCopyWithZone方法
  5. 重写release方法
  6. 重写retain方法
  7. 建议在retainCount方法中返回一个最大值

配置MRC环境

  1. 注意ARC不是垃圾回收机制,是编译器特性
  2. 配置MRC环境:build setting -> 搜索automatic ref -> 修改为N0

MRC中单例代码实现

配置好MRC环境之后,在ARC代码基础上重写下面的三个方法即可

- (oneway void)release
{
   
}

- (instancetype)retain
{
    return _instance;
}
// 惯用法,有经验的程序员通过打印retainCount这个值可以猜到这是一个单例
- (NSUInteger)retainCount
{
    return MAXFLOAT;
}

- (instancetype)autorelease{

}

在MRC环境下,如果用户retain了一次,那么直接返回instance变量,不对引用计数器+1;

如果用户release了一次,那么什么都不做,因为单例模式在整个程序运行过程中都拥有且只有一份,程序退出之后被释放,所以不需要对引用计数器操作.

扩展

Q:new -> alloc -> allocWithZone 最终必须调用的方法
A:通过打印方式查看调用方法;
+[CustomManager new]
+[CustomManager alloc]
+[CustomManager allocWithZone:]

说明

避免使用new创建对象,从安全和设计角度来说我们应该对初始化所有属性,提高程序的健壮性和复用性。
不论是何种情况,在类中至少包含一个构造函数是一种很好的编程实践,如果类中有属性,好的实践往往是初始化这些属性。
——以上摘自《The Object-Oriented Thought Process》 by Matt Weisfeld

Q:写一份单例代码在ARC和MRC环境下都适用?
A:可以使用条件编译来判断当前项目环境是ARC还是MRC

Q:条件编译的代码呢,么么哒?
A:条件编译

宏判断是否ARC环境

在PCH文件使用代参数的宏和条件编译,利用条件编译来判断是ARC还是MRC环境。

#if __has_feature(objc_arc)
  // ARC
#else
  // MRC
#endif

Q:使用带参数的宏完成通用版单例模式代码
A:

  • 注意条件编译的代码不能包含在宏定义里面
  • 宏定义的代码只需要写一次就好,之后直接拖到项目中用就OK

copy 和 mutableCopy 区别

  1. mutableCopy 创建一个新的可变对象,并初始化为原对象的值,新对象的引用计数为 1;
  2. copy 返回一个不可变对象。分两种情况:
    (1)若原对象是不可变对象,那么返回原对象,并将其引用计数加1;
    (2)若原对象是可变对象,那么创建一个新的不可变对象,并初始化为原对象的值,新对象的引用计数为 1。

扩展宏

这时我们就可以一劳永逸,任何项目中,当我们要使用单例类的时候只要在项目中导入PCH文件然后
在.h文件中调用singleH(类名)
在.m文件中调用singleM(类名)

创建类时直接调用share类名方法即可。

PCH文件Single.h
#define singleH(name) +(instancetype)share##name;

#if __has_feature(objc_arc)

#define singleM(name) static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)share##name\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}
#else
#define singleM static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)shareTools\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(oneway void)release\
{\
}\
\
-(instancetype)retain\
{\
return _instance;\
}\
\
-(NSUInteger)retainCount\
{\
return MAXFLOAT;\
}
#endif

更新记录

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

推荐阅读更多精彩内容

  • 导读: 一、什么是单例模式 二、单例的作用 三、常见的单例类 四、自定义单例类的方法 一、什么是单例模式 单例模式...
    千山小畻阅读 1,248评论 0 0
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个...
    LeaderBiao阅读 1,012评论 0 1
  • 内容大纲:1:单例模式介绍 2:单例模式的使用 先来说说写作:关于写作,我(业余的)个人认为最初写作的目的就...
    潇湘候晨雪阅读 1,259评论 0 0
  • 12月25号,我参加了天津趁早读书会举办的2016年趁早趴体。我真正开始知道趁早是在加入100天搭配小组,搭...
    呆呆望会儿天阅读 178评论 0 0
  • 今天最重要的事情大概就是:我妈被狗咬了。 一家人吃过午饭,我、弟弟、小弟弟,还有老爸四个人围在电脑前看《王牌对王牌...
    penny胖妮阅读 413评论 5 3