单例就是这么简单

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wojiaoweide/article/details/52301565

单例目的就是为了在程序的整个生命周期内,只会创建一个类的实例对象,而且只要程序不被杀死,该实例对象就不会被释放。

应用场景
比如在APP开发中我们可能在任何地方都要使用用户的信息,那么可以在登录的时候就把用户信息存放在一个文件里面,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
有的情况下,某个类可能只能有一个实例。比如说你写了一个类用来播放音乐,那么不管任何时候只能有一个该类的实例来播放声音。再比如,一台计算机上可以连好几个打印机,但是这个计算机上的打印程序只能有一个,这里就可以通过单例模式来避免两个打印任务同时输出到打印机中,即在整个的打印过程中我只有一个打印程序的实例。

创建单例:
Demo01
1.ViewController.h

        Singleton *s = [Singleton sharedSingleton];
        NSLog(@"s:%p", s);

        Singleton *s1 = [Singleton sharedSingleton];
        NSLog(@"s1:%p", s1);

2.Singleton.h
=============

+(id)sharedSingleton;

Singleton.m

static Singleton *single = nil; //定义一个静态全局指针变量,static将全局指针变量的作用域限制在本文件中(static 限制全局变量的作用域)

+(id)sharedSingleton
{
    if (single == nil)
    {
        single = [[Singleton alloc]init];
    }
    return single
}

结果

//由此可见,打印出来的地址相同
2016-08-24 16:13:18.769 单例_1[5090:206307] s:0x1001002f0
2016-08-24 16:13:18.770 单例_1[5090:206307] s1:0x1001002f0
Program ended with exit code: 0

Demo02
1.ViewController.h

void James()
{
    Singleton *j = [Singleton sharedChildWithName:@"lbj" andAge:15];
    [j print];
}

- (void)ViewDidLoad {
    [super viewDidLoad];
    Singleton *k = [Singleton shareChildWithName:@"kobe" andAge:20];
}

2.Singleton.h

@property NSString *name;
@property int age;
-(id)initWithName:(NSString*)name andAge:(int)age;
+(id)sharedChildWithName:(NSString*)name andAge:(int)age;
-(void)print;

Singleton.m

static Singleton *single = nil;
//自定义初始化方法
-(id)initWithName:(NSString*)name andAge:(int)age 
{
//要跟父类方法一致
    if(self=[super init])
    {
        self.name = name;
        self.age = age;
    }
    return self;
}

+(id)sharedChildWithName:(NSString*)name andAge:(int)age{
    if(single == nil)
    {
        single = [self alloc]initWithName:name andAge:age];
    }
    return single;
}

-(void)print
{
    NSLog(@"name:%@, age:%d", self.name, self.age);
}

结果

//可见后面lbj即使改变了之前的赋值,打印出来的还是kobe,这说明了单例的作用以及效果,就是该类的对象只可以被创建一次。
2016-08-24 16:30:42.700 单例_02[5233:218731] name:kobe, age:20

另外为了线程安全,一般还会用GCD方式或者互斥锁方式创建单例
应用场景
假设此时有两条线程:线程1和线程2,都在调用shareInstance方法来创建单例,那么线程1运行到if (_instance == nil)出发现_instance = nil,那么就会初始化一个_instance,假设此时线程2也运行到if的判断处了,此时线程1还没有创建完成实例_instance,所以此时_instance = nil还是成立的,那么线程2又会创建一个_instace。

此时就创建了两个实例对象,导致问题。

1、使用dispatch_once

dispatch_once保证程序在运行过程中只会被运行一次,那么假设此时线程1先执行shareInstance方法,创建了一个实例对象,线程2就不会再去执行dispatch_once的代码了。从而保证了只会创建一个实例对象。

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return _instance;
}

2、使用互斥锁

假设此时线程1在执行shareInstance方法,那么synchronize大括号内创建单例的代码,如下所示:

if (_instance == nil) {
_instance = [[self alloc] init];
}
就会被当做一个任务被加上了一把锁。此时假设线程2也想执行shareInstance方法创建单例,但是看到了线程1加的互斥锁,就会进入睡眠模式。等到线程1执行完毕,才会被唤醒,然后去执行上面所示的创建单例的代码,但是此时_instance !=nil,所以不会再创建新的实例对象了。从而保证只会创建一个实例对象。

但是互斥锁会影响性能,所以最好还是使用GCD方式创建单例。

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized(self) {
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
+ (instancetype)sharedInstance
{
@synchronized(self) {
if (_instance == nil) {
_instance = [[self alloc] init];
}
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}

上面两种方式都可以创建单例,而且保证了用户不管是通过shareInstance方法,还是alloc、copy方法得到的实例都是一样的

猜你喜欢

转载自blog.csdn.net/wojiaoweide/article/details/52301565
今日推荐