Objective-C Singletons和LLVM / clang泄漏警告
-
06-07-2019 - |
题
我在应用程序的几个地方使用单例模式,在分析代码时,我从 clang
中得到内存泄漏错误。
static MyClass *_sharedMyClass;
+ (MyClass *)sharedMyClass {
@synchronized(self) {
if (_sharedMyClass == nil)
[[self alloc] init];
}
return _sharedMyClass;
}
// clang error: Object allocated on line 5 is no longer referenced after this point and has a retain count of +1 (object leaked)
我正在将这些设置用于 scan-build
:
scan-build -v -v -v -V -k xcodebuild
我非常肯定单例中的代码很好 - 毕竟,它与Stack Overflow以及Apple的文档中引用的代码相同 - 但我希望将内存泄漏警告排除在外我的scan-build返回成功。
解决方案
我可能会异常密集,但肯定是你的第5行
[[self alloc] init];
分配包含类类型的对象,并迅速抛弃它?你不想要
_sharedMyClass = [[self alloc] init];
其他提示
Apple已经更新了他们的推荐单例代码通过静态分析器:
+ (MyGizmoClass*)sharedManager
{
if (sharedGizmoManager == nil) {
sharedGizmoManager = [[super allocWithZone:NULL] init];
}
return sharedGizmoManager;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedManager] retain];
}
现在 + sharedManager
调用super的 -allocWithZone:
并分配 -init
的返回值,以及singleton的 -allocWithZone:
只返回一个保留的sharedInstance。
编辑:
为什么保留+ allocWithZone:?
+ allocWithZone:被覆盖,因为使用MyGizmoClass的人可以通过调用[[MyGizmoClass alloc] init]而不是[MyGizmoClass sharedManager]来绕过单例。它被保留,因为+ alloc应该总是返回一个保留计数为+1的对象。
对+ alloc的每次调用都应该使用-release或-autorelease进行平衡,因此如果不在+ allocWithZone:中保留,共享实例可能会从其他用户下解除分配。
您可能会对 Mike Ash的网站:
+ (id)sharedFoo
{
static dispatch_once_t pred;
static Foo *foo = nil;
dispatch_once(&pred, ^{ foo = [[self alloc] init]; });
return foo;
}
您正在类方法中引用 self
!大禁忌!其次,您正在调用 [[self alloc] init]
并丢弃实例。您应该在类方法中分配单例引用,而不是在 init
中,就像我猜你正在做的那样。接下来,没有真正保证将 _sharedMyClass
初始化为零。您应该将其显式初始化为 nil
。
static MyClass *_sharedMyClass = nil;
+ (MyClass *)sharedMyClass {
@synchronized(self) {
if (_sharedMyClass == nil)
_sharedMyClass = [[MyClass alloc] init];
}
return _sharedMyClass;
}
你也可能也有这个......
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [super allocWithZone:zone];
return sharedInstance; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
你没有将它存储在init中的原因是因为你将它存储在alloc调用的方法中。这是Apple在他们的例子中的模式。如果你也在init中保存了值,一切都很好,警告消失了。我将单独留下allocWithZone实现。