哥的单例是伪的还是真的有图为证(Singleton)

原文:哥的单例是伪的还是真的有图为证(Singleton)

前言


前段时间和同事交流单例模式,感觉自己对单例了解还有很多不足。上网查了下发现,有些帖子太坑,拿着伪单例大书特书,看着眼疼。
单例不是简单的编程技巧,它是编程模式,能上升到模式级别的东西,不是说几行简单代码能搞定的,每次听到“单例模式是设计模式中最简单的形式”这句话时,都有种想拿人撞墙的感觉。

什么是单例模式


提到单例,如果在大学里上Java课的时候,你忘了睡觉,那么你肯定知道单例有很多种写法,比如:饿汉式、懒汉式等。单例类的写法也是很多公司,笔试必考题之一。在iOS系统为我们提供了许多单例类,如,UIApplication、NSUserDefaults等。

单例模式是一个Class在系统中只允许生成一个Instance Object,以共享内存的方式供系统中其他实例对象进行访问。在iOS开发中,单例模式是非常有用的一种设计模式。

提到单例,很多人都会举一个例子,
具体场景:一台机器多个可打印的应用Pro1、 Pro2、Pro3……,一台打印机Print;
在多个应用连接打印机的时候,Pro1、 Pro2、Pro3连接打印机Print的时候,生成的Print实例是同一个实例。

单例的作用


  1. 单例类保证了应用程序的生命周期中有且仅有一个该类的实例对象,而且易于外界访问。
  2. 共享内存,存储用户数据等
  3. 无交互类传值时,充当中间人

写法分类:


  1. 懒汉式 : 首次用到单例对象的时,创建实例对象;
  2. 饿汉式 : 进入程序就创建实例对象

提到这两种方式,首先说个知识点:NSObject的load和initialize方法,点开Apple官方关于NSObject的API会发现,在NSObject.h文件中有两个类方法load、initialize,

@interface NSObject <NSObject> 
{
    Class isa  OBJC_ISA_AVAILABILITY;
}
+ (void)load;
+ (void)initialize;

The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.
The order of initialization is as follows:
All initializers in any framework you link to.
All +load methods in your image.
All C++ static initializers and C/C++ attribute(constructor) functions in your image.
All initializers in frameworks that link to you.

The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses. The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize].

Apple的官方文档清楚地说明了initialize和load的区别:
load:只要类所在文件被引用就会被调用,系统运行,load就会被执行;
initialize:在类或者其子类的方法被调用前被调用,它属于懒汉的,自己的方法没被用到,它是不会执行的。

相同点:两种方法都只会被调用一次。

为了防止不停的if –>( _instance == nil) else ….这种令人作呕的判断,下面实现使用线程方式(记得实现NSCopying, NSMutableCopying协议方法):

######先验证下,哥的单例是伪的,还是真的,有图为证:

有朋友问到,使用alloc创建实例为什么会地址相同,主要是因为重写了allocWithZone,返回同一个实例。后面mutableCopyWithZone和copyWithZone方法的重写,是即便是使用copy修饰,也同样返回同一个实例。

1
@property (nonatomic, copy) EVNHelper *helper;

懒汉式单例

.h文件:

//
//  EVNHelper.h
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright © 2016年 仁伯安. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface EVNHelper : NSObject<NSCopying, NSMutableCopying>

+ (instancetype)shareInstance;

@end

.m文件:

//
//  EVNHelper.m
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright © 2016年 仁伯安. All rights reserved.
//

#import "EVNHelper.h"

static id _instance;

@implementation EVNHelper
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}
+ (instancetype)shareInstance
{
    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;
}
@end

饿汉式单例

.h文件:

//
//  EVNHelper.h
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright © 2016年 仁伯安. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface EVNHelper : NSObject<NSCopying, NSMutableCopying>

+ (instancetype)shareInstance;

@end

.m文件:

//
//  EVNHelper.m
//  MMBao_master
//
//  Created by developer on 16/6/11.
//  Copyright © 2016年 仁伯安. All rights reserved.
//

#import "EVNHelper.h"

static id _instance;

@implementation EVNHelper

 /**
 *  只要系统中引用了该类,程序运行,就会主动调用load(不用手动调用,而且只会加载1次)
 */
+ (void)load
{
    _instance = [[EVNHelper alloc] init]; //[[self alloc] init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}
+ (instancetype)shareInstance
{
    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;
}
@end

上面的写法是在ARC情况下,MRC还需在.m文件中添加如下方法:(鄙人不太懂MRC,只为扩充下,有问题,请回复,指教)

(1) 重写release方法为空
(2) 重写retain方法返回self
(3) 重写retainCount返回 1
(4) 重写autorelease返回self

- (oneway void)release
{ 
    NSLog(@"这里空语句就行");
}
- (id)retain 
{
    return self;
}
- (NSUInteger)retainCount
{
     return 1;
}
- (id)autorelease 
{
   return self;
}