• 设置模式之单例模式(附上一个Objective-C编写的播放音乐的单例类)


    在查阅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里面,只需要对单例类加上内存管理语句即可。

     

     

  • 相关阅读:
    K8S实战(十七)| 通过 StorageClass 实现动态卷供应
    K8S实战(十六)| 持久化存储卷
    K8S实战(十五)| 存储卷概念
    K8S实战(十四)| ConfigMap 对象
    K8S实战(十三)| Secret 对象
    K8S实战(十二)| 为 Ingress 以及后端 Nginx 增加证书
    批量删除git 本地分支、远程分支、tag
    React 页面间传值的个人总结
    搭建一个属于自己的webpack config(-)
    HTTP 2 新特性
  • 原文地址:https://www.cnblogs.com/ziyi--caolu/p/4871173.html
Copyright © 2020-2023  润新知