单例模式作用

  • 可以保证在程序运行过程中,一个类只有一个实例,而且该实例易于供外界使用
  • 从而方便地控制了实例个数,并节约系统资源

单例模式使用场合

  • 在整个引用程序中,共享一份资源(这份资源只需要创建初始化1次,只分配一次存储空间)

    • 例如:背景音乐,音频调节器等

单例的简单使用

  • 使用单例的目的就是为了要在程序运行过程中,共享一份资源,且这份资源只会初始化一次,只分配一次存储空间,节约系统资源;先来看一下平时我们创建对象时,内存地址的变化情况:
创建对象内存分配地址演示

1.这里用SJTools这个类来演示

//  创建SJTools类
SJTools *tool1 = [[SJTools alloc] init]; SJTools *tool2 = [SJTools new]; SJTools *tool3 = [[SJTools alloc] init]; NSLog(@"\ntool1%@:\ntool2%@:\ntool3%@:",tool1, tool2, tool3);

执行结果:从打印的内存地址可以看出——每次创建同一个类,都会分配新的内存地址,这在某些场合是没有必要的(比如播放音乐,一般我们不会有在同一个播放器同时播放多首歌的情况出现),而且系统的资源是有限的,特别是移动设备,所以前辈们总结出新的模式——单例

单例创建演示

1.这里依旧使用SJTools这个类演示

  • 现在我们的目的是类无论创建多少次,都只分配一次存储空间,而分配存储空间的操作是在alloc:中执行,所以我们要重写类中的alloc:类方法
  • 那么我们通过以下几种思路来实现单例
+ (instancetype)alloc
+ (instancetype)allocWithZone:(struct _NSZone *)zone

在重写alloc:过程中,我们发现有个allocWithZone:方法,这个和alloc:方法有什么区别呢?——其实,alloc:方法内部最终会去调用allocWithZone:方法来分配存储空间,所以为了能更深层控制,我们放弃重写alloc:方法,直接重写allocWithZone:方法。

思路一(ARC模式下单例模式的实现)

1.因为是类方法,且不想外面获取到这个变量,所以我们先定义一个静态变量

//  声明一个静态变量(不然外界获取到)
static SJTools *_instance;

1.2 重写allocWithZone:方法

1.2.1 第一种方式 —— 懒加载

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 考虑到使用时可能在多条线程同时执行此方法任务,那么就可能引发线程安全问题,所以我们需要对线程进行加锁操作
@synchronized(self) {
if (_instance == nil) { // 如果为nil就执行以下操作
_instance = [super allocWithZone:zone];
}
}
return _instance;
}

1.2.2 第二种方式 —— GCD一次性代码

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 我们也可以使用GCD提供给我们的一次性代码函数来实现,因为它本身就是线程安全的
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
}); return _instance;
}

到这里,简单的单例就实现了,但是还有一些问题需要解决,先不管,先Run来试一下是不是管用

执行结果:从结果可以看出,确实所有创建的操作只分配了一次内存

  • 为了让我们的单例严谨一点,我们还要考虑copy这种情况,所以我们还要重写copy和mutableCopy

1.1 要重写copy和mutableCopy方法,必须先遵守协议

@interface SJTools()<NSCopying, NSMutableCopying>

1.2 重写copy和mutableCopy方法

- (id)copyWithZone:(NSZone *)zone
{
// 直接返回变量即可,因为只有创建了对象,才能使用copy方法
return _instance;
} - (id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}
  • 为了供外界方便访问,我们还需要提供相应的调用方法,一般我们会提供给外界一个类方法供外界使用,这也牵扯到命名的问题,怎样命名才规范,这里顺便提一下

    • 一般常见的命名形式有这几种 —— share 、share + 类名 、default、default + 类名

      • 优点:

        • 减少沟通成本
        • 方便外界访问

1.在.h文件中声明

/**
* 获取单例对象
*/
+ (instancetype)shareSJTools;

2.在.m文件中实现

+ (instancetype)shareSJTools
{
// 返回实例对象
return [[self alloc] init];
}
  • 到这来我们的单例模式就实现了,但是上面的方式在MRC环境下就不好用了,那有没有同时在ARC和MRC中都可使用的方法呢?下面我们就来解决这样的问题。

思路二(MRC下单例模式的实现)

首先,需要MRC的环境,需要先修改一下XCode配置

接下来需要修改下前面创建对象的代码,让其符合MRC规则,并运行

    //  创建SJTools类
SJTools *tool1 = [[SJTools alloc] init]; [tool1 release]; SJTools *tool2 = [SJTools new];
[tool2 release]; SJTools *tool3 = [[SJTools alloc] init];
[tool3 release]; NSLog(@"\ntool1%@:\ntool2%@:\ntool3%@:",tool1, tool2, tool3);

执行结果:编译器提示我们消息发送给已经释放的对象,这是因为我们在创建对象后,对其进行了release操作,这样对象的计数器为0,系统就将会其释放,所以在设计单例时我们还需要考虑MRC的环境。

  • 因为单例的生命周期是从被创建的那一刻起,到程序运行结束,也就是说,我们可以不用理会MRC中的规则,但是又不想破坏使用者的使用习惯,那么我们可以在类中重写retainreleaseretainCount方法,使其不受外界影响。
- (oneway void)release
{
// 因为我们不理会MRC规则,所以在release方法中可以什么都不做
} - (instancetype)retain
{
// 因为retain方法要求返回一个instancetype返回值,我们直接返回变量
return _instance;
} - (NSUInteger)retainCount
{
// 在之前的一些早期的MRC项目中发现,单例中普遍会直接给retaiCount一个MAXFLOAT作为返回值,这样做应该是为了让外界认为这个类是单例,进一步减少沟通成本
return MAXFLOAT;
}

执行结果:这样程序就能正常运行了

为了让单例在ARCMRC模式下都能愉快使用,我们可以使用宏来判断当前系统环境,并做出对应处理

#ifdef __has_feature(objc_arc)

//  ARC环境
#else // MRC环境
- (oneway void)release
{
// 因为我们不理会MRC规则,所以在release方法中可以什么都不做
} - (instancetype)retain
{
// 因为retain方法要求返回一个instancetype返回值,我们直接返回变量
return _instance;
} - (NSUInteger)retainCount
{
// 在之前的一些早期的MRC项目中发现,单例中普遍会直接给retaiCount一个MAXFLOAT作为返回值,这样做应该是为了让外界认为这个类是单例,进一步减少沟通成本
return MAXFLOAT;
} #endif

到此,我们的单例就可以欢快地使用了。


单例模式使用注意

  • 单例是不可继承的,我们可以来演示一下

首先,新建一个SJNewTools类,并让他继承SJTools,然后打印一下创建SJTools对象和SJNewTools对象后的内存地址

    //  创建SJTools类
SJTools *tool1 = [SJTools shareSJTools]; // 创建SJNewTools类
SJNewTools *newTool1 = [SJNewTools shareSJTools]; NSLog(@"\ntool1%@:\nnewTool1%@:\n",tool1, newTool1);

执行结果:打印出来的内存地址是相同的,而且他们的真实类型都是SJTools

为什么会这样?

其实是因为alloc:方法内部会调用allocWithZone:方法,在allocWithZone:内会对_instance这个变量进行初始化,给它分配存储空间;在这个阶段的时候,系统会去判断这个变量的真实类型,因为当前变量的类型是SJTools类型,所以_instance变量就是SJTools类型,后面SJNewTools继承了SJTools,但是内部使用的是同一个变量,就造成了上面的情况,所以,单例是不能继承的。

懒人福音 —— 单例的复用

  • 在开发中,一般出现像这样可能复用的代码,而又不能继承的方式,那简直对我们这种懒人的晴天霹雳!难道每次要使用单例都要敲着这样一大串恶心的代码么?其实,我们还可以用宏来解决这样的事情嘛O(∩_∩)O

首先,因为单例可以分为2部分,一部分是.h文件,一部分是.m文件,所以,宏要分开来写 —— 先创建一个.h文件

Singleton.h文件

1.因为我们在只需要提供一个方法给外界使用,而且为了让其用起来更加清晰,我们给其加入参数

宏内怎么接收和使用参数呢?

  • 格式:

    • #define 宏名(参数名) ##参数名
#define SingletonH(name) +(instancetype)share##name;

2.将之前我们在SJTools类的.m文件所做的操作拷贝过来,但会发现除了第一行以外的所有代码段都是没有列入宏的范围内(颜色不同);这时候需要使用""符来进行连接,同样,我们要让外界传入参数来改变方法名,让其与.h文件相对应;同时变量的类型直接修改为id类型即可。

#define SingletonW(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;\
}

这样宏就已经做好了,我们只需要在要使用单例的类中先引入头文件,然后在.h文件中这样使用

#import <Foundation/Foundation.h>
#import "Singleton.h" @interface SJTools : NSObject SingletonH(SJTools) @end

.m文件中这样使用就可以了

@implementation SJTools

SingletonW(SJTools)

@end

为了使我们的宏能同时ARC和MRC,我们需要将宏判断部分也加入到单例宏中,但是宏判断是不能内嵌的,所以只能先判断环境,再分别进行相应操作

#define SingletonH(name) +(instancetype)share##name;

#if __has_feature(objc_arc)

//  ARC环境
#define SingletonW(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 // MRC环境
#define SingletonW(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;\
}\
- (oneway void)release\
{\
}\
- (instancetype)retain\
{\
return _instance;\
}\
- (NSUInteger)retainCount\
{\
return MAXFLOAT;\
} #endif

好了,这样我们的单例宏就完成了,只要在需要的项目中导入这个宏,然后在需要的地方使用宏就可以了!

iOS 单例模式 浅叙的更多相关文章

  1. iOS单例模式(Singleton)写法简析

    单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 1.单例模式的要点: 显然单例模式的要点有三个:一是某个类只能有一个实例: ...

  2. IOS单例模式(Singleton)

    IOS单例模式(Singleton)   单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 1.单例模式的要点: 显然单例模 ...

  3. iOS 多线程 浅述

    什么是进程? 进程是指在系统中正在运行的一个应用程序. 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内. 什么是线程? 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程 ...

  4. IOS 单例模式的写法

    iOS的单例模式有两种官方写法,如下: 1)不使用GCD的方式 #import "Manager.h" static Manager *manager; @implementati ...

  5. iOS单例模式

    单例模式用于当一个类只能有一个实例的时候, 通常情况下这个“单例”代表的是某一个物理设备比如打印机,或是某种不可以有多个实例同时存在的虚拟资源或是系统属性比如一个程序的某个引擎或是数据.用单例模式加以 ...

  6. IOS中 浅谈iOS中MVVM的架构设计与团队协作

    今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  7. iOS Architectures 浅谈

    iOS项目打包,或者只是在项目里面调用第三方静态库抑或是自己新建一个静态库,就要无可避免的和Architectures打交道.Architectures在Targets面板的Build Setting ...

  8. iOS开发--浅谈CocoaAsyncSocket编程

    Socket就是一种特殊的文件.它是一个连接了两个用户的文件,任何一个用户向Socket里写数据,另一个用户都能看得到,不管这两个用户分布在世界上相距多么遥远的角落,感觉就像坐在一起传纸条一样. 这么 ...

  9. ios 单例模式(懒汉式)

    1. 单例模式的作用 可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问 从而方便地控制了实例个数,并节约系统资源 2. 单例模式的使用场合 在整个应用程序中,共享一份资源(这份资源 ...

随机推荐

  1. openjudge7834:分成互质组 解析报告

    7834:分成互质组 总时间限制:  1000ms 内存限制:  65536kB 描述 给定n个正整数,将它们分组,使得每组中任意两个数互质.至少要分成多少个组? 输入 第一行是一个正整数n.1 &l ...

  2. ROS 常用命令字典

    版权声明:本文为博主原创文章,转载请标明出处: http://www.cnblogs.com/liu-fa/p/5761448.html 该博文适合已经具备一定的ROS编程基础的人,快速查看ROS相关 ...

  3. 基于MDK-ARM创建STM32L-DISCOVERY Project

    本文只针对使用MDK-ARM建立软件开发环境,并基于STM32L1xx_StdPeriph_Lib_V1.1.1库及其Examples,其余情况可参考UM1451 User manual Gettin ...

  4. WPF系列:无边框窗口

    <Window x:Class="Ares.Animations.Window3" xmlns="http://schemas.microsoft.com/winf ...

  5. ASP.NET访问Excel 失败的解决方法(错误号:80070005,8000401a)

    用asp.net把值写入Excel在本地测试通过,然后提交服务器后老是写入不成功 并提示错误: Retrieving the COM class factory for component with ...

  6. Hello, Android 快速入门

    Hello, Android Android 开发与 Xamarin 简介 在这两节指南中,我们将 (使用 Xamarin Studio或 Visual Studio)建立我们的第一个 Xamarin ...

  7. DataTable 除去列中重复值

    DataTable dtPCI = dtblSourceData.DefaultView.ToTable(true, new string[] { "Server Cell PCI" ...

  8. Linux安装JDK1.8

    1. 安装前,最好先删除Linux自带的OpenJDK: (1)运行java-version,会发现Linux自带的OpenJDK,运行rpm -qa | grep OpenJDK,找出自带的Open ...

  9. Java 读取Properties文件时应注意的路径问题

    1. 使用Class的getResourceAsStream()方法读取Properties文件(资源文件)的路径问题:  InputStream in = this.getClass().getRe ...

  10. hibernate----N-1(一)

    *************************hibernate.cfg.xml <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE ...