你真的能写好一个单例么?
关注公众号“iOSSir”,看你想看的,给你想要的!
单例可能是 iOS 开发者最熟悉设计模式之一了。 我们的项目里头也使用了很多单例。 最近为了解决项目中单例的 bug 而花费了两天多的时间,发现用 ObjC 写好一个单例真的不容易!
V1.0
可能有很多人不服气,单例么, 有什么难的, 一个简单的 dispatch_once
不就解决了么! 比如下边的代码:
@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]
获取到的的确都是同一个实例。但是有没有例外情况呢?
- 比如
SingletonClass
这个类需要托管到其他框架, 那么其他框架生成实例的时候, 为了通用, 一般都会通过[[SingletonClass alloc] init]
来初始化; - 项目中的单例类又没有明显标识, 长的和其他类差不多, 那么会不会有某些同事"误用"
[[SingletonClass alloc] init]
来初始化呢? 毕竟你又没有规定不让用。
那么问题来了, 运行下边代码:
NSLog(@"1: %p", [SingletonClass sharedInstance]); NSLog(@"2: %p", [SingletonClass sharedInstance]); NSLog(@"3: %p", [[SingletonClass alloc] init]); NSLog(@"4: %p", [[SingletonClass alloc] init]);
输出结果:
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)。 费了老大的劲, 终于从一些别人的历史文章里边找到了和当年苹果差不多的实现:
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 之后测试看看:
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! 完美!
且慢~~ 在用到项目中的时候, 还是有问题。 原来项目中有单例继承的情况(关于单例是否可以继承, 以及什么场景下用单例继承, 这是另外一个争论话题~)。 那就写个子类继承单例, 测试一下吧:
@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]);
运行结果如下:
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!
修改一下, 给儿子把方法补全:
@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:
修改如下:
/// 父类 + (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]; }
执行测试用例:
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
方法:
- (instancetype)init { self = [super init]; NSLog(@"%@ call %s", self, __PRETTY_FUNCTION__); return self; }
继续运行测试用例, 输出如下:
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
中添加标记呢? 我能想到的办法有俩:
- 通过 method swizzle 替换单例的
-init
方法。 我们可以给每个单例增加一个 category, 然后在 category 中实现+load
方法(不用担心会覆盖主类中的+load
, 每个 category 都可以添加自己的+load
方法, 而且这些+load
方法都会被执行), 在这里替换掉-init
。 - 模板中实现
-init
, 就可以增加这个标记了, 然后定义一个新的初始化方法-singletonInit
, 在-init
中调用就可以了。外部单例类只需要实现这个-singletonInit
就可以了。
经过仔细考虑, 我最后选择了方案二, 主要是 method swizzle 风险不太可控, 方案二虽然保守, 但是比较可靠。
修改一下单例 -init
方法实现:
// 父类, 子类也类似 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); } 复制代码
继续运行测试用例, 结果如下:
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
。 所以我们继续添加如下代码:
/// .h @interface NSObject (SingletonInit) - (void)singletonInit NS_REQUIRES_SUPER; @end /// .m @implementation NSObject (SingletonInit) - (void)singletonInit {} @end 复制代码
然后在每个单例的 -singletonInit
中添加[super singletonInit];
, 运行测试用例, 输出如下:
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 单例模式:
// static SingletonClass *instance_SingletonClass = nil; static __weak SingletonClass *instance_SingletonClass = nil; 复制代码
运行下边的测试用例:
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]); 复制代码
结果如下:
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
:
+ (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; } 复制代码
输出如下:
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
#ifndef ALSingletonTemplate_H #define ALSingletonTemplate_H /** * A template code for define a singleton class. * Example: <code> // .h file @interface SingletionTest : NSObject AS_SINGLETON @end // .m file @implementation SingletionTest SYNTHESIZE_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) __AL_SINGLETON_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) __AL_SINGLETON_MACRO_CONCAT_(a, b) #undef __AL_SINGLETON_INSTANCE_FOR_CLASS #define __AL_SINGLETON_INSTANCE_FOR_CLASS(cls) __AL_SINGLETON_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
-
NSObject+ALSingletonInit.h
@interface NSObject (ALSingletonInit) - (void)singletonInit NS_REQUIRES_SUPER; @end
-
NSObject+ALSingletonInit.m
#import "NSObject+ALSingletonInit.h" @implementation NSObject (ALSingletonInit) - (void)singletonInit {}; @end
把这几个文件添加到工程中, 如果某个类需要时单例, 只需在文件中简单的添加两行就可以:
// .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 单例模式还需要另外考虑。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
图解:openinstall的APP传参安装流程详解
APP 如何自动实现携带参数安装?这是许多开发者感兴趣的问题,毕竟在 APP 开发的许多逻辑上常常不可避免的需要判断安装来源,比如:广告投放、用户邀请、用户行为、社交分享等 APP 推广环节,国内的 openinstall 就是 APP 安装来源追踪领域的专业第三方服务商。 下面我们就来详解 openinstall 的APP传参安装技术流程: 首先开发者在分享的 H5 页面上集成 openinstall 的 web sdk,发布分享链接时在 url 上动态拼接自定义参数(如:邀请码、渠道编号、游戏房间号等)。 当用户点击该链接时,设备的个性化信息和自定义参数将会被自动采集,并上传到 openinstall 服务器暂存。 用户通过该 H5 页面安装 APP(包括跳转APP Store、Android各大应用市场、直接下载等)并首次打开时,openinstall Android/iOS sdk 将从 openinstall 服务器取回暂存的参数进行匹配;如果用户已安装 APP,则点击链接时直接唤醒 APP 并还原内部场景页面,同时进行参数匹配。 简而言之,开发者只需根据 APP 开发需求拼...
- 下一篇
View事件机制分析
目录介绍 01.Android中事件分发顺序 1.1 事件分发的对象是谁 1.2 事件分发的本质 1.3 事件在哪些对象间进行传递 1.4 事件分发过程涉及方法 1.5 Android中事件分发顺序 02.Activity的事件分发机制 2.1 源码分析 2.2 点击事件调用顺序 2.3 得出结论 03.ViewGroup事件的分发机制 3.1 看一下这个案例 3.2 源码分析 3.3 得出结论 04.View事件的分发机制 4.1 源码分析 4.2 得出结论 4.3 验证结论 05.思考一下 5.1 onTouch()和onTouchEvent()的区别 5.2 Touch事件的后续事件传递 好消息 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! 链接地址:https://git...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境