美文网首页疯狂iOSiOS Developer编程知识点
单例的必要认识(深刻理解)

单例的必要认识(深刻理解)

作者: Dwyane_Coding | 来源:发表于2017-04-12 00:53 被阅读514次
图片来源网络

最近我面试人家,问他对单例的认识,他对单例的基本认识、创建方式回答全对,只有一点吞吞吐吐。

单例的认识

单例模式:单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例
系统单例:
1、UIApplication(应用程序实例)
2、NSNotificationCenter(消息中心):
3、NSFileManager(文件管理):
4、NSUserDefaults(应用程序设置):
5、NSURLCache(请求缓存):
6、NSHTTPCookieStorage(应用程序cookies池):

1.单例模式的要点:

显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

2.单例模式的优点:

1.安全性和唯一性:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例。单例类也可以防止他人复制(copy),保留(retain)或释放(release)实例。如果您发现需要,您可以创建自己的单例。例如,如果您有一个类为应用程序中的其他对象提供声音,则可以将其设为单例。
  2.灵活性:因为类控制了实例化过程,所以类可以更加灵活修改实例化过程

创建步骤

1、为你的单例类声明一个静态的实例(声明静态全局变量),并且初始化它的值为nil; eg:
static TestSingleton *testSingleton = nil;
这样,在获取实例的方法中,只有在静态实例为nil的时候,产生一个你的类的实例,这个实例通常被称为共享的实例;
2、重写allocWithZone方法,用于确定:不能够使用其他的方法创建我们类的实例,限制用户只能通过获取实例的方法得到这个类的实例。所以我们在allocWithZone方法中直接返回共享的类实例;
3、写+(instancetype)shareSingleton的函数体

创建方法

一、传统方法

+(instancetype)shareSingleton{ 
static Singleton *singleton = nil; 
if (singleton == nil){ 
singleton = [[self alloc] init]; 
} 
return singleton; 
}

二、推荐方法(GCD)

+(instancetype)shareSingleton{ 
static Singleton *singleton = nil; 
//给单例加一个线程锁 
static dispatch_once_t onceToken; 
dispatch_once(&onceToken, ^{ 
singleton = [[Singleton alloc] init]; 
}); 
return singleton;```

三、线程加锁方法(不推荐使用)

+(instancetype)shareSingleton{
static Singleton *singleton = nil;
@synchronized(self) {
singleton = [[Singleton alloc] init];
}
return singleton;
}```

注:“线程加锁方法”这样性能不是很好,因为每次调用+ (instancetype)sharedSingleton函数都会付出取锁的代价

吞吞吐吐:

但我问他,这样写的话是保证了线程安全,但通过自带的<code>alloc</code>或者<code>new</code>来进行实例化,还是不能保证该对象只被创建一次,如何避免呢?他就回答不上了,其实很简单:

  • .h头文件:
@interface TestSingleton : NSObject
@property (nonatomic, copy)NSString *testStr;
+ (TestSingleton *)shareinstance;

@end
  • .m文件:
+ (TestSingleton *)shareinstance {
    static TestSingleton  *testSingleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
//testSingleton = [self singletonAlloc]; //后面使用该创建方法  
testSingleton = [self new];//[[self alloc]init];
    });
    return testSingleton;
}

//我们需要重载<code>alloc、new</code>方法

+ (instancetype)singletonAlloc
{
    return [super alloc];
}

+ (instancetype)alloc
{
//加断言,使用alloc会蹦,并且reason提示
    NSAssert(NO, @"不要用alloc,要使用singletonAlloc方法创建");
    return nil;
}
+ (instancetype)new
{
    return [self alloc];
}

+ (TestSingleton *)shareinstance {
    static TestSingleton  *testSingleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    testSingleton = [[self singletonAlloc]init]; 
    });
    return testSingleton;
}

重载了alloc、allocWithZone和new方法,在alloc方法中加上断言来提醒,让用户不能使用alloc创建实例。

扩展一:(面试经常问到)

普通类跟单例的区别

普通类允许调用者根据需要创建尽可能多的类的实例,而对于单例类,每个进程只能有一个类的实例,保证了单例数据的唯一性,安全性
调用下面方法:

-(void)testSigleton
{
    /**
     *  singleton和singleton2是同一个对象;
     */
    TestSingleton *singleton = [TestSingleton shareinstance];
    TestSingleton *singleton2 = [TestSingleton shareinstance];
    
    if (singleton == singleton2) {
        NSLog(@"singleton == singleton2");
    }
    
    NSLog(@"singleton地址:%@",singleton);
    NSLog(@"singleton2地址:%@",singleton2);
}

打印如下

 SingletonTest[1598:54880] singleton == singleton2
 SingletonTest[1598:54880] singleton地址:<TestSingleton: 0x60000001e930>
 SingletonTest[1598:54880] singleton2地址:<TestSingleton: 0x60000001e930>

可以看出地址一样,证明单例只会创建一个静态变量,全局唯一

扩展二:(宏定义单例)

作用:有时在项目中需要创建好多个单例,把单例的代码定义为宏,则可以省去重复代码,节省时间。

/**
 *  在.h文件中定义的宏,arc
 *
 *  DWSingletonH(classname, accessorMethodName) 这个是宏
 * 在外边我们使用 “DWSingletonH(classname,   accessorMethodName)” 那么在.h文件中,定义了一个方
   法"+ (instancetype)accessorMethodName;"
 *
 */
#define DWSingletonH(classname, accessorMethodName) + (instancetype)accessorMethodName;

/**
 *  在.m文件中处理好的宏 arc
 *
 *  DWSingletonM(classname, accessorMethodName) 这个是宏,因为是多行的东西,所以每行后面都有一个"\",最后一行除外
 */
#define DWSingletonM(classname, accessorMethodName) \
static classname *instance_ = nil;\
+ (instancetype)accessorMethodName{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance_ = [[self alloc] init];\
});\
return instance_;\
}\
+ (instancetype)allocWithZone:(struct _NSZone *)zone{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
instance_ = [super allocWithZone:zone];\
});\
return instance_;\
}\
- (id)copyWithZone:(NSZone *)zone{\
return instance_;\
}

把上面代码用一个<code>Header File</code>文件里面,然后就可以在其他类快速创建单例了

  • .h头文件:
@interface Singleton2 : NSObject
//@property double t;
DWSingletonH(Singleton2, shareInstance)
@end
  • .m文件:
@implementation Singleton2
DWSingletonM(Singleton2, shareInstance)
@end

相关文章

  • 单例的必要认识(深刻理解)

    最近我面试人家,问他对单例的认识,他对单例的基本认识、创建方式回答全对,只有一点吞吞吐吐。 单例的认识 单例模式:...

  • 单例模式

    单例模式是一种简单的的设计模式。虽然简单,但是应用却是十分的广泛。所有有必要针对单例模式的知识点进行梳理。 单例模...

  • 羊皮书APP(Android版)开发系列(二十二)10分钟秒懂单

    单例模式在实际开发过程中经常会用到,我们有必要充分的理解单例模式。单例模式有多种写法,分为懒汉式、饿汉式、双重锁等...

  • 2018-06-13

    什么是单例模式? 在文章开始之前我们还是有必要介绍一下什么是单例模式。单例模式是为确保一个类只有一个实例,并为整个...

  • iOS单例(GCD)

    今天用到音效播放,顺便把单例做下记录很多单例都会文章都会实现copyWithZone:个人感觉不是很有必要(自定义...

  • 初识设计模式之单例模式

    此文为笔者个人的学习笔记,在学习部分博客、书籍、资料后所总结,仅供参考。 对单例模式的认识单例模式是什么单例模式的...

  • (一)Android中的单例模式

    作为一个Android开发的老司机,或者刚入行的司机,我觉得你还是有必要学习下Android的单例模式,毕竟单例模...

  • 2020-11-02-Spring单例 vs. 单例模式

    Spring 单例不是 Java 单例。本文讨论 Spring 的单例与单例模式的区别。 前言 单例是 Sprin...

  • IOS单例模式的底层原理

    单例介绍 本文源码下载地址 1.什么是单例 说到单例首先要提到单例模式,因为单例模式是单例存在的目的 单例模式是一...

  • Android设计模式总结

    单例模式:饿汉单例模式://饿汉单例模式 懒汉单例模式: Double CheckLock(DCL)实现单例 Bu...

网友评论

  • f005c5dd4c56:哥们,宏那个有点意思
  • 啊哈呵:单例没有必要非要弄这么严格的,把其他方法的路都断了,有时候需要单例,有时候不需要单例,太严格了路都封死了。系统的NSFileManager就是没有严格,可以单例,也可以new实例。 如果说怕别人乱用(或者不小心)乱用,那只能怪他了,大家用单例都是约定好名字,什么defaultManager 或者sharedInstance 反正大家一看就知道单例。对于那些不按常理出牌的使用者(比如项目规范,某个需求要用单例,他不去调用defaultManager方法,非要调用new方法),让他自己找BUG吧
    Dwyane_Coding:哈哈哈,是的,我之前跟同事说,你们别乱用方法创实例啊,他们也笑着说:我们用Share开头的就好
  • 剑未佩妥:你说的单例是一个类的方法是错误的,单例还是一个实例,只不过存放在静态区
    剑未佩妥:嗯,是这样理解的,之前你的那个说法容易让人误解。
    Dwyane_Coding:所以我觉得单例是一个实例的观点,我不敢苟同,apple 文档也没有直说单例是一个实例。不过很感谢你的质疑,感谢
    Dwyane_Coding:@竹和青梅 单例,怎么说呢,一般是理解成单例类(singleton class)或者是一个模式,使得该类只有一个全局实例
  • 草原野马:看来是你面试是碰上刚出来的了
    Dwyane_Coding:@草原野马 他说他干了一年多啦
  • 季末微夏://第1步: 存储唯一实例
    static Tools *_tools = nil;
    //第2步: 分配内存空间时都会调用这个方法. 保证分配内存alloc时都相同.
    +(id)allocWithZone:(struct _NSZone *)zone{
    //调用dispatch_once保证在多线程中也只被实例化一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _tools = [super allocWithZone:zone];
    });
    return _tools;
    }
    //第3步: 保证init初始化时都相同
    +(instancetype)sharedTools{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _tools = [[Tools alloc] init];
    });
    return _tools;
    }
    //第4步: 保证copy时都相同
    -(id)copyWithZone:(NSZone *)zone{
    return _tools;
    }
    这样写,无论是alloc还是new还是copy还是使用sharedTools都是同一个地址,就不用断言来阻止使用alloc
  • hanryChen:单例是创建一个实例存在静态区,导致这个单例在这次程序运行期间不会释放,除此之外它和普通创建的实例没有本质的区别,alloc创建的实例怎么会影响到它的值呢,它们就是两个不同的实例

本文标题:单例的必要认识(深刻理解)

本文链接:https://www.haomeiwen.com/subject/bdjpattx.html