Objective-C单例的严谨写法

2020-04-22  本文已影响0人  哦小小树
先附上写法,后面分析
+ (instancetype)shareInstance {
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    static ObjManager * instance;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

0x01单例的调用方式

ObjManager *obj1 = [ObjManager shareInstance];
ObjManager *obj2 = [[ObjManager alloc] init]
ObjManager *obj3 = [ObjManager new]
ObjManager *obj4 = [[ObjManager  allocWithZone:NULL] init];
目标

创建一个单例必须要满足以上四种调用生成的都只是同一个对象


0x02写法延伸

构建类方法

+ (instancetype)shareInstance {
    static dispatch_once_t onceToken;
    static ObjManager *instance;
    dispatch_once(&onceToken, ^{
        instance = [[ObjManager alloc] init];
    });
    return instance;
}

测试

ObjManager *obj = [[ObjManager alloc] init];
ObjManager *obj1 = [ObjManager shareInstance];
ObjManager *obj2 = [ObjManager new];
ObjManager *obj3 = [[ObjManager allocWithZone:NULL] init];
ObjManager *obj4 = [ObjManager shareInstance];

NSLog(@"\nobj:%@\nobj1:%@\nobj2:%@\nobj3:%@\nobj4:%@",obj, obj1, obj2, obj3, obj4);

# 打印输出
obj:<ObjManager: 0x60000137c480>
obj1:<ObjManager: 0x60000137c4b0>
obj2:<ObjManager: 0x60000137c4c0>
obj3:<ObjManager: 0x60000137c4d0>
obj4:<ObjManager: 0x60000137c4b0>

# 只有obj1和obj4是通过shareInstance创建的一样,其他都不同

只有通过shareInstance调用的方法生成的单例才是一样的,如果通过其他几种方法生成的对象并不是一个单例

将实现向底层迁移

由于单例是共享一块存储空间,所以我们考虑对alloc方法进行重写;
如果重写init是无效的,init只是用来初始化分配好的那块内存空间的。

+ (instancetype)shareInstance {
    return [[self alloc] init];
}

+ (instancetype)alloc {
    static dispatch_once_t onceToken;
    static ObjManager *instance;
    dispatch_once(&onceToken, ^{
        instance = [super alloc];
    });
    return instance;
}

测试

ObjManager *obj = [[ObjManager alloc] init];
ObjManager *obj1 = [ObjManager shareInstance];
ObjManager *obj2 = [ObjManager new];
ObjManager *obj3 = [[ObjManager allocWithZone:NULL] init];
ObjManager *obj4 = [ObjManager shareInstance];

NSLog(@"\nobj:%@\nobj1:%@\nobj2:%@\nobj3:%@\nobj4:%@",obj, obj1, obj2, obj3, obj4);

# 打印输出
obj:<ObjManager: 0x600000b1fdc0>
obj1:<ObjManager: 0x600000b1fdc0>
obj2:<ObjManager: 0x600000b1fdc0>
obj3:<ObjManager: 0x600000b1fdd0>
obj4:<ObjManager: 0x600000b1fdc0>

# 除了obj3是通过allocWithZone:,无法涵盖其他都可以
将内存分配再向下迁移
+ (instancetype)shareInstance {
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    static ObjManager * instance;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

测试

ObjManager *obj = [[ObjManager alloc] init];
ObjManager *obj1 = [ObjManager shareInstance];
ObjManager *obj2 = [ObjManager new];
ObjManager *obj3 = [[ObjManager allocWithZone:NULL] init];
ObjManager *obj4 = [ObjManager shareInstance];

NSLog(@"\nobj:%@\nobj1:%@\nobj2:%@\nobj3:%@\nobj4:%@",obj, obj1, obj2, obj3, obj4);

# 打印输出
obj:<ObjManager: 0x600000630500>
obj1:<ObjManager: 0x600000630500>
obj2:<ObjManager: 0x600000630500>
obj3:<ObjManager: 0x600000630500>
obj4:<ObjManager: 0x600000630500>

# 可以完全确保无论如何构建单例,都不会出现返回不同的对象效果

0x03 执行过程

// 在此处打下断点
ObjManager *obj = [[ObjManager alloc] init];

等到执行此处时,我们设置断点:

# lldb 命令,因为我们没有实现ObjManager的alloc方法,因此需要打NSObject alloc的断点,看执行过程
(lldb) br set -n "+[NSObject alloc]"
Breakpoint 5: where = libobjc.A.dylib`+[NSObject alloc], address = 0x000000010ed07f34

# 继续执行
(lldb) c

进入汇编调试页面

libobjc.A.dylib`+[NSObject alloc]:
->  0x10ed07f34 <+0>: jmp    0x10ed07f58               ; _objc_rootAlloc

即将开始调用 _objc_rootAlloc,再次设置符号断点

(lldb) br set -n "_objc_rootAlloc"
Breakpoint 6: where = libobjc.A.dylib`_objc_rootAlloc, address = 0x000000010ed07f58
# 执行,进入汇编页面
(lldb) c

进入汇编调试页面

libobjc.A.dylib`_objc_rootAlloc:
->  0x10ed07f58 <+0>:  movq   (%rdi), %rax
    0x10ed07f5b <+3>:  testb  $0x40, 0x1d(%rax)
    0x10ed07f5f <+7>:  je     0x10ed07f66               ; <+14>
    0x10ed07f61 <+9>:  jmp    0x10ed020a3               ; _objc_rootAllocWithZone
    0x10ed07f66 <+14>: movq   0x140eb(%rip), %rsi       ; "allocWithZone:"
    0x10ed07f6d <+21>: xorl   %edx, %edx
    0x10ed07f6f <+23>: jmpq   *0x120eb(%rip)            ; (void *)0x000000010ecee400: objc_msgSend

通过内部实现可以看到最终会进入到allocWithZone:方法


总结:

无论是通过什么方式进行创建,最初想操作系统申请内存空间的方法一定会调用allocWithZone:。

单例的严谨写法可以归为:

+ (instancetype)shareInstance {
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    static ObjManager * instance;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}
上一篇 下一篇

猜你喜欢

热点阅读