GCD在单例设计模式中的应用

  在iOS开发中,单例设计模式的应用十分广泛,最经典的应用场景就是在登陆界面的处理上。以新闻App为例,如果你要查看我的收藏,或者我的评论时,它会弹出一个登陆界面,或者你直接点击登陆时,它也会弹出一个登陆界面。通常情况下,一个应用的资源是十分有限的,为了保证一个类在整个程序运行过程中只有一个实例,一般会对登陆界面做单例化处理。在前面的笔记中,我们学习了多线程的相关知识,在单例设计过程中刚好可以用上。下面,我们就通过一个实例来演示一下如何设计单例模式。

一、ARC环境下的单例设计

  
  1、单例化ESTool对象

  新建一个工程,然后按住command + N新建一个继承自NSObject的ESTool类,在ViewController中导入它的头文件,最后在- viewDidLoad方法中创建几个ESTool对象,打印一下它们的地址:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建实例
    ESTool *tool1 = [[ESTool alloc] init];
    ESTool *tool2 = [[ESTool alloc] init];
    ESTool *tool3 = [ESTool new];

    NSLog(@"tool1:%@", tool1);
    NSLog(@"tool2:%@", tool2);
    NSLog(@"tool3:%@", tool3);
}

  运行程序,注意看一下它们的内存地址有什么不同:

1240
多个不同的实例.png

  从内存地址上看,它们是三个不同的实例。现在,我们需要将它们单例化,也就是不管你通过什么方式、实例化多少ESTool对象,它们最终都只有一份内存。

  因为内存空间的分配是和+ alloc方法相关的,而+ alloc方法底层又会调用+ allocWithZone:方法,因此,我们只需要在ESTool.m文件中重写这个方法,对内存空间的分配做一些相应的处理就可以了。具体的做法是,先提供一个全局静态变量,然后在重写+ allocWithZone:方法时,对其进行懒加载:

// 提供一个全局变量
static ESTool *_instance;

// MARK:- 重写allocWithZone:方法,利用懒加载来实现单例设计
+ (instancetype)allocWithZone:(struct _NSZone *)zone {

    // 如果_instance为空
    if (!_instance) {

        // 初始化_instance为一个单例
        _instance = [super allocWithZone:zone];
    }

    // 返回这个_instance单例
    return _instance;
}

  运行程序,然后看一下控制台打印出来的ESTool对象的内存地址:

1240
ESTool对象的单例化.png

  从运行结果上看,我们俨然已经达到了最终的目的。不过,重写的过程还不够完善。我们在前面学习过多线程的知识,假若用户在外面开了多条子线程,并且在每个线程中都使用了[[ESTool alloc] init]来实例化一个对象,这样可能会引发线程安全问题。为了避免这种情况出现,在初始化_instance之前,先给它加一把互斥锁:

// 提供一个全局变量
static ESTool *_instance;

// MARK:- 重写allocWithZone:方法,利用懒加载来实现单例设计
+ (instancetype)allocWithZone:(struct _NSZone *)zone {

    // 加互斥锁保证线程安全
    @synchronized (self) {

        // 如果_instance为空
        if (!_instance) {

            // 初始化_instance为一个单例
            _instance = [super allocWithZone:zone];
        }

        // 返回这个_instance单例
        return _instance;
    }
}

  运行程序看一下结果:

1240
单例化ESTool对象.png

  现在,单例化ESTool对象的过程基本上算完善了。不过,在学习完GCD的基本知识以后,在单例化过程中,我们还有更好的选择:

// 提供一个全局变量
static ESTool *_instance;

// MARK:- 重写allocWithZone:方法,利用GCD的一次性代码来设计单例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {

    // GCD的一次性代码本身就是线程安全的,不需要再加互斥锁
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        // 初始化_instance变量
        _instance = [super allocWithZone:zone];

    });

    // 返回一个_instance单例
    return _instance;
}

  dispatch_once( )函数本身就是线程安全的,不需要再去加互斥锁,而且,我们也不用再去判断_instance是否有值,这样操作起来更为简单和方便。运行程序看一下实际效果:

1240
使用GCD的基本知识来单例化一个实例.png

  2、为外界提供一个类方法

  现在,我们已经完成了对ESTool对象单例化的处理,但是,作为一个单例设计模式而言,我们的工作远远还没有完成。根据之前的开发经验,为了方便外界快速获取一个单例对象,我们应该提供一个类方法。至于这个类方法怎么命名,可以参照苹果官方的做法,即通常以"default + 类名"(NSFileManager的+ defaultManager),或者"share + 类名"(UIApplication的+ shareApplication)来组成这个类方法的名称:

@interface ESTool : NSObject

// 提供一个类方法供外部获取单例对象
+ (instancetype)shareTool;
+ (instancetype)defaultTool;

@end

  实现+ shareTool方法或者+ defaultTool方法的代码非常简单,直接返回[[self alloc] init]就可以了:

// MARK:- 提供一个类方法,方便外部访问获取单例
+ (instancetype)shareTool {

    return [[self alloc] init];  // + alloc底层会调用+ allocWithZone:
}

+ (instancetype)defaultTool {

    return [[self alloc] init];
}
/**
 *  单例的类方法命名是有规范的,一般是以:share + 类名,或者default + 类名作为方法名
 */

  为了验证这个类方法有没有效,可以回到ViewController的- viewDidLoad方法中,使用类方法快速创建一个ESTool对象,然后打印它的内存地址看一下:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建实例
    ESTool *tool1 = [[ESTool alloc] init];
    ESTool *tool2 = [ESTool new];
    ESTool *tool3 = [ESTool shareTool];
    ESTool *tool4 = [ESTool defaultTool];

    NSLog(@"tool1:%@", tool1);
    NSLog(@"tool2:%@", tool2);
    NSLog(@"tool3:%@", tool3);
    NSLog(@"tool4:%@", tool4);
}

  运行程序,看一下这几个ESTool对象的内存地址是不是同一个:

1240
使用类对象快速获取一个ESTool单例.png

  从控制台打印出来的结果来看,我们这个单例设计向着完美又近了一步。但是,它还是不够严谨。因为,除了上面那几种创建ESTool对象的方式之外,我们还可以用- copy或者- mutableCopy。为此,必须进一步完善这些细节。

  3、完善设计细节

  为了保证外界用ESTool对象调用- copy方法,或者- mutableCopy方法获取到的对象同样是一个单例对象,必须重写- copyWithZone:和mutableCopyWithZone:方法:

// MARK:- 重写copyWithZone:和mutableCopyWithZone:
- (id)copyWithZone:(NSZone *)zone {

    return _instance;  // 因为copy和mutableCopy方法是通过对象来调的,这就意味着_instance这个变量已经存在了,直接将它返回就可以了
}

- (id)mutableCopyWithZone:(NSZone *)zone {

    return _instance;
}

  能用- copy方法,或者- mutableCopy方法再创建一个ESTool对象,说明项目中原本就已经存在一个实例对象了。而在我们这个项目中,这个对象已经就是一个单例了,因此,在上面的重写过程中,只需要返回_instance就可以了。在- viewDidLoad方法中通过- copy方法,或者- mutableCopy方法再创建一个ESTool对象,然后打印它们的地址看一下:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建实例
    ESTool *tool1 = [[ESTool alloc] init];
    ESTool *tool2 = [ESTool new];
    ESTool *tool3 = [ESTool shareTool];
    ESTool *tool4 = [ESTool defaultTool];

    NSLog(@"tool1:%@", tool1);
    NSLog(@"tool2:%@", tool2);
    NSLog(@"tool3:%@", tool3);
    NSLog(@"tool4:%@", tool4);

    // 通过ESTool对象调用
    ESTool *tool5 = [tool1 copy];
    ESTool *tool6 = [tool2 mutableCopy];

    NSLog(@"tool5:%@", tool5);
    NSLog(@"tool6:%@", tool6);
}

  运行程序,然后注意控制台打印信息:

GCD在单例设计模式中的应用_第1张图片
使用- copy方法或者- mutableCopy方法获取一个单例.png

  至此,我们的单例设计算是顺利完成了。不过,上面这些是在ARC环境中的单例设计,如果是在MRC环境中,那么单例设计又是怎样的呢?下面,我们就看一下在MRC环境中如何进行单例模式的设计。

二、MRC环境下的单例设计

  
  1、非ARC编译环境下的单例设计

  首先,要修改项目的编译环境为MRC。点击项目工程,然后选择Build Settings,在搜索框中输入automatic定位到"Objective-C Automatic Reference Counting",将它从Yes改为No就可以了:

GCD在单例设计模式中的应用_第2张图片
修改项目编译环境为MRC.png

  我们知道,在非ARC环境中,每alloc一下,就要手动release一次。但是,当我们手动释放以后,在运行程序,它就直接崩掉了:

GCD在单例设计模式中的应用_第3张图片
手动释放以后运行程序引发崩溃.png

  通常情况下,程序崩溃时,系统会给出反馈的,告诉你到底出了什么问题,但是我们这里并没有。原因是没有开启僵尸模式。依次点击Xcode菜单栏上的"Product——Scheme——Edit Scheme",然后在点击"Run——Diagnostics",勾选Memory Management下面的"Zombie Objects",如下图所示:

GCD在单例设计模式中的应用_第4张图片
开启僵尸模式.png

  勾选完僵尸模式以后,再次运行程序:

GCD在单例设计模式中的应用_第5张图片
引发崩溃的原因.png

  此时,我们看到,在控制台下面,系统已经给出错误原因了——"message sent to deallocated instance 0x600000011400",即"把消息发送给一个已经释放掉的对象了"。

  那么,现在如何解决这个bug呢?在回答这个问题之前,我们先来看回顾一下retain和release到底做了什么工作。在内存管理过程中,retain操作会对引用计数器执行+1操作,而release则会对引用计数器执行-1操作。当引用计数器最后变为0时,就意味着需要将某个对象给释放掉。但是,在单例设计模式中,一个对象在被创建以后,在整个程序运行过程中,它是不会被释放的。因此,就没必要再对其进行引用计数器管理了。现在问题来了,在非ARC的单例设计模式中,如何进行内存管理呢?

  解决上面问题的方法非常简单,就是重写- retain和- release方法。重写的目的就是为了让这两个方法失效,因此,重写时什么都不用做就可以了。只不过,- retain方法有一个返回值,在这里我们直接返回_instance就可了:

// MARK:- 非ARC环境下的内存管理
- (instancetype)retain {

    // 直接返回_instance
    return _instance;
}

- (oneway void)release {

    // 什么都不用做
}

  但是,仅仅是重写上面这两个方法是不严谨的。因为,在MRC环境中,我们经常会用到- retainCount方法来打印引用计数器的数量,为此,还需要重写这个方法。通常情况下,直接返回一个最大数量的引用计数器就可以了:

// MARK:- 重写retainCount,返回最大值
- (NSUInteger)retainCount {

    return MAXFLOAT;
}

  运行程序,看一下控制台打印信息:

GCD在单例设计模式中的应用_第6张图片
解决MRC编译环境下程序报错的问题.png

  2、兼容ARC和MRC编译环境下的单例设计

  在iOS开发过程中,经常会碰到ARC和MRC混编的情况,又或者因为项目需求的变化,需要从ARC转换到MRC,或者从MRC转换到ARC。为了应对这种状况,需要对我们的代码进行兼容性改造。可以利用条件编译进行判断,当我们的编译环境为ARC时,就不需要重写- retain、- release和- retainCount方法了;否则,就重写这三个方法:

// MARK:- 兼容ARC和非ARC编译环境下的单例设计
#if __has_feature(objc_arc)  // has前面为两个下划线,其它均为一个下划线

    // 如果是ARC编译环境,则什么也不干

#else  // 如果不是ARC编译环境

// MARK:- 非ARC环境下的内存管理
- (instancetype)retain {

    // 直接返回_instance
    return _instance;
}

- (oneway void)release {

    // 什么都不用做
}

// MARK:- 重写retainCount,返回最大值
- (NSUInteger)retainCount {

    return MAXFLOAT;
}

#endif

  加入编译条件以后,可以切换项目的编译环境,具体看一下在ARC和MRC编译环境下项目的具体表现。

三、单例设计模式通用的宏

  
  1、单例设计过程中不能使用继承

  在一个项目中,有时候可能不止一个单例。假如有多个,是不是意味着每一个都需要完整的重写一遍呢?从上面的单例设计中,我们知道单例设计是有套路的,而且有很多代码在多个文件中都是可以复用的。那么,能不能想办法将通用的代码给抽出来呢?答案是肯定的。

  根据之前的经验,我们知道,继承可以达到代码复用的目的。但是,在单例设计过程中,代码的复用是不能使用继承的!下面,我们就演示一下为什么不能使用继承。

  新建一个继承自ESTool的ESShareTool,然后来到ViewController中包含它的头文件,在- viewDidLoad方法中创建一个ESShareTool对象,然后打印它:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建实例
    ESTool *tool1 = [[ESTool alloc] init];

    ESTool *tool2 = [ESTool new];
    ESTool *tool3 = [ESTool shareTool];
    ESTool *tool4 = [ESTool defaultTool];

    NSLog(@"tool1:%@", tool1);
    NSLog(@"tool2:%@", tool2);
    NSLog(@"tool3:%@", tool3);
    NSLog(@"tool4:%@", tool4);

    // 通过ESTool对象调用
    ESTool *tool5 = [tool1 copy];
    ESTool *tool6 = [tool2 mutableCopy];

    NSLog(@"tool5:%@", tool5);
    NSLog(@"tool6:%@", tool6);

    // 创建ESShareTool对象
    ESShareTool *shareTool = [[ESShareTool alloc] init];

    NSLog(@"shareTool:%@", shareTool);
}

  运行程序,注意一下shareTool对象的类型:

GCD在单例设计模式中的应用_第7张图片
使用继承以后不同类实例化以后的真实类型.png

  从打印的信息来看,我们明明创建的是ESShareTool对象,但是它的实际对象却是ESTool类型。再调换一下shareTool对象创建的位置:

- (void)viewDidLoad {
    [super viewDidLoad];

    ESShareTool *shareTool = [[ESShareTool alloc] init];

    // 创建实例
    ESTool *tool1 = [[ESTool alloc] init];
    [tool1 release];

    ESTool *tool2 = [ESTool new];
    ESTool *tool3 = [ESTool shareTool];
    ESTool *tool4 = [ESTool defaultTool];

    NSLog(@"tool1:%@", tool1);
    NSLog(@"tool2:%@", tool2);
    NSLog(@"tool3:%@", tool3);
    NSLog(@"tool4:%@", tool4);

    // 通过ESTool对象调用
    ESTool *tool5 = [tool1 copy];
    ESTool *tool6 = [tool2 mutableCopy];

    NSLog(@"tool5:%@", tool5);
    NSLog(@"tool6:%@", tool6);
    NSLog(@"shareTool:%@", shareTool);
}

  运行程序,注意看一下ESTool对象的真实类型:

GCD在单例设计模式中的应用_第8张图片
使用继承以后不同类实例化以后的真实类型.png

  这一次,ESTool对象的真实类型居然全部变成了ESShareTool!这就有点懵逼了!之所以会出现这种状况,还是跟单例类的特性有关。因为在整个程序运行过程中,不管你实例化过多少次,单例类的对象永远都只有一份内存(也就是只有一个实例对象)。这也就意味着,如果你在单例设计代码复用的过程中使用了继承关系,后面不管你用哪个类去创建多少个实例对象,它们的真实类型永远都是最开始实例化那一个的类型。很明显,这种设计肯定不符合要求。为此,必须采用其它设计。

  2、ARC环境下通用的宏

  其实,除了继承关系可以减少重复代码之外,宏也可以起到同样的效果。我们可以将单例设计模式中通用的代码抽成宏。新建一个继承自NSObject的ESSingleton头文件,然后在它里面定义两个宏:

// ESSingleton.h
#define ESSingleton_H + (instancetype)shareTool

// ESSingleton.m
#define ESSingleton_M static id _instance;\
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone {\
    \
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        \
        _instance = [super allocWithZone:zone];\
        \
    });\
    \
    return _instance;\
}\
\
+ (instancetype)shareTool {\
    \
    return [[self alloc] init];\
}\
\
+ (instancetype)defaultTool {\
    \
    return [[self alloc] init];\
}\
\
- (id)copyWithZone:(NSZone *)zone {\
    \
    return _instance;\
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone {\
    \
    return _instance;\
}

  关于上面的两个宏需要做一些说明。其中,宏ESSingleton_H表示单例中类方法的声明;宏ESSingleton_M表示单例中需要重写的方法和类方法的实现。ESSingleton_M这个宏稍微复杂一点,有两点需要特别补充:1、为了方便所有的单例都能识别,将原来static后面的ESTool修改为id;2、因为这个宏比较长,中间有很多回车和空格,我们用"\"(反斜杠)将它们连成了一个整体。来到ESShareTool的头文件中,包含ESSingleton的头文件,然后让ESShareTool继承自NSObject,实现下面的代码:

#import "ESTool.h"
#import "ESSingleton.h"

@interface ESShareTool : NSObject

// 包含ESSingleton_H宏
ESSingleton_H;

@end

  来到ESShareTool的源文件中,实现下面的代码:

#import "ESShareTool.h"

@implementation ESShareTool

ESSingleton_M;

@end

  修改ESShareTool的继承关系,让它继承自NSObject,然后重新运行程序,注意看一下控制台打印出来各种实例对象的真实类型:

GCD在单例设计模式中的应用_第9张图片
使用通用宏来减少单例设计中重复的代码.png

  现在,我们不仅减少了在单例设计过程中重复出现的代码,同时还能不影响不同单例类实例化以后的真实类型。但是,这还不够完善,主要是单例类的类名写死了,不利于扩展。为此,需要将它们重写为带参数的宏:

// ESSingleton.h
#define ESSingleton_H(className) + (instancetype)share##className

// ESSingleton.m
#define ESSingleton_M(className) static id _instance;\
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone {\
    \
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        \
        _instance = [super allocWithZone:zone];\
        \
    });\
    \
    return _instance;\
}\
\
+ (instancetype)share##className {\
    \
    return [[self alloc] init];\
}\
\
+ (instancetype)defaultTool {\
    \
    return [[self alloc] init];\
}\
\
- (id)copyWithZone:(NSZone *)zone {\
    \
    return _instance;\
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone {\
    \
    return _instance;\
}

  与之对应,ESShareTool的头文件和源文件中的代码也需要做相应的修改。并且,要将类名作为参数传递进去:

// 头文件
#import "ESTool.h"
#import "ESSingleton.h"

@interface ESShareTool : NSObject

// 包含ESSingleton_H宏
ESSingleton_H(Tool);

@end

// 源文件
#import "ESShareTool.h"

@implementation ESShareTool

ESSingleton_M(Tool);

@end

  回到ViewController中,在- viewDidLoad方法中调用ESShareTool的+ shareTool方法实例化一个对象。有两点需要说明:1、我们没有使用继承关系,所以ESShareTool不会继承ESTool的+ shareTool类方法;2、ESShareTool的类方法+ shareTool是通过带参数的宏来实现的

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建实例
    ESTool *tool1 = [[ESTool alloc] init];
    [tool1 release];

    ESTool *tool2 = [ESTool new];
    ESTool *tool3 = [ESTool shareTool];
    ESTool *tool4 = [ESTool defaultTool];

    NSLog(@"tool1:%@", tool1);
    NSLog(@"tool2:%@", tool2);
    NSLog(@"tool3:%@", tool3);
    NSLog(@"tool4:%@", tool4);

    // 通过ESTool对象调用
    ESTool *tool5 = [tool1 copy];
    ESTool *tool6 = [tool2 mutableCopy];

    NSLog(@"tool5:%@", tool5);
    NSLog(@"tool6:%@", tool6);

    // 创建ESShareTool对象
    ESShareTool *shareTool = [ESShareTool shareTool];
    [shareTool release];

    NSLog(@"shareTool:%@", shareTool);
}

  运行程序,看一下实际效果如何:

GCD在单例设计模式中的应用_第10张图片
利用宏来完成单例设计.png

  3、MRC环境下通用的宏

  定义MRC编译环境下的宏也比较简单。宏ESSingleton_H(className)的声明与上面相同。不同的是,在声明宏ESSingleton_M(className)的时候,需要利用条件编译对编译环境进行判断。如果是ARC编译环境,宏ESSingleton_M(className)的声明和上面一样;如果是MRC编译环境,宏ESSingleton_M(className)的声明在上面的基础上,需要再拼接- retain、- release和- retainCount这三个方法重写的代码:

// ESSingleton.h
#define ESSingleton_H(className) + (instancetype)share##className

// MARK:- 利用条件编译兼容ARC和MRC编译环境下的宏
#if __has_feature(objc_arc)

// ARC编译环境下ESSingleton.m中的宏
#define ESSingleton_M(className) static id _instance;\
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone {\
\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
\
_instance = [super allocWithZone:zone];\
\
});\
\
return _instance;\
}\
\
+ (instancetype)share##className {\
\
return [[self alloc] init];\
}\
\
+ (instancetype)defaultTool {\
\
return [[self alloc] init];\
}\
\
- (id)copyWithZone:(NSZone *)zone {\
\
return _instance;\
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone {\
\
return _instance;\
}

#else  // 如果不是ARC编译环境

// MRC编译环境下ESSingleton.m中的宏
#define ESSingleton_M(className) static id _instance;\
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone {\
\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
\
_instance = [super allocWithZone:zone];\
\
});\
\
return _instance;\
}\
\
+ (instancetype)share##className {\
\
return [[self alloc] init];\
}\
\
+ (instancetype)defaultTool {\
\
return [[self alloc] init];\
}\
\
- (id)copyWithZone:(NSZone *)zone {\
\
return _instance;\
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone {\
\
return _instance;\
}\
\
- (instancetype)retain {\
    \
    return _instance;\
}\
\
- (oneway void)release {\
    \
}\
\
- (NSUInteger)retainCount {\
    \
    return MAXFLOAT;\
}

#endif

  首先将我们的编译环境切换到MRC。为了检验我们这个宏是否可靠,再新建一个继承自NSObject的ESDownloadTool类,然后在- viewDidLoad方法中调用+ shareDownloadTool方法实例化两个个对象,看看它们是不是单例:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建实例
    ESTool *tool1 = [[ESTool alloc] init];
    [tool1 release];

    ESTool *tool2 = [ESTool new];
    ESTool *tool3 = [ESTool shareTool];
    ESTool *tool4 = [ESTool defaultTool];

    NSLog(@"tool1:%@", tool1);
    NSLog(@"tool2:%@", tool2);
    NSLog(@"tool3:%@", tool3);
    NSLog(@"tool4:%@", tool4);

    // 通过ESTool对象调用
    ESTool *tool5 = [tool1 copy];
    ESTool *tool6 = [tool2 mutableCopy];

    NSLog(@"tool5:%@", tool5);
    NSLog(@"tool6:%@", tool6);

    // 创建ESShareTool对象
    ESShareTool *shareTool = [ESShareTool shareTool];
    [shareTool release];

    ESDownloadTool *downloadTool1 = [ESDownloadTool shareDownloadTool];
    ESDownloadTool *downloadTool2 = [ESDownloadTool shareDownloadTool];

    NSLog(@"shareTool:%@", shareTool);
    NSLog(@"downloadTool:%@", downloadTool1);
    NSLog(@"downloadTool:%@", downloadTool2);
}

  运行程序,注意控制台打印,重点看看它们的真实类型和内存地址:

GCD在单例设计模式中的应用_第11张图片
兼容ARC和MRC编译环境.png

  上面的运行结果证明我们的设计是有效的。有了ESSingleton.h这个宏,以后如果再想创建一个单例类,只需要新建一个继承自NSObject文件的类,包含ESSingleton的头文件,然后分别在这个新建单例类的头文件和源文件中调用对应的宏,然后再将类名传进去就可以了。

  以上就是单例设计模式的全部内容,后面会接着整理多线程的其它知识。详细代码参见ESSingletonPatternExercise。

你可能感兴趣的:(OC开发)