单例

ps:所谓单例,就是在程序中只有一个实例,它提供一个类方法供全局调用。在编译时初始化这个类,然后保存在内存中,在程序退出时,由系统自动释放这部分内存。系统为我们提供的单例有:UIApplication、NSNotificationCenter、NSFileManager、NSUserDefaults、NSURLCache、NSHTTPCookieStorage等。

创建一个单例时,需要注意的是:实例化一个类有多种方法,alloc、new、allocWithZone,你写的初始化单例的类方法 sharedInstance,但你不能保证你的同伴不会调用其他初始化方法去初始化这个类,因此你需要将其余的初始化方法全部干掉,保证不管用什么初始化方法创建的实例,都有一个共同的内存地址。

+(instancetype)sharedInstance
{
    static Person *p = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        p = [[Person alloc]init];
    });
    return p;
}

    Person *p1 = [[Person alloc]init];
    Person *p2 = [Person new];
    Person *p0 = [Person sharedInstance];
    Person *p = [Person sharedInstance];
    NSLog(@"\n%@==\n%@==\n%@==\n%@",p1,p2,p0,p);

log:
2018-12-04 17:02:32.057731+0800 OCTest[46730:8876108] 
<Person: 0x600003f36d10>==
<Person: 0x600003f36da0>==
<Person: 0x600003f36db0>==
<Person: 0x600003f36db0>

像上面这样显然是不可取的,不同初始化方法初始化的对象的内存不是同一个。

创建单例方法一:重写allocWithZone

OC初始化一个对象分为两步:[[Class alloc]init]
1,给对象分配内存空间(alloc)
2,初始化对象的相关信息 (init)
当调用alloc给对象分配内存时,系统会自动调用allocWithZone为其分配内存。因此重写allocWithZone方法就可以保证,实例化对象的时候,为对象分配的内存永远都是同一个。(ps: alloc与allocWithZone的区别http://justinyan.me/post/1306

#import "Person.h"
@interface Person()<NSCopying,NSMutableCopying>
@end
@implementation Person
+(instancetype)sharedInstance
{
    return [[self alloc]init];
}
-(instancetype)init
{
    if(self = [super init]){
    }
    return self;
}
//重写allocWithZone 保证创建对象分配的内存是同一个
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    static Person *p = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        p = [super allocWithZone:zone];
    });
    return p;
}

上面的代码就可以保证不管使用的什么初始化方法初始化对象,对象的内存地址只有一个。
既然都写到这里了,那连copy和mutableCopy也一起处理,虽然用到的时候不多,但不重写的情况下,调用copy会引起crash。还是那句话,你并不能保证你的队友不会做什么。

-(instancetype)copyWithZone:(NSZone *)zone
{
    return [Person sharedInstance];
}
-(instancetype)mutableCopyWithZone:(NSZone *)zone
{
    return [Person sharedInstance];
}

创建单例方法二:重写+(void)initialize和+ (instancetype)alloc

当第一次调用一个类时,当这个类是第一次被调用时,会先调用+(void)initialize方法,在调用初始化方法+ (instancetype)alloc;当不是第n>1次时,会直接调用+ (instancetype)alloc方法。因此可以在initialize方法中初始化单例,在alloc做出判断,若对象已经初始化,就不再次初始化,保证对象只初始化一次。

#import "Person.h"
@interface Person()<NSCopying,NSMutableCopying>
@end
@implementation Person

static Person *_p = nil;
//第一次调用该类时调用
+(void)initialize
{
    [self sharedInstance];
}
//初始化对象
+(instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _p = [[self alloc]init];
    });
    return _p;
}
//重写alloc 判断对象是否已经初始化。若已经初始化,就用原来的;若没有就c初始化一个。只有第一次调用该类的时候,才会创建。
+(instancetype)alloc
{
    if (_p) {
        return _p;
    }
    return [super alloc];
}
-(instancetype)copyWithZone:(NSZone *)zone
{
    return [Person sharedInstance];
}
-(instancetype)mutableCopyWithZone:(NSZone *)zone
{
    return [Person sharedInstance];
}

单例优缺点:

优点:
(1)初始化后可供全局调用,方便
(2)只初始化一次,在程序只存在一个对象,节省了系统内存资源。
缺点:
(1)不能被继承
(2)不易被重写(可以用分类)
(3)在程序运行中,不会被销毁,会一直占用部分内存,在闲置时,占用了内存资源。

补充:怎么用宏定义 定义单例类,在类中写上宏定义后,使这个类变为单例类。

在.h中定义宏定义
// .h文件
#define ZYShareSingleton_H(name) \
+(instancetype)shared##name
// .m文件
#define ZYShareSingleton_M(name) \
+(instancetype)shared##name \
{ \
    return [[self alloc]init]; \
} \
+(instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
    static name *instance = nil; \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        instance = [super allocWithZone:zone]; \
    }); \
    return instance; \
} \
-(instancetype)copyWithZone:(NSZone *)zone \
{ \
    return [name shared##name]; \
} \
-(instancetype)mutableCopyWithZone:(NSZone *)zone \
{ \
    return [name shared##name]; \
}

这样,在需要使用单例的类Person中如下定义:
在类的.h中 写上ZYShareSingleton_H(Person);

@interface Person : NSObject
ZYShareSingleton_H(Person);
@end

在类的.m中 写上ZYShareSingleton_M(Person);

@implementation Person
ZYShareSingleton_M(Person);
@end

demo地址:https://github.com/coloredSky/iOS.git

小知识:宏定义中的字符用处

(1)
字符:##
作用:链接字符创
例:

#define KName(n) x ## n
=>使用
KName(8)
=>输出
x8

(2)
字符:\
作用:续行符,当定义的宏不能用一行表达完整时,用 \ 表示下一行继续此宏的定义。
例:

#define K_Man(a,b) \
a>=b?a:b
=>使用
K_Man(2,3)
=>输出
3

(3)
字符:#
作用:字符串化。给参数加双引号。
例:

#define K_Char(n) #n
=>使用
K_Char(888)
=>输出
"888"

(4)
字符:VA_ARGS
作用:用来接受不定数量的参数。可以是一个、两个、n个参数
例:

#define K_Log(...) NSLog(__VA_ARGS__)
=>使用
K_Log(@"%@%@",@"111",@"222");
=>输出
111222

(5) ## 的特殊用法
"##"放在逗号","和参数之间,如果参数留空的话,那么"##"前面的","就会被删掉,从而防止编译错误。
例:

#define K_Log(parameter1, ...) NSLog(parameter1, __VA_ARGS__)
K_Log(@"11111");//会报错,因为至少需要传递两个参数,而只传了一个
#define K_Log(parameter1, ...) NSLog(parameter1, ##__VA_ARGS__)
K_Log(@"11111");//不会报错,虽然只传了一个参数,但##将前面的","去除了

推荐阅读更多精彩内容