设置模式之单例模式(附上一个Objective-C编写的播放音乐的单例类)
在查阅Cocoa Touch开发文档时,会发现框架中随处可见的大量单例类,比如说,UIApplication、NSFileManager 等。
- UIApplication
框架中极为常用的一个单例类,它提供了一个控制并协调iOS应用程序的集中点。每一个应用程序有且只有一个UIApplication的实例,它由UIApplicationMain函数在应用程序启动的时候创建为单例对象。之后,对于同一UIApplication实例可以通过sharedApplication类方法进行访问。
UIApplication对象为应用程序处理许多内务管理任务,包括传入用户事件的最初路由,以及为UIControl分发动态消息给合适的目标对象,它还维护应用程序中所有打开UIWindows对象的列表。应用程序对象总是会被分配一个UIApplicationDelegate对象,应用程序会把重要的运行时事件通知给它,比如iOS程序中的应用程序启动、内存不足时的警告、应用程序终止和后台进程执行。这让委托(delegate)有机会作出适当的响应。
[UIApplication sharedApplication].windows;
[UIApplication sharedApplication].keyWindow;
[UIApplication sharedApplication].version;
2. NSFileManager
NSFileManager是文件管理常用的一个单例类,对于它可以通过defaultManager类方法进行访问。刚好前两天封装了一个它的使用,下面是这个类的使用的部分代码:
#import <Foundation/Foundation.h> typedef enum {
ZYFileToolTypeDocument,
ZYFileToolTypeCache,
ZYFileToolTypeLibrary,
ZYFileToolTypeTmp
} ZYFileToolType; @interface ZYFileTool : NSObject
/** 获取Document路径 */
+ (NSString *)getDocumentPath;
/** 获取Cache路径 */
+ (NSString *)getCachePath;
/** 获取Library路径 */
+ (NSString *)getLibraryPath;
/** 获取Tmp路径 */
+ (NSString *)getTmpPath;
/** 此路径下是否有此文件存在 */
+ (BOOL)fileIsExists:(NSString *)path; /**
* 创建目录下文件
* 一般来说,文件要么放在Document,要么放在Labrary下的Cache里面
* 这里也是只提供这两种存放路径
*
* @param fileName 文件名
* @param type 路径类型
* @param context 数据内容
*
* @return 文件路径
*/
+ (NSString *)createFileName:(NSString *)fileName type:(ZYFileToolType)type context:(NSData *)context;
@end #import "ZYFileTool.h" @implementation ZYFileTool + (NSString *)getRootPath:(ZYFileToolType)type
{
switch (type) {
case ZYFileToolTypeDocument:
return [self getDocumentPath];
break;
case ZYFileToolTypeCache:
return [self getCachePath];
break;
case ZYFileToolTypeLibrary:
return [self getLibraryPath];
break;
case ZYFileToolTypeTmp:
return [self getTmpPath];
break;
default:
break;
}
return nil;
} + (NSString *)getDocumentPath
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; } + (NSString *)getCachePath
{
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
} + (NSString *)getLibraryPath
{
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
} + (NSString *)getTmpPath
{
return NSTemporaryDirectory();
} + (BOOL)fileIsExists:(NSString *)path
{
if (path == nil || path.length == 0) {
return false;
}
return [[NSFileManager defaultManager] fileExistsAtPath:path];
} + (NSString *)createFileName:(NSString *)fileName type:(ZYFileToolType)type context:(NSData *)context
{
if (fileName == nil || fileName.length == 0) {
return nil;
}
NSString *path = [[self getRootPath:type] stringByAppendingString:fileName];
if ([self fileIsExists:path]) {
if (![[NSFileManager defaultManager] removeItemAtPath:path error:nil]) {
return nil;
}
}
[[NSFileManager defaultManager] createFileAtPath:path contents:context attributes:nil];
return path;
}
@end
3. 单例的概念以及在iOS开发中的实现一个单例类
概念:保证一个类只有一个实例,并且提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象并访问,但它不能防止你实例化多个对象,一个最好的方法是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。
基于上面的原理,我们可以很快的实现一个“简单的单例”(但事实上,它是有很大问题的)
简单单例代码:
#import <Foundation/Foundation.h> @interface ZYSimpleManager : NSObject
+ (instancetype)defaultManager;
@end #import "ZYSimpleManager.h" static ZYSimpleManager *_instance = nil;
@implementation ZYSimpleManager
+ (instancetype)defaultManager
{
if (_instance == nil) {
_instance = [[self alloc] init];
}
return _instance;
} - (instancetype)init
{
if (_instance) return _instance; if (self = [super init]) {
/**
* 初始化
*/
}
return self;
}
@end
初看之下,只要_instance被实例化一次之后,就不会再给它分配存储空间了,这样保证了一个类只实例化一次,好像是对的?
单例,一个常见的担忧是它往往不是线程安全的,也就是上面代码出现的情况了,这个担忧是十分合理的,基于它们的用途:单例常常被多个控制器同时访问。
当前状态下,上面的代码非常简单,然而if的条件分支不是线程安全的,如果想要在控制台看到输出,可以这样调试下上面的代码:
#import "ZYSimpleManager.h" static ZYSimpleManager *_instance = nil;
@implementation ZYSimpleManager
+ (instancetype)defaultManager
{
if (_instance == nil) {
_instance = [[self alloc] init]; NSLog(@"%d",(int)[NSThread currentThread]); //加上这句,判断当前是哪条线程
}
return _instance;
} - (instancetype)init
{
if (_instance) return _instance; if (self = [super init]) {
/**
* 初始化
*/
}
return self;
}
@end
在viewController里面调用如下代码:
#import "ViewController.h"
#import "ZYSimpleManager.h"
@interface ViewController () @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[ZYSimpleManager defaultManager];
}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[ZYSimpleManager defaultManager];
}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[ZYSimpleManager defaultManager];
}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[ZYSimpleManager defaultManager];
}); }
会看到如下打印:
可以发现,有多个线程同时实例化了这个单例对象。
为什么会出现这样的错误?就是因为这样的代码是线程不安全的,多个线程可以在同一时间访问,从而导致多个对象被创建。
这个输出展示了临界区被执行多次,而它本来只应该执行一次。现在,固然是我强制这样的状况发生,但可以想像一下这个状况在无意间发生会导致什么?
要纠正这个状况,实例化代码应该只执行一次,并阻塞其它实例在 if 条件的临界区运行。这刚好就是 dispatch_once 能做的事。
正确代码:
#import "ZYSimpleManager.h" static ZYSimpleManager *_instance = nil;
@implementation ZYSimpleManager
+ (instancetype)defaultManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init]; NSLog(@"%@",[NSThread currentThread]);
});
return _instance;
} + (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
} - (instancetype)init
{
__block ZYSimpleManager *temp = self; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ((temp = [super init]) != nil) {
/**
* 初始化数据
*/
}
});
self = temp;
return self;
}
@end
当你将代码改成这样,再运行上面viewController里面的代码,不论多少异步线程访问这个单例,永远只会有一次打印。有且仅有一个单例的实例——这就是我们对单例的期望!
基本上,iOS开发中,单例的实现以及为什么要这样实现已经总结完了,还得说说,单例的命名规范,一般我们将实例化它的那个类方法以shared、manager等开头,我这里是仿照NSFileManager的defaultManager开头的。下面是一个音乐播放单例的实现:
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@interface ZYAudioManager : NSObject
+ (instancetype)defaultManager; //播放音乐
- (AVAudioPlayer *)playingMusic:(NSString *)filename;
- (void)pauseMusic:(NSString *)filename;
- (void)stopMusic:(NSString *)filename; //播放音效
- (void)playSound:(NSString *)filename;
- (void)disposeSound:(NSString *)filename;
@end #import "ZYAudioManager.h" @interface ZYAudioManager ()
@property (nonatomic, strong) NSMutableDictionary *musicPlayers;
@property (nonatomic, strong) NSMutableDictionary *soundIDs;
@end static ZYAudioManager *_instance = nil; @implementation ZYAudioManager
+ (instancetype)defaultManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
} - (instancetype)init
{
__block ZYAudioManager *temp = self; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ((temp = [super init]) != nil) {
_musicPlayers = [NSMutableDictionary dictionary];
_soundIDs = [NSMutableDictionary dictionary];
}
});
self = temp;
return self;
} + (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
} //播放音乐
- (AVAudioPlayer *)playingMusic:(NSString *)filename
{
if (filename == nil || filename.length == 0) return nil; AVAudioPlayer *player = self.musicPlayers[filename]; //先查询对象是否缓存了 if (!player) {
NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil]; if (!url) return nil; player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil]; if (![player prepareToPlay]) return nil; self.musicPlayers[filename] = player; //对象是最新创建的,那么对它进行一次缓存
} if (![player isPlaying]) { //如果没有正在播放,那么开始播放,如果正在播放,那么不需要改变什么
[player play];
}
return player;
} - (void)pauseMusic:(NSString *)filename
{
if (filename == nil || filename.length == 0) return; AVAudioPlayer *player = self.musicPlayers[filename]; if ([player isPlaying]) {
[player pause];
}
}
- (void)stopMusic:(NSString *)filename
{
if (filename == nil || filename.length == 0) return; AVAudioPlayer *player = self.musicPlayers[filename]; [player stop];
} //播放音效
- (void)playSound:(NSString *)filename
{
if (!filename) return; //取出对应的音效ID
SystemSoundID soundID = (int)[self.soundIDs[filename] unsignedLongValue]; if (!soundID) {
NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
if (!url) return; AudioServicesCreateSystemSoundID((__bridge CFURLRef)(url), &soundID); self.soundIDs[filename] = @(soundID);
} // 播放
AudioServicesPlaySystemSound(soundID);
} //摧毁音效
- (void)disposeSound:(NSString *)filename
{
if (!filename) return; SystemSoundID soundID = (int)[self.soundIDs[filename] unsignedLongValue]; if (soundID) {
AudioServicesDisposeSystemSoundID(soundID); [self.soundIDs removeObjectForKey:filename]; //音效被摧毁,那么对应的对象应该从缓存中移除
}
}
@end
viewController里面的代码:
#import "ViewController.h"
#import "ZYAudioManager.h"
@interface ViewController () @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[ZYAudioManager defaultManager] playingMusic:@"12309111.mp3"]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[[ZYAudioManager defaultManager] stopMusic:@"12309111.mp3"];
}
@end
这个单例是ARC情况下的写法,在MRC里面,只需要对单例类加上内存管理语句即可。
设置模式之单例模式(附上一个Objective-C编写的播放音乐的单例类)的更多相关文章
- 设计模式(java) 单例模式 单例类
·单例类 单实例类,就是这个类只能创建一个对象,保证了对象实例的唯一性. 1.单例模式( Singleton Pattern) 是一个比较简单的模式, 其定义如下:Ensure a class has ...
- [转]单例模式——C++实现自动释放单例类的实例
[转]单例模式——C++实现自动释放单例类的实例 http://www.cnblogs.com/wxxweb/archive/2011/04/15/2017088.html http://blog.s ...
- [Android面试题-7] 写出一个Java的Singleton类(即单例类)
1.首先明确单例的概念和特点: a>单例类只能有一个实例 b>单例类必须自己创建一个自己的唯一实例 c>单例类必须为其他所有对象提供这个实例 2.单例具有几种模式,最简单的两种分别是 ...
- Singleton单例类模式
body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...
- 自己动手写一个简易对象关系映射,ORM(单例版和数据库池版)
准备知识 DBUtils模块 <<-----重点 DBUtils是Python的一个用于实现数据库连接池的模块 此连接池有两种连接模式: DBUtils提供两种外部接口: Persist ...
- iOS 创建一个在退出登录时可以销毁的单例
一.单例简介 单例模式是在软件开发中经常用的一种模式.单例模式通俗的理解是,在整个软件生命周期内,一个类只能有一个实例对象存在. 二.遇到的问题 在平时开发使用单例的过程中,有时候会有这样的需求,在用 ...
- 牛客网Java刷题知识点之什么是单例模式?解决了什么问题?饿汉式单例(开发时常用)、懒汉式单例(面试时常用)、单例设计模式的内存图解
不多说,直接上干货! 什么是单例设计模式? 解决的问题:可以保证一个类在内存中的对象唯一性,必须对于多个程序使用同一个配置信息对象时,就需要保证该对象的唯一性. 如何保证? 1.不允许其他程序用new ...
- struts action不在是一个单例类
在servlet中,servlet类是一个单例,在servlet中的成员变量,将会被所有请求共享,同时也有可能存在线程安全问题,如有一个成员变量num,每次方法后市的num自增 package act ...
- 用 Java 写一个单例类?
饿汉式单例 public class Singleton { private Singleton(){} private static Singleton instance = new Singlet ...
随机推荐
- Apache Rewrite规则详解[转]
1.Rewrite规则简介:Rewirte主要的功能就是实现URL的跳转,它的正则表达式是基于Perl语言.可基于服务器级的(httpd.conf)和目录级的(.htaccess)两种方式.如果要想用 ...
- 使用tmpfs的好处
EBS上超高频率的IO写入 这几天注意到我的EC2机器上有非常大量的IO,导致AWS的总体开销增加了很多.比如9月份的帐单中: I/O requests 120,076,984 IOs ...
- 魅族MX四核手机转让,二手淘宝上+hi-pda论坛结合使用成功已出
2013-3-14 内容存档在evernote,笔记名"魅族MX四核手机转让,二手淘宝上+hi-pda论坛结合使用成功已出"
- 搞不懂为什么开发人员爱iOS恨Android?
导读:很多网站发表文章大同小异.唯有这个不同点,给大家分享. Android和iOS的较量一直都是人们津津乐道的话题.两个平台各有各的优势所在,同时也都力图能在各个方面赶超对手.对于用户来说,青菜萝卜 ...
- 从前端的UI开始
MVC分离的比较好,开发顺序没有特别要求,先开发哪一部分都可以,这次我们主要讲解前端UI的部分. ASP.NET MVC抛弃了WebForm的一些特有的习惯,例如服务器端控件,ViewState这些东 ...
- SQL临时表
临时表就是那些名称以井号 (#) 开头的表.如果当用户断开连接时没有除去临时表,SQL Server 将自动除去临时表.临时表不存储在当前数据库内,而是存储在系统数据库 tempdb 内. 临时表有 ...
- 为什么 c = tf.matmul(a, b) 不立即执行矩阵乘法?
在 TensorFlow Python API 中,a.b 和 c 是 tf.Tensor 对象.Tensor 对象是指令结果的符号句柄,但它实际上并不存放指令的输出值.相反,TensorFlow ...
- cucumber java从入门到精通(2)用代码定义步骤
cucumber java从入门到精通(2)用代码定义步骤 上一节里我们定义了feature文件,feature文件就是自然语言描述的用例文件,它有一定的章法,具体的潜规则是: 使用Feature关键 ...
- Mac OS X 下多个JDK版本的切换小技巧
如果在系统中安装了多个Java JDK 版本,可能会根据项目或编译环境的不同来回切换,下面有两种非常方便的方法供大家选择. 我的机器上有 Java 7 和 Java 8,大家根据自己的 JDK 版本自 ...
- PHP位运算用途
在实际应用中可以做用户权限的应用 我这里说到的权限管理办法是一个普遍采用的方法,主要是使用到”位运行符”操作,& 位与运算符.| 位或运行符.参与运算的如果是10进制数,则会被转换至2进制数参 ...