Objective-Cシングルトンと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
シングルトンのコードが問題ないことはかなり確かです-結局のところ、それはここでスタックオーバーフローと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];
}
Now + sharedManager
はスーパーの -allocWithZone:
を呼び出し、 -init
の戻り値とシングルトンの -allocWithZone:
は保持されたsharedInstanceを返します。
編集:
+ allocWithZoneで保持する理由:?
+ allocWithZone:は、MyGizmoClassを使用しているユーザーが[MyGizmoClass sharedManager]の代わりに[[MyGizmoClass alloc] init]を呼び出すことでシングルトンを回避できるため、オーバーライドされます。 + allocは、保持カウントが+1のオブジェクトを常に返すことが期待されるため、保持されます。
+ allocのすべての呼び出しは、-releaseまたは-autoreleaseとバランスを取る必要があるため、+ allocWithZone:での保持なしでは、共有インスタンスが他のユーザーから割り当て解除される可能性があります。
+ (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の実装はそのままにしておきます。