前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >你真的能写好一个单例么?

你真的能写好一个单例么?

原创
作者头像
iOSSir
修改于 2019-05-13 01:52:51
修改于 2019-05-13 01:52:51
9450
举报

单例可能是 iOS 开发者最熟悉设计模式之一了。 我们的项目里头也使用了很多单例😓。 最近为了解决项目中单例的 bug 而花费了两天多的时间,发现用 ObjC 写好一个单例真的不容易!

V1.0

可能有很多人不服气,单例么, 有什么难的, 一个简单的 dispatch_once 不就解决了么! 比如下边的代码:

代码语言:txt
AI代码解释
复制
@implementation SingletonClass
+ (instancetype)sharedInstance {
    static SingletonClass *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
//...
@end

可能大部分人的单例都是这么实现的, 貌似也没啥问题,通过[SingletonClass sharedInstance] 获取到的的确都是同一个实例。但是有没有例外情况呢?

  1. 比如SingletonClass这个类需要托管到其他框架, 那么其他框架生成实例的时候, 为了通用, 一般都会通过[[SingletonClass alloc] init]来初始化;
  2. 项目中的单例类又没有明显标识, 长的和其他类差不多, 那么会不会有某些同事"误用" [[SingletonClass alloc] init]来初始化呢? 毕竟你又没有规定不让用。

那么问题来了, 运行下边代码:

代码语言:txt
AI代码解释
复制
NSLog(@"1: %p", [SingletonClass sharedInstance]);
NSLog(@"2: %p", [SingletonClass sharedInstance]);
NSLog(@"3: %p", [[SingletonClass alloc] init]);
NSLog(@"4: %p", [[SingletonClass alloc] init]);

输出结果:

代码语言:txt
AI代码解释
复制
2019-04-12 18:44:51.147445+0800 TestProj[92371:7344641] 1: 0x600002a0c360
2019-04-12 18:44:51.147553+0800 TestProj[92371:7344641] 2: 0x600002a0c360
2019-04-12 18:44:51.147630+0800 TestProj[92371:7344641] 3: 0x600002a1e700
2019-04-12 18:44:51.147737+0800 TestProj[92371:7344641] 4: 0x600002a11060

可以看出, 1和2是一样的, 但是和3, 4都不一样, 所以这种方案不完善。

弊端:没有保证无论用何种初始化方法, 都应该只有一个实例。

V2.0

在很久很久以前, iOS的蛮荒时代, 那时候还没有 swift, 苹果还把 Objective-C 叫“小甜甜”。 在苹果网站上, 曾经有一份OC实现单例的 sample code(现在没有了,链接失效了, 现在只有 swift 的, 毕竟现在的小甜甜是 swift)。 费了老大的劲, 终于从一些别人的历史文章里边找到了和当年苹果差不多的实现:

代码语言:txt
AI代码解释
复制
static SingletonClass *sharedInstance = nil;

@implementation SingletonClass
#pragma mark Singleton Methods
+ (id)sharedInstance {
  @synchronized(self) {
      if(sharedInstance == nil)
          sharedInstance = [[super allocWithZone:NULL] init];
  }
  return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
  return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone {
  return self;
}
- (id)retain {
  return self;
}
- (unsigned)retainCount {
  return UINT_MAX; //denotes an object that cannot be released
}
- (oneway void)release {
  // never release
}
- (id)autorelease {
  return self;
}
- (void)dealloc {
  // Should never be called, but just here for clarity really.
  [someProperty release];
  [super dealloc];
}
@end

这个还是 MRC 的, 那时候也还没有 dispatch_once。 改写成 ARC 之后测试看看:

代码语言:txt
AI代码解释
复制
2019-04-12 21:59:16.844126+0800 TestProj[6248:7514391] 1: 0x600002afc430
2019-04-12 21:59:16.844285+0800 TestProj[6248:7514391] 2: 0x600002afc430
2019-04-12 21:59:16.844402+0800 TestProj[6248:7514391] 3: 0x600002afc430
2019-04-12 21:59:16.844499+0800 TestProj[6248:7514391] 4: 0x600002afc430
复制代码

OK! 完美!

且慢~~ 在用到项目中的时候, 还是有问题。 原来项目中有单例继承的情况😭(关于单例是否可以继承, 以及什么场景下用单例继承, 这是另外一个争论话题~)。 那就写个子类继承单例, 测试一下吧:

代码语言:txt
AI代码解释
复制
@interface SingletonClassSon : SingletonClass
@end
@implementation SingletonClassSon
@end

/// test case:
NSLog(@"01: %@", [SingletonClass sharedInstance]);
NSLog(@"02: %@", [[SingletonClass alloc] init]);

NSLog(@"11: %@", [SingletonClassSon sharedInstance]);
NSLog(@"12: %@", [[SingletonClassSon alloc] init]);

运行结果如下:

代码语言:txt
AI代码解释
复制
2019-04-12 22:10:47.305874+0800 TestProj[6737:7524929] 01: <SingletonClass: 0x60000166ca20>
2019-04-12 22:10:47.306011+0800 TestProj[6737:7524929] 02: <SingletonClass: 0x60000166ca20>
2019-04-12 22:10:47.306110+0800 TestProj[6737:7524929] 11: <SingletonClass: 0x60000166ca20>
2019-04-12 22:10:47.306191+0800 TestProj[6737:7524929] 12: <SingletonClass: 0x60000166ca20>

WTF?爹还是爹, 儿子不见了? 原因是子类调用的是父类的sharedInstance方法, 直接返回父类的实例了, 子类根本没有被 alloc!

修改一下, 给儿子把方法补全:

代码语言:txt
AI代码解释
复制
@interface SingletonClassSon : SingletonClass
@end
@implementation SingletonClassSon
#pragma mark Singleton Methods
+ (id)sharedInstance {
    static SingletonClassSon *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[super allocWithZone:NULL] init];
    });
    return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
    return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
    return self;
}
@end

继续运行原来的 test case, 崩了:

调用栈如下, 很明显子类的 sharedInstance 方法发生了递归调用, 导致dispatch_once死锁了:图片上传失败...(image-153bfc-1557473278527)

弊端:无法实现单例继承

V3.0

仔细观察上个版本的崩溃堆栈, 发现问题所在就是 allocWithZone:的实现! 把两个类的allocWithZone: 修改如下:

代码语言:txt
AI代码解释
复制
/// 父类
+ (id)allocWithZone:(NSZone *)zone {
    if (self == SingletonClass.class) {
        return [self sharedInstance];
    }
    return [super allocWithZone:zone];
}

/// 子类
+ (id)allocWithZone:(NSZone *)zone {
    if (self == SingletonClassSon.class) {
        return [self sharedInstance];
    }
    return [super allocWithZone:zone];
}

执行测试用例:

代码语言:txt
AI代码解释
复制
2019-04-12 22:46:44.697281+0800 TestProj[8125:7555154] 01: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.697575+0800 TestProj[8125:7555154] 02: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.698047+0800 TestProj[8125:7555154] 11: <SingletonClassSon: 0x6000014b7840>
2019-04-12 22:46:44.698309+0800 TestProj[8125:7555154] 12: <SingletonClassSon: 0x6000014b7840>

🎉🎉🎉 大功告成~~~

放到项目中跑起来, 貌似隐约感觉不对~~~ 有些单例中的状态怎么被reset 了? 添加一些生命周期方法, 加上日志测试。。。 原来问题在-init上!

分别给父类和子类添加如下 -init方法:

代码语言:txt
AI代码解释
复制
- (instancetype)init {
    self = [super init];
    NSLog(@"%@ call %s", self, __PRETTY_FUNCTION__);
    return self;
}

继续运行测试用例, 输出如下:

代码语言:txt
AI代码解释
复制
2019-04-12 22:46:44.697151+0800 TestProj[8125:7555154] <SingletonClass: 0x6000014b7830> call -[SingletonClass init]
2019-04-12 22:46:44.697281+0800 TestProj[8125:7555154] 01: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.697398+0800 TestProj[8125:7555154] <SingletonClass: 0x6000014b7830> call -[SingletonClass init]
2019-04-12 22:46:44.697575+0800 TestProj[8125:7555154] 02: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.697881+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClass init]
2019-04-12 22:46:44.697959+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClassSon init]
2019-04-12 22:46:44.698047+0800 TestProj[8125:7555154] 11: <SingletonClassSon: 0x6000014b7840>
2019-04-12 22:46:44.698138+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClass init]
2019-04-12 22:46:44.698213+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClassSon init]
2019-04-12 22:46:44.698309+0800 TestProj[8125:7555154] 12: <SingletonClassSon: 0x6000014b7840>

一眼就能看到, 只要用 alloc + init 的方式获取单例实例, -init方法都会被执行一次, 单例中的状态也就会丢失了~。

弊端:无法保证初始化方法不可重入。

V4.0

我们在项目中, 为了减少重复代码, 把单例的实现写成一个模板, 只需要把这个宏添加到类实现中, 就能把这个类变成单例。详情可以参考我很久很久以前的文章

如何保证初始化方法不可重入呢? 这个问题我想了好久, 貌似除了在-init方法中添加初始化标记, 没有其他办法了。 但是如何在 -init中添加标记呢? 我能想到的办法有俩:

  1. 通过 method swizzle 替换单例的-init方法。 我们可以给每个单例增加一个 category, 然后在 category 中实现+load方法(不用担心会覆盖主类中的+load, 每个 category 都可以添加自己的+load方法, 而且这些+load方法都会被执行), 在这里替换掉-init
  2. 模板中实现 -init, 就可以增加这个标记了, 然后定义一个新的初始化方法 -singletonInit, 在 -init 中调用就可以了。外部单例类只需要实现这个 -singletonInit就可以了。

经过仔细考虑, 我最后选择了方案二, 主要是 method swizzle 风险不太可控, 方案二虽然保守, 但是比较可靠。

修改一下单例 -init方法实现:

代码语言:txt
AI代码解释
复制
// 父类, 子类也类似
static SingletonClass *instance_SingletonClass = nil;
- (instancetype)init {
    static dispatch_semaphore_t semaphore;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        semaphore = dispatch_semaphore_create(1);
    });

    SingletonClass *strongRef = instance_SingletonClass;
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    if (strongRef.class != self.class) {
        self = [super init];
        if (self.class == SingletonClass.class) {
            SEL sel = NSSelectorFromString(@"singletonInit");
            if ([self respondsToSelector:sel]) {
                [self performSelector:sel];
            }
            instance_SingletonClass = self;
        }
    }
    dispatch_semaphore_signal(semaphore);
    return self;
}

- (void)singletonInit {
    NSLog(@"caller: %@; SingletonClass customic init", self);
}
复制代码

继续运行测试用例, 结果如下:

代码语言:txt
AI代码解释
复制
2019-04-13 13:04:35.396087+0800 TestProj[11692:7647465] caller: <SingletonClass: 0x600002c681d0>; SingletonClass customic init
2019-04-13 13:04:35.396231+0800 TestProj[11692:7647465] 01: <SingletonClass: 0x600002c681d0>
2019-04-13 13:04:35.396312+0800 TestProj[11692:7647465] 02: <SingletonClass: 0x600002c681d0>
2019-04-13 13:04:35.396402+0800 TestProj[11692:7647465] caller: <SingletonClassSon: 0x600002c63280>; SingletonClassSon customic init
2019-04-13 13:04:35.396473+0800 TestProj[11692:7647465] 11: <SingletonClassSon: 0x600002c63280>
2019-04-13 13:04:35.396561+0800 TestProj[11692:7647465] 12: <SingletonClassSon: 0x600002c63280>
复制代码

这次好像没问题了, 不会重复执行-init方法了。 可是子类的初始化貌似不太对?因为我们现在修改了-init方法, 真正的类的初始化是在-init里的-singletonInit里边进行的, 因此子类的初始化也必须调用父类的方法, 这样才能保证完全初始化。 所以我们必须在-singletonInit中调用 super 方法。 可是问题来了, -singletonInit是需要开发者自己实现的, 怎么保证开发者不会忘记添加[super singletonInit]呢? 大家可能会想起, 在 xcode 中写 viewController 的时候,-viewWillAppear:等方法, 如果不写 supper 调用, 就会出编译警告, 提示你必须调用 super 方法。 这就是利用了 llvm 的编译属性, 苹果已经把它封装成一个宏:NS_REQUIRES_SUPER。 所以我们继续添加如下代码:

代码语言:txt
AI代码解释
复制
/// .h
@interface NSObject (SingletonInit)
- (void)singletonInit NS_REQUIRES_SUPER;
@end

/// .m
@implementation NSObject (SingletonInit)
- (void)singletonInit {}
@end
复制代码

然后在每个单例的 -singletonInit 中添加[super singletonInit];, 运行测试用例, 输出如下:

代码语言:txt
AI代码解释
复制
2019-04-13 13:40:57.294312+0800 TestProj[12932:7675173] caller: <SingletonClass: 0x6000028874f0>; SingletonClass customic init
2019-04-13 13:40:57.294442+0800 TestProj[12932:7675173] 01: <SingletonClass: 0x6000028874f0>
2019-04-13 13:40:57.294569+0800 TestProj[12932:7675173] 02: <SingletonClass: 0x6000028874f0>
2019-04-13 13:40:57.294653+0800 TestProj[12932:7675173] caller: <SingletonClassSon: 0x600002898240>; SingletonClass customic init
2019-04-13 13:40:57.294724+0800 TestProj[12932:7675173] caller: <SingletonClassSon: 0x600002898240>; SingletonClassSon customic init
2019-04-13 13:40:57.294810+0800 TestProj[12932:7675173] 11: <SingletonClassSon: 0x600002898240>
2019-04-13 13:40:57.294879+0800 TestProj[12932:7675173] 12: <SingletonClassSon: 0x600002898240>
复制代码

事情貌似都解决了, 嗯~~ 好像又看到了一个新概念weak singleton。 修改成 wean 单例模式:

代码语言:txt
AI代码解释
复制
// static SingletonClass *instance_SingletonClass = nil;
static __weak SingletonClass *instance_SingletonClass = nil;
复制代码

运行下边的测试用例:

代码语言:txt
AI代码解释
复制
    id obj = [SingletonClass sharedInstance];
    NSLog(@"01: %@", obj);
    NSLog(@"02: %@", [[SingletonClass alloc] init]);

    obj = [SingletonClass sharedInstance];
    NSLog(@"11: %@", obj);
    NSLog(@"12: %@", [[SingletonClass alloc] init]);

    obj = nil;
    obj = [SingletonClass sharedInstance];
    NSLog(@"21: %@", obj);
    NSLog(@"22: %@", [[SingletonClass alloc] init]);
复制代码

结果如下:

代码语言:txt
AI代码解释
复制
2019-04-14 13:24:21.327596+0800 TestProj[36068:8203530] 01: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.327725+0800 TestProj[36068:8203530] 02: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.327950+0800 TestProj[36068:8203530] 11: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.328037+0800 TestProj[36068:8203530] 12: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.328366+0800 TestProj[36068:8203530] 21: (null)
2019-04-14 13:24:21.328617+0800 TestProj[36068:8203530] 22: (null)
复制代码

对象被释放之后, 再也不能继续创建单例了, 得到的都是nil。 原因就是, dispatch_once, 得换个方法。

弊端:不支持 weak 单例

V5.0

我们把+sharedInstance里边的dispatch_once换成dispatch_semaphore

代码语言:txt
AI代码解释
复制
+ (id)sharedInstance {
    __block SingletonClass *strongRef = instance_SingletonClass;
    if (strongRef == nil) {
        static dispatch_semaphore_t lock;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            lock = dispatch_semaphore_create(1);
        });
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        if (instance_SingletonClass == nil) {
            strongRef = [[super allocWithZone:NULL] init];
            instance_SingletonClass = strongRef;
        } else {
            strongRef = instance_SingletonClass;
        }
        dispatch_semaphore_signal(lock);
    }
    return strongRef;
}
复制代码

输出如下:

代码语言:txt
AI代码解释
复制
2019-04-14 13:29:20.280302+0800 TestProj[36272:8208680] 01: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280400+0800 TestProj[36272:8208680] 02: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280486+0800 TestProj[36272:8208680] 11: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280594+0800 TestProj[36272:8208680] 12: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280871+0800 TestProj[36272:8208680] 21: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.281358+0800 TestProj[36272:8208680] 22: <SingletonClass: 0x600003824970>
复制代码

至此, 我们得到了一个基本完整ObjC单例实现, 我们用宏把它变成一个模板:

  • ALSingletonTemplate.h
代码语言:txt
复制
代码语言:txt
复制

#ifndef ALSingletonTemplate_H

代码语言:txt
复制

#define ALSingletonTemplate_H

代码语言:txt
复制

/**

代码语言:txt
复制
  • A template code for define a singleton class.// .m file@implementation SingletionTestSYNTHESIZE_SINGLETON(SingletionTest)// IMPORTANT: DO NOT add `-init` in you singleton class!!! you should use `-singletonInit` instead!!!// and DONT FORGET to add `[super singletonInit]` in you singletonInit method.- (void)singletonInit { [super singletonInit]; // your init code here ... }// your code here ...@end// usage:SingletionTest *singleton = [SingletionTest sharedInstance];// or: SingletionTest *singleton = [[SingletionTest alloc] init];// or: SingletionTest *singleton = [SingletionTest new];</code> */////////////////////////////////////////////////////////////////////////////////////////////////// singleton#undef AL_AS_SINGLETON#if __has_feature(objc_arc)#define AL_AS_SINGLETON \ + (instancetype)sharedInstance; \ + (void)al_destroySingleton; \ - (void)al_destroySingleton;#else #define AL_AS_SINGLETON \ + (instancetype)sharedInstance; #endif/// weak singleton; only supports ARC#if __has_feature(objc_arc)#undef AL_AS_WEAK_SINGLETON#define AL_AS_WEAK_SINGLETON AL_AS_SINGLETON#endif///////////////////////////////////////////////////////////////////////////////////////////////#undef AL_SYNTHESIZE_SINGLETON#if __has_feature(objc_arc)#undef AL_SYNTHESIZE_WEAK_SINGLETON#define AL_SYNTHESIZE_WEAK_SINGLETON(CLS) \ static __weak CLS *__AL_SINGLETON_INSTANCE_FOR_CLASS(CLS) = nil; \ __AL_SYNTHESIZE_SINGLETON_ARC(CLS);#define AL_SYNTHESIZE_SINGLETON(CLS) \ static CLS *__AL_SINGLETON_INSTANCE_FOR_CLASS(CLS) = nil; \ __AL_SYNTHESIZE_SINGLETON_ARC(CLS);#else#define AL_SYNTHESIZE_SINGLETON(CLS) \ static CLS *__AL_SINGLETON_INSTANCE_FOR_CLASS(CLS) = nil; \ __AL_SYNTHESIZE_SINGLETON_MRC(CLS);#endif///////////////////////////////////////////////////////////////////////////////////////////////#undef __AL_SINGLETON_SEMAPHORE_FOR_CLASS#define AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls) ALSINGLETON_MACRO_CONCAT(__al_singleton_semaphore, cls)#undef __AL_SYNTHESIZE_SINGLETON_COMMON#define __AL_SYNTHESIZE_SINGLETON_COMMON(cls) \+(dispatch_semaphore_t) __AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls) { \ static dispatch_semaphore_t semaphore; \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ semaphore = dispatch_semaphore_create(1); \ }); \ return semaphore; \} \ \+(instancetype) sharedInstance { \ if (self != cls.class) { \ printf( \ "‼️ [SINGLETON] class `%s` invokes `%s` will return the instance of `%s`, which is not the one " \ "you expected.\n\n", \ NSStringFromClass(self).UTF8String, __PRETTY_FUNCTION__, #cls); \ } \ __block cls *strongRef = __AL_SINGLETON_INSTANCE_FOR_CLASS(cls); \ if (strongRef == nil) { \ dispatch_semaphore_t semaphore = [cls __AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls)]; \ __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(semaphore, \ if (__AL_SINGLETON_INSTANCE_FOR_CLASS(cls) == nil) { \ strongRef = [[super allocWithZone:NULL] init]; \ __AL_SINGLETON_INSTANCE_FOR_CLASS(cls) = strongRef; \ } else { strongRef = __AL_SINGLETON_INSTANCE_FOR_CLASS(cls); }); \ } \ return strongRef; \} \ \ + (id) allocWithZone : (NSZone *) zone { \ if (self == cls.class) { \ return [self sharedInstance]; \ } \ return [super allocWithZone:zone]; \} \ \-(instancetype) init { \ static dispatch_semaphore_t semaphore; \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ semaphore = dispatch_semaphore_create(1); \ }); \ \ cls *strongRef = __AL_SINGLETON_INSTANCE_FOR_CLASS(cls); \ __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(semaphore, if (strongRef.class != self.class) { \ self = [super init]; \ if (self.class == cls.class) { \ [self singletonInit]; \ } \ }); \ return self; \} \ \-(id) copyWithZone : (nullable NSZone *) zone { \ return self; \} \-(id) mutableCopyWithZone : (nullable NSZone *) zone { \ return self; \}///////////////////////////////////////////////////////////////////////////////////////////////#undef __AL_SYNTHESIZE_SINGLETON_ARC#define __AL_SYNTHESIZE_SINGLETON_ARC(cls) \__AL_SYNTHESIZE_SINGLETON_COMMON(cls); \+ (void)al_destroySingleton { \ printf("‼️ [SINGLETON] The singleton instance `%s` will be deallocated.\n", \ [self description].UTF8String); \ dispatch_semaphore_t lock = [cls __AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls)]; \ __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(lock, \ __AL_SINGLETON_INSTANCE_FOR_CLASS(cls) = nil; \ ); \} \-(void) al_destroySingleton { \ [self.class al_destroySingleton]; \};///////////////////////////////////////////////////////////////////////////////////////////////#undef __AL_SYNTHESIZE_SINGLETON_MRC#define __AL_SYNTHESIZE_SINGLETON_MRC(cls) \__AL_SYNTHESIZE_SINGLETON_COMMON(cls); \ \- (instancetype)retain { return self; } \ - (oneway void)release{} \ - (instancetype)autorelease { return self; } \ - (NSUInteger)retainCount { return NSUIntegerMax; }///////////////////////////////////////////////////////////////////////////////////////////////#undef _AL_SINGLETON_MACRO_CONCAT#define _AL_SINGLETON_MACRO_CONCAT(a, b) a##b#undef __AL_SINGLETON_MACRO_CONCAT#define AL_SINGLETON_MACRO_CONCAT(a, b) ALSINGLETON_MACRO_CONCAT(a, b)#undef __AL_SINGLETON_INSTANCE_FOR_CLASS#define AL_SINGLETON_INSTANCE_FOR_CLASS(cls) ALSINGLETON_MACRO_CONCAT(__al_singleton_instance, cls)////// execute the code statements jobStmt in dispatch_semaphore./// Try to get the semaphore in 10 secods, if failed, that may means a deadlock is occured. and you should check you code./// @note DO NOT return in jobStmt, otherwise the samaphore will not be processed correctly.///#undef __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT#define __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(sema, jobStmt) \if (dispatch_semaphore_wait((sema), dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.f * NSEC_PER_SEC))) == 0) { \ jobStmt; \ dispatch_semaphore_signal((sema)); \} else { \ NSAssert(NO, @"[SINGLETON] %s: timeout while waiting to acquire the lock. Deadlock may occured!", __PRETTY_FUNCTION__); \}#endif // ALSingletonTemplate_H
  • Example: <code> // .h file @interface SingletionTest : NSObject AS_SINGLETON @end
  • NSObject+ALSingletonInit.h
代码语言:txt
复制
代码语言:txt
复制

@interface NSObject (ALSingletonInit)

代码语言:txt
复制
  • (void)singletonInit NS_REQUIRES_SUPER; @end
  • NSObject+ALSingletonInit.m
代码语言:txt
复制
代码语言:txt
复制

#import "NSObject+ALSingletonInit.h"

代码语言:txt
复制

@implementation NSObject (ALSingletonInit)

代码语言:txt
复制
  • (void)singletonInit {}; @end

把这几个文件添加到工程中, 如果某个类需要时单例, 只需在文件中简单的添加两行就可以:

代码语言:txt
AI代码解释
复制
// .h
@interface MyClass : NSObject
AL_AS_SINGLETON; // <- 头文件中加入这个宏

/// your code here ...
@end

// .m
@implementation MyClass
AL_SYNTHESIZE_SINGLETON(MyClass); // <- .m文件中加入这个宏

/// 需要注意的是, 初始化不能直接用 init 方法, 需要用 singletonInit
/// - (void)singletonInit {
///    /// 初始化代码写这里, 比如
///     _myIvar = xxx;
/// }

/// your code here ...
@end

总结

要用 ObjC 实现一个完整的单例, 需要注意以下几点:

  • 不管用何种初始化方式, 都只能有一个实例。
  • alloc init必须保证“原子性”,否则在多线程情况下就会出现 ThreadA 执行完 alloc, 然后另外一个线程就有可能获取到的是这个刚 alloc 出来还没执行 init 的实例,导致意外情况。
  • int 必须保证只能执行一次。
  • 【可选】继承,weak 单例模式还需要另外考虑。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
2 条评论
热度
最新
这个py文件在哪里下载呀作者大大
这个py文件在哪里下载呀作者大大
11点赞举报
github搜ARL-Finger-ADD-Pro,能够看到
github搜ARL-Finger-ADD-Pro,能够看到
回复回复点赞举报
推荐阅读
ARL-Finger-ADD-Pro更新V1.2.0版本,新增指纹库
1. 新增指纹库finger_chunsou.json,导入ARL后新增不重复4848条指纹,共计:12488;
BigYoung小站
2024/06/27
2580
ARL-Finger-ADD-Pro更新V1.2.0版本,新增指纹库
Chunsou(春蒐)多线程Web指纹识别工具(项目发布)
Chunsou(春蒐),Python编写的多线程Web指纹识别工具,适用于安全测试人员前期的资产识别、风险收敛以及企业互联网资产摸查。目前主要功能为针对Web资产进行指纹识别,目前指纹规则条数约 10000+,辅助功能包括子域名爆破和FOFA资产收集。工具开发初衷为辅助网络安全人员开展测试工作,提高资产识别和管理的效率。项目地址:https://github.com/Funsiooo/chunsou
funsiooo
2023/07/05
6200
开源堡垒机JumpServer远程命令执行漏洞复现
JumpServer 是全球首款完全开源的堡垒机, 使用GNU GPL v2.0 开源协议, 是符合4A 的专业运维审计系统。JumpServer 使用Python / Django 进行开发。
Timeline Sec
2021/02/03
3.9K0
开源堡垒机JumpServer远程命令执行漏洞复现
EHole(棱洞)3.0 红队重点攻击系统指纹检测工具
EHole是一款对资产中重点系统指纹识别的工具,在红队作战中,信息收集是必不可少的环节,如何才能从大量的资产中提取有用的系统(如OA、V**、Weblogic...)。EHole旨在帮助红队人员在信息收集期间能够快速从C段、大量杂乱的资产中精准定位到易被攻击的系统,从而实施进一步攻击。
用户8478399
2022/09/27
2.8K1
EHole(棱洞)3.0 红队重点攻击系统指纹检测工具
自动化一开,SRC没跑了!冲啊!!!
可以对 suricata 规则进行测试, 需先拉取和启用 suricata 容器 (注意:本功能需要社区版权限)
天钧
2021/03/04
2.2K0
【漏洞复现】jumpserver未授权访问漏洞
完成后进入安装目录/opt/jumpserver-installer-v2.6.1,执行如下命令即可启动jumpserver:
谢公子
2022/01/19
6.2K0
【漏洞复现】jumpserver未授权访问漏洞
Jumpserver-RCE复现及报警规则
简便的方法是,根据影响版本可以得到的信息是,v2.6.2是安全的,所以只需要在github上对比v2.6.2和v2.6.1的版本更迭记录即可。
FB客服
2021/03/09
1K0
Jumpserver-RCE复现及报警规则
聚合了一波渗透测试工具&学习资料的方方面面,更有2.6w+POC/EXP,不是广告哦~
最近在更新自己的武器库,实时追随新的工具,学习大佬们的骚操作。当个牛X的“脚本小子”,哈哈~。
用户1631416
2021/09/17
1.9K1
分享几款好用的端口扫描器
最近乱七八糟的忙,没有收获但感觉还挺累的,好操蛋。趁周末介绍几款在办公网扫描的小工具,亲测效果还不错,小巧迅速,功能强大,导出的报告也是干净整洁没什么花里胡哨的的东西,缺点是没怎么隐匿自己的流量,如果碰到蜜罐肯定要栽的,但是相比nmap来讲,他们实在太快太香。
天钧
2021/01/18
22.9K2
分享几款好用的端口扫描器
SRC信息收集思路分享
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
drunk_kk
2021/11/04
2K0
CVE-2023-42820:JumpServer密码重置漏洞
JumpServer开源堡垒机是一款运维安全审计系统产品,提供身份验证、授权控制、账号管 理、安全审计等功能支持,帮助企业快速构建运维安全审计能力。JumpServer开源堡垒机 通过企业版或者软硬件一体机的方式,向企业级用户交付开源增值的运维安全审计解决方 案。
Timeline Sec
2023/11/13
5.5K0
CVE-2023-42820:JumpServer密码重置漏洞
红蓝对抗|常用攻防工具大全
首先恭喜你发现了宝藏。本项目集成了全网优秀的攻防武器项目,包含信息收集工具(自动化利用工具、资产发现工具、目录扫描工具、子域名收集工具....etc...),漏洞利用工具(各大CMS利用工具、中间件利用工具等项目),内网渗透工具、应急响应工具、甲方运维工具、等其他安全资料项目,供攻防双方使用。如果你有更好的建议,欢迎提出请求,本文收录全网优秀工具,欢迎大佬提交。
亿人安全
2022/06/30
5.3K0
红蓝对抗|常用攻防工具大全
Python程序打包指南:手把手教你一步步完成
最近感兴趣想将开发的项目转成Package,研究了一下相关文章,并且自己跑通了,走了一下弯路,这里记录一下如何打包一个简单的Python项目,展示如何添加必要的文件和结构来创建包,如何构建包,以及如何将其上传到Python包索引(PyPI)。
用户3578099
2023/11/16
2.1K0
Python程序打包指南:手把手教你一步步完成
干货 | 渗透测试之目标分析与指纹识别总结
•形如: www.xxx.com www.xxx.com/bbs www.xxx.com/old•渗透思路:网站可能有多个cms或框架组成,那么对于渗透而言,相当于渗透目标是多个(一个cms一个思路)
HACK学习
2021/12/21
2.5K0
Python | 搭建Python数据分析平台
来源:http://blog.csdn.net/raptor/article/details/78035782
用户1634449
2018/12/13
1.3K0
复式记账指北(三):如何打造不半途而废的记账方案
没错,我回来更新了!没想到上篇文章安利效果超强,所以我就打算先行跳过第二篇,先介绍个人方案以便各位参考啦。至于第二篇的详细做账方法就慢慢摸吧。不过由于这篇文章按照规划来讲还是第三篇,所以会有一些虚空引用第二篇的部分内容,如果看到的话请假装看过第二篇吧(逃)
KAAAsS
2022/01/14
1.9K0
复式记账指北(三):如何打造不半途而废的记账方案
一文就能看懂的Nginx操作详解,你还在查漏补缺吗!
通过安装前的配置检查,发现有报错。检查中发现一些依赖库没有找到,这时候需要先安装 nginx 的一些依赖库
Java程序猿
2021/07/27
1.2K0
harbor搭建详解(仓库阁楼搭建效果图)
Docker容器应用的开发和运行离不开可靠的镜像管理,虽然Docker官方也提供了公共的镜像仓库,但是从安全和效率等方面考虑,部署私有环境内的Registry也是非常必要的。Harbor是由VMware公司开源的企业级的Docker Registry管理项目,它包括权限管理(RBAC)、LDAP、日志审核、管理界面、自我注册、镜像复制和中文支持等功能
全栈程序员站长
2022/08/02
6490
harbor搭建详解(仓库阁楼搭建效果图)
附022.Kubernetes_v1.18.3高可用部署架构一
Kubernetes的高可用主要指的是控制平面的高可用,即指多套Master节点组件和Etcd组件,工作节点通过负载均衡连接到各Master。
木二
2020/06/16
1.3K0
Bty全国首个PHP宝塔IDC分销系统
https://url99.ctfile.com/f/34816699-534146806-de335a
若梦
2022/04/01
1.9K0
推荐阅读
相关推荐
ARL-Finger-ADD-Pro更新V1.2.0版本,新增指纹库
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档