在查阅Cocoa Touch开发文档时,会发现框架中随处可见的大量单例类,比如说,UIApplication、NSFileManager 等。

  1. 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编写的播放音乐的单例类)的更多相关文章

  1. 设计模式(java) 单例模式 单例类

    ·单例类 单实例类,就是这个类只能创建一个对象,保证了对象实例的唯一性. 1.单例模式( Singleton Pattern) 是一个比较简单的模式, 其定义如下:Ensure a class has ...

  2. [转]单例模式——C++实现自动释放单例类的实例

    [转]单例模式——C++实现自动释放单例类的实例 http://www.cnblogs.com/wxxweb/archive/2011/04/15/2017088.html http://blog.s ...

  3. [Android面试题-7] 写出一个Java的Singleton类(即单例类)

    1.首先明确单例的概念和特点: a>单例类只能有一个实例 b>单例类必须自己创建一个自己的唯一实例 c>单例类必须为其他所有对象提供这个实例 2.单例具有几种模式,最简单的两种分别是 ...

  4. Singleton单例类模式

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  5. 自己动手写一个简易对象关系映射,ORM(单例版和数据库池版)

    准备知识 DBUtils模块  <<-----重点 DBUtils是Python的一个用于实现数据库连接池的模块 此连接池有两种连接模式: DBUtils提供两种外部接口: Persist ...

  6. iOS 创建一个在退出登录时可以销毁的单例

    一.单例简介 单例模式是在软件开发中经常用的一种模式.单例模式通俗的理解是,在整个软件生命周期内,一个类只能有一个实例对象存在. 二.遇到的问题 在平时开发使用单例的过程中,有时候会有这样的需求,在用 ...

  7. 牛客网Java刷题知识点之什么是单例模式?解决了什么问题?饿汉式单例(开发时常用)、懒汉式单例(面试时常用)、单例设计模式的内存图解

    不多说,直接上干货! 什么是单例设计模式? 解决的问题:可以保证一个类在内存中的对象唯一性,必须对于多个程序使用同一个配置信息对象时,就需要保证该对象的唯一性. 如何保证? 1.不允许其他程序用new ...

  8. struts action不在是一个单例类

    在servlet中,servlet类是一个单例,在servlet中的成员变量,将会被所有请求共享,同时也有可能存在线程安全问题,如有一个成员变量num,每次方法后市的num自增 package act ...

  9. 用 Java 写一个单例类?

    饿汉式单例 public class Singleton { private Singleton(){} private static Singleton instance = new Singlet ...

随机推荐

  1. 入门程序,hello world

    RabbitMQ是消息代理.从本质上说,它接受来自生产者的信息,并将它们传递给消费者.在两者之间,它可以根据你给它的路由,缓冲规则进行传递消息. 一.专业术语 1. 生产者: 在现实生活中就好比制造商 ...

  2. 【MongoDB】MongoDB的安装教程

    1,MongoDB简介 MongoDB也是一种数据库,只不过它既不是关系型数据库,也不是非关系型数据库(NoSQL),而是一种介于关系型数据库和NoSQL之间的一种数据库.如果说非关系型数据库是轻量级 ...

  3. 【colaboratory】在colab中安装mxnet

    在学习<动手学深度学习>内容是,该内容用的是mxnet框架,在电脑本地安装过程中又容易出现错误,怎么也安装不上,所有的条件都尝试了. 汗颜,指的另谋他法. 只有在谷歌的学习平台上安装使用h ...

  4. Fuel4d 2.3 公布

    [版本号编号]:Fuel4D 2.3. [公布日期]:2014年10月20日. [编译环境]:UNICODE.VS2010.x86. [开发环境]:ANSI/UTF-8/UNICODE.VS2005/ ...

  5. 全面拥抱移动测试,Mobile JSON Wire Protocol Specification文档翻译

    Selenium3已经宣布不支持移动化测试.对于老牌测试工具selenium来说这是以退为进,因为移动自动化测试工具的标准还在selenium团队手上. 本文轻度翻译了这个标准,看得懂的人不用翻译也能 ...

  6. apache Tomcat配置SSL(https)步骤

    Tomcat配置https 1      生成Server端安全证书 要实现通信加密,首先要在本地准备一份符合X.509标准的Server端安全证书.如果有条件的话,可以向权威CA申请一份经过认证的安 ...

  7. 复习:JSP基本的语法(JSP凝视 + JSP指令 + JSP脚本元素 + JSP动作元素)

    JSP原理: 1.    对于每个请求.jsp容器都会创建一个新的线程来处理它: 2.    Servlet容器载入jsp后转换成的servlet(.class文件)是常驻内存的,所以对应速度一般比較 ...

  8. mac安装GnuGP

    1.首先查看gnugp最新的稳定版: localhost:~ jack$ brew search gnupg ==> Formulae gnupg gnupg-pkcs11-scd gnupg@ ...

  9. jquery 取子节点及当前节点属性值

    分享下jquery取子节点及当前节点属性值的方法. <li class="menulink"><a href="#" rel="ex ...

  10. Unity中yield return null和yield return WaitForEndOfFrame的区别

    2017/07/04修改 - 对WaitForEndOfFrame的LateUpdate时序进行说明. 测试结论: 1.如果只是等待下一帧执行,用yield return null即可.调用顺序在Up ...