mcpField
A
axiom-objc-block-retain-cycles

axiom-objc-block-retain-cycles

github.com/charleswiltgen開発1.2k169 回インストール今日更新
リポジトリ

axiom-objc-block-retain-cycles について

Objective-C 開発者向けに、block の循環参照によるメモリ問題の診断と修正を案内するスキルです。診断、パターン選択、検証の手順に沿い、weak-strong パターンやネストした block、隠れた self 参照を扱います。

主な機能

  1. 01block の循環参照を診断
  2. 02weak-strong パターンの適用
  3. 03マクロ内の隠れた参照を確認
  4. 04ネストした block を階層ごとに処理
  5. 05オブジェクトの解放を検証

活用シーン

  1. 01画面を閉じてもビューコントローラーが解放されない
  2. 02非同期コールバックでメモリが増え続ける
  3. 03NSLog や NSAssert 内に self 参照がある

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-objc-block-retain-cycles

インストール後、Claude Code で/axiom-objc-block-retain-cyclesを入力して読み込まれたか確認します。

詳細

Objective-C のブロックによる保持サイクル

概要

ブロックによる保持サイクルは、Objective-C のメモリーリークの最大の原因です。ブロックが self をキャプチャし、同じオブジェクトに(直接、または operation/request を介して間接的に)保存されると、self → block → self という循環参照が発生します。基本原則 ブロックによるメモリーリークの 90%は、weak-strong パターンの欠落または誤った適用が原因であり、Apple フレームワーク自体の不具合が原因ではありません。

危険信号 — ブロックによる保持サイクルを疑う

次のいずれかが見られる場合、ほかの原因ではなく、ブロックによる保持サイクルを疑ってください。

  • 通常のアプリ使用中にメモリー使用量が徐々に増え続ける
  • UIViewController のインスタンスが解放されない(Instruments で確認済み)
  • クラッシュ:「解放済みインスタンスへのメッセージ送信」がネットワーク/非同期コールバックで発生する
  • ネットワークリクエストやアニメーションによって、ビューコントローラーを閉じられない
  • ブロック内で弱参照が予期せず nil になる
  • NSLog、NSAssert、文字列フォーマットによって self への参照が隠れている
  • ビューコントローラーが「解放されているはず」の後に完了ハンドラーが実行される
  • ❌ 禁止 「おそらく通常のメモリー使用量」として合理化する
    • メモリーリークは決して「通常」ではありません
    • ユーザーが画面を閉じた後、アプリのメモリー使用量は基準値に戻るべきです
    • 「これで十分」や「後で監視する」として合理化しないでください

重要な区別 ブロックによる保持サイクルは、気付かれないまま蓄積します。1 つのサイクルが 100KB でも、50 画面を表示した後には、解放されないメモリーが 5MB に達します。必須:修正後はシミュレーターだけでなく、実機(サポート対象の最も古いモデル)でテストしてください。

必須の最初の手順

必ず最初に、次の操作を実行してください(コードを変更する前に)。

// 1. Identify the leak with Allocations instrument
// In Xcode: Xcode > Open Developer Tool > Instruments
// Choose Allocations template
// Perform an action (open/close a screen with the suspected block)
// Check if memory doesn't return to baseline
// Record: "Memory baseline: X MB, after action: Y MB, still allocated: Z objects"

// 2. Use Memory Debugger to trace the cycle
// Run app, pause at suspected code location
// Debug > Debug Memory Graph
// Search for the view controller that should be deallocated
// Right-click > Show memory graph
// Look for arrows pointing back to self (the cycle)
// Record: "ViewController retained by: [operation/block/property]"

// 3. Check if block is assigned to self or self's properties
// Search for: setBlock:, completion:, handler:, callback:
// Check: Is the block stored in self.property?
// Check: Is the block passed to something that retains it (network operation)?
// Record: "Block assigned to: [property or operation]"

// 4. Search for self references in the block
// Look for: [self method], self.property, self-> access
// Look for HIDDEN self references:
//   - NSLog(@"Value: %@", self.property)
//   - NSAssert(self.isValid, @"message")
//   - Format strings: @"Name: %@", self.name
// Record: "self references found in block: [list]"

// Example output:
// Memory not returning to baseline ✓
// ViewController retained by: AFHTTPRequestOperation
// Operation retains: successBlock
// Block references self: [self updateUI], NSLog with self.property
// → DIAGNOSIS: Block retain cycle confirmed
この結果から分かること
  • メモリー使用量が高いまま → 誤検知ではなく、リークが確認されています
  • ViewController が operation によって保持されている → 原因はブロックです
  • ブロックが self を参照している → 必要なパターンは weak-strong です
  • NSLog/NSAssert に隠れた self → すべてのマクロ呼び出しを確認する必要があります
  • self への参照が見つからない → ブロックによるサイクルではない可能性があります。別の原因を調査してください
必須の解釈

コードを変更する前に、次のいずれかを必ず確認してください。

  1. メモリー使用量が基準値に戻らず、ViewController がまだ割り当てられている場合 → ブロックによる保持サイクルが存在します
  2. メモリー使用量が基準値に戻る場合 → 保持サイクルではありません。ほかの原因を調査してください
  3. サイクルが存在するものの self への参照が見つからない場合 → 隠れた参照(マクロ、間接的なプロパティアクセス)を確認してください
  4. サイクルを見つけたものの参照の連鎖が分からない場合 → Memory Graph で保持されているオブジェクトをたどって、逆方向に追跡してください
診断結果に矛盾がある場合、または不明瞭な場合
  • 停止してください。まだパターンの適用に進んではいけません
  • 診断情報を追加してください:オブジェクトグラフを出力し、保持されているオブジェクトを一覧表示します
  • 「メモリー使用量が少ないのに、なぜ ViewController はまだ割り当てられているのか」と考えてください
  • メモリーグラフが分かりにくい場合は、Instruments の Leaks インストゥルメントを実行してください

判断フロー

Block memory leak suspected?
├─ Memory stays high after dismiss?
│  ├─ YES
│  │  ├─ ViewController still allocated in Memory Graph?
│  │  │  ├─ YES → Proceed to patterns
│  │  │  └─ NO → Not a block cycle, check other leaks
│  │  └─ NO → Not a leak, normal memory usage
│  │
│  └─ Crash: "Sending message to deallocated instance"?
│     ├─ Happens in block/callback?
│     │  ├─ YES → Block captured weakSelf but it became nil
│     │  │  └─ Apply Pattern 4 (Guard condition is wrong or missing)
│     │  └─ NO → Different crash, not block-related
│     └─ Crash is timing-dependent (only on device)?
│        └─ YES → Weak reference timing issue, apply Pattern 2
│
├─ Block assigned to self or self.property?
│  ├─ YES → Apply Pattern 1 (weak-strong mandatory)
│  ├─ Assigned through network operation/timer/animation?
│  │  └─ YES → Apply Pattern 1 (operation retains block indirectly)
│  └─ Block called immediately (inline execution)?
│     ├─ YES → Optional to use weak-strong (no cycle possible)
│     │  └─ But recommend for consistency with other blocks
│     └─ NO → Block stored or passed to async method → Use Pattern 1
│
├─ Multiple nested blocks?
│  └─ YES → Apply Pattern 3 (must guard ALL nested blocks)
│
├─ Block contains NSAssert, NSLog, or string format with self?
│  └─ YES → Apply Pattern 2 (macro hides self reference)
│
└─ Implemented weak-strong pattern but still leaking?
   ├─ Check: Is weakSelf used EVERYWHERE?
   ├─ Check: No direct `self` references mixed in?
   ├─ Check: Nested blocks also guarded?
   └─ Check: No __unsafe_unretained used?

よくあるパターン

パターン選択のルール(必須)
一度に 1 つのパターンだけを、次の順序で適用します
  1. 必ずパターン 1(Weak-Strong の基本)から始めます

    • ブロックが self または self のプロパティに割り当てられている場合 → パターン 1
    • self を保持する operation/request にブロックが渡されている場合 → パターン 1
    • パターン 1 を適用してもリークが続く場合に限り、パターン 2 に進みます
  2. 次にパターン 2(マクロ内に隠れた self)

    • パターン 1 の適用後もメモリーリークが続く場合に限ります
    • NSAssert、NSLog、文字列フォーマットを確認します
    • 見つかった場合はパターン 2 を適用します
  3. 次にパターン 3(ネストされたブロック)

    • ブロックにネストされたコールバックがある場合に限ります
    • ネストされた各ブロックに、それぞれ独自のガードが必要です
    • 見つかった場合はパターン 3 を適用します
  4. 次にパターン 4(ガード条件のエッジケース)

    • weakSelf アプローチでクラッシュが発生する場合に限ります
    • ガード条件が正しいことを確認します
    • すべての箇所で strongSelf が使われていることを確認します
禁止事項
  • ❌ 複数のパターンを一度に適用する
  • ❌ 「weak-strong はもう分かっている」という理由でパターン 1 を飛ばす
  • ❌ 回避策として __unsafe_unretained を使う
  • ❌ 「今回だけ」と strong self を使う
  • ❌ 「ブロックが小さいからリークにはならない」として合理化する

パターン 1: weak-strong パターン (必須)

原則 self をキャプチャするブロックは、そのブロックが self によって (直接的または間接的に) 保持される場合、必ず weak-strong パターンを使用しなければなりません。

❌ 誤り (循環参照を引き起こす)
[self.networkManager GET:@"url" success:^(id response) {
    self.data = response;  // self is retained by block
    [self updateUI];       // block is retained by operation
} failure:^(NSError *error) {
    [self handleError:error];  // CYCLE!
}];
✅ 正しい (循環を断ち切る)
__weak typeof(self) weakSelf = self;
[self.networkManager GET:@"url" success:^(id response) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        strongSelf.data = response;
        [strongSelf updateUI];
    }
} failure:^(NSError *error) {
    __weak typeof(self) weakSelf2 = self;
    typeof(self) strongSelf = weakSelf2;
    if (strongSelf) {
        [strongSelf handleError:error];
    }
}];
これが機能する理由
  1. __weak typeof(self) weakSelf = self; はブロックの外側で weak 参照を作成します
  2. ブロックは self (strong 参照) ではなく weakSelf (weak 参照) をキャプチャします
  3. ブロックが実行される際に、strongSelf (一時的な strong 参照) に変換します
  4. strongSelf が nil かどうかを確認します (オブジェクトが解放された)
  5. ブロックの実行中は strongSelf を使用します
  6. ブロックの終了時に strongSelf が解放されます → 循環は発生しません
重要な詳細
  • weakSelf はブロックの内側ではなく、外側で宣言してください
  • 型安全性のために typeof(self) を使用してください (ARC と非 ARC の両方で機能します)
  • ガード条件は必ず if (strongSelf) を使用し、単に宣言するだけにしないでください
  • weakSelf を宣言したら、ブロック内で直接 self を使用しないでください
  • self をキャプチャするすべてのブロックに適用してください
  • self をキャプチャするあらゆるブロックは weak-strong パターンを使用しなければなりません
    • これには以下が含まれます: [self method]、self.property、self->ivar
    • プロパティアクセス (self.property = value) は、メソッド呼び出しと同様に self をキャプチャします
  • フレームワークに渡されるブロック:
    • フレームワークのドキュメントに「ブロックは非同期で呼び出される」と記載されている場合 → weak-strong パターンを使用してください (フレームワークがブロックを保持します)
    • フレームワークのドキュメントに「ブロックは即座に呼び出される」と記載されている場合 → それでも weak-strong を使用するのが安全です (より良い慣行です)
    • フレームワークの動作が不明な場合 → 常に weak-strong を使用してください (害はありません)
変数のキャプチャ (間接的な self 参照を避ける)
// ✅ SAFE: Capture simple values extracted from self
__weak typeof(self) weakSelf = self;
[self.manager fetch:^(id response) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        NSString *name = strongSelf.name;  // Extract value
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Name: %@", name);  // Captured the STRING, not self
        });
    }
}];

// ❌ WRONG: Capture properties directly in nested blocks
__weak typeof(self) weakSelf = self;
[self.manager fetch:^(id response) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Name: %@", strongSelf.name);  // Captures strongSelf again!
        });
    }
}];

ブロックをネストする場合は、まず単純な値を抽出し、それを内側のブロックに渡してください。これにより、プロパティアクセスを通じた self の間接的なキャプチャが発生するのを避けられます。

所要時間 ブロックあたり 30 秒


パターン 2: マクロに隠れた self

原則 NSAssert、NSLog、文字列フォーマットなどのマクロは、密かに self をキャプチャする可能性があります。それらを確認する必要があります。

❌ 誤り (NSAssert が self をキャプチャする)
[self.button setTapAction:^{
    NSAssert(self.isValidState, @"State must be valid");  // self captured!
    [self doWork];  // Another self reference
}];
// Leak exists even though you think only [self doWork] captures self
✅ 正しい (隠れたキャプチャを確認する)
__weak typeof(self) weakSelf = self;
[self.button setTapAction:^{
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // NSAssert still references self indirectly through strongSelf
        NSAssert(strongSelf.isValidState, @"State must be valid");
        [strongSelf doWork];
    }
}];
よくある隠れた self 参照
  • NSAssert(self.condition, ...) → 代わりに strongSelf を使用してください
  • NSLog(@"Value: %@", self.property) → strongSelf.property を使用してください
  • NSError *error = [NSError errorWithDomain:@"MyApp" ...] → 安全です、self をキャプチャしません
  • 文字列フォーマット: @"Name: %@", self.name → strongSelf.name を使用してください
  • インライン条件式: self.flag ? @"yes" : @"no" → strongSelf.flag を使用してください
それらを見つける方法
  1. ブロック内のすべての self. のインスタンスを検索します
  2. それらをマークします: [self method]、self.property、self->ivar
  3. それらがマクロ呼び出し (NSAssert、NSLog など) の内側にあるかどうかを確認します
  4. strongSelf に置き換えます

所要時間 ブロックあたり 1 分で監査


パターン 3: ネストされたブロック (それぞれにガードが必要)

原則 ネストされたブロックは連鎖を作ります: 外側のブロックが self をキャプチャし、内側のブロックが外側のブロック変数 (strongSelf を保持している) をキャプチャし、新たな循環を作ります。各ネストされたブロックには、それぞれ独自の weak-strong パターンが必要です。

❌ 誤り (外側のブロックのみをガードしている)
__weak typeof(self) weakSelf = self;
[self.manager fetchData:^(NSArray *result) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // Inner block captures strongSelf!
        [strongSelf.analytics trackEvent:@"Fetched"
                              completion:^{
            strongSelf.cachedData = result;  // Still strong reference!
            [strongSelf updateUI];
        }];
    }
}];
✅ 正しい (すべてのネストされたブロックをガードする)
__weak typeof(self) weakSelf = self;
[self.manager fetchData:^(NSArray *result) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // Declare new weak reference for inner block
        __weak typeof(strongSelf) weakSelf2 = strongSelf;

        [strongSelf.analytics trackEvent:@"Fetched"
                              completion:^{
            typeof(strongSelf) strongSelf2 = weakSelf2;
            if (strongSelf2) {
                strongSelf2.cachedData = result;
                [strongSelf2 updateUI];
            }
        }];
    }
}];
これが機能する理由
  • 各ネストレベルには、それぞれ独自の weakSelf/strongSelf のペアが必要です
  • 外側のブロック: weakSelf → strongSelf
  • 内側のブロック: weakSelf2 → strongSelf2
  • 各レベルは独立しており安全です
重要な詳細
  • ネストされたブロックで同じ weakSelf 変数を再利用しないでください
  • 各ネストレベルには新しいペア (weakSelf2、strongSelf2) を使用してください
  • ガード条件は各レベルで必須です
  • 一貫した命名を使用してください: weakSelf、weakSelf2、weakSelf3 (可読性のため)
パターン 3 を必要とする一般的なネストされたブロックパターン
  • コールバック内の完了ハンドラ
  • dispatch_async(queue, ^{ ... })
  • dispatch_after(time, queue, ^{ ... })
  • [NSTimer scheduledTimerWithTimeInterval:... block:^{ ... }]
  • [UIView animateWithDuration:... animations:^{ ... }]

これらのそれぞれは strongSelf をキャプチャする可能性のあるブロックであり、それぞれ独自の weak-strong パターンが必要です。

dispatch_async を使用した例
__weak typeof(self) weakSelf = self;
[self.manager fetchData:^(NSArray *result) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        __weak typeof(strongSelf) weakSelf2 = strongSelf;

        dispatch_async(dispatch_get_main_queue(), ^{
            typeof(strongSelf) strongSelf2 = weakSelf2;
            if (strongSelf2) {
                strongSelf2.data = result;
                [strongSelf2 updateUI];
            }
        });
    }
}];

所要時間 ネストレベルごとに1分


パターン 4:ガード条件のエッジケース

原則 ガード条件 if (strongSelf) が正しくなければなりません。よくある間違いは、ガードを忘れる、条件を誤る、または self と strongSelf を混在させることです。

❌ 誤り(複数のガードの失敗)
__weak typeof(self) weakSelf = self;
[self.button setTapAction:^{
    typeof(self) strongSelf = weakSelf;
    // MISTAKE 1: Forgot guard condition
    self.counter++;  // CRASH! self is deallocated, accessing freed object

    // MISTAKE 2: Guard exists but used wrong variable
    if (weakSelf) {
        [weakSelf doWork];  // weakSelf is weak, might become nil again
    }

    // MISTAKE 3: Mixed self and strongSelf
    if (strongSelf) {
        self.flag = YES;  // Used self instead of strongSelf!
        [strongSelf doWork];
    }
}];
✅ 正しい(適切なガードと一貫した使用)
__weak typeof(self) weakSelf = self;
[self.button setTapAction:^{
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // CORRECT: Use strongSelf everywhere, never self
        strongSelf.counter++;
        strongSelf.flag = YES;
        [strongSelf doWork];
    }
    // If strongSelf is nil, entire block skips gracefully
}];
これが機能する理由
  1. if (strongSelf) はオブジェクトがまだ存在するかどうかを確認します
  2. 存在する場合、strongSelf は強参照です(安全です)
  3. 存在しない場合(オブジェクトが解放済みの場合)、ブロックはスキップされます
  4. すべての箇所で strongSelf を使うことで、意図しない self 参照を防げます
重要なルール(必須、例外なし)
  • ✅ 使用前に必ず if (strongSelf) を確認する
  • ✅ if ブロック内では必ず strongSelf を使い、直接 self を使わない
  • ✅ strongSelf はブロックのスコープ全体で有効であることが保証されています
  • ❌ 絶対に if (!strongSelf) return; を使わない(ロジックが紛らわしくなります)
  • ❌「コードを節約する」ためにガードを省略しない
  • ❌ weakSelf と strongSelf へのアクセスを混在させない
  • ❌ ガードなしで strongSelf を使わない(必ずクラッシュします)
間違えた場合に起きること
  • ガードなし:「解放済みインスタンスにメッセージを送信しています」というエラーでクラッシュします
  • 条件が誤っている場合:オブジェクトが解放されたままになり、やはりクラッシュします
  • self と strongSelf の混在:意図しない self が1つあるだけで、パターン全体が無効になります
  • ガードなしで strongSelf を使用:オブジェクトが解放されていると必ずクラッシュします
ガード内
if (strongSelf) {
    strongSelf.data1 = value1;
    [strongSelf doWork1];
    [strongSelf doWork2];  // All safe
}
// ❌ WRONG: Using strongSelf after guard ends
strongSelf.data = value2;  // CRASH! Outside guard
してはいけないこと
// ❌ FORBIDDEN: strongSelf without guard guarantees crash
typeof(self) strongSelf = weakSelf;
strongSelf.data = value;  // CRASH if weakSelf is nil!

// ✅ MANDATORY: Always guard before using strongSelf
if (strongSelf) {
    strongSelf.data = value;  // Safe
}

所要時間 ガードが正しいか、ブロックごとに10秒かけて確認します


クイックリファレンス表

問題確認事項修正方法
メモリ使用量がベースラインに戻らないメモリグラフに ViewController がまだ存在していますか?パターン 1(weak-strong)を適用します
クラッシュ:「解放済みインスタンスへのメッセージ」ガード条件が抜けているか、誤っていませんか?パターン 4(正しいガード)を適用します
weak-strong を適用してもリークするself への参照はすべて strongSelf を使っていますか?self と strongSelf の混在がないか確認します
ブロックに NSAssert または NSLog が含まれているそれらは self を参照していますか?パターン 2(マクロ内で strongSelf を使用)を適用します
ネストされたブロックすべてのレベルで weak-strong を適用していますか?パターン 3(各ブロックにガードを適用)を適用します
ブロックが循環参照を作るかどうかわからないブロックは self または self.property に代入されていますか?はいの場合は、パターン 1 を適用します

30分経っても行き詰まっている場合

30分以上かけてもリークが解消されない場合:

作業を止めてください。次のいずれかです
  1. 必須の診断手順を省略した(最もよくある原因)
  2. すべてのブロックに weak-strong を適用していない(ネストされたブロックを見落としている)
  3. 隠れた self 参照がある(NSAssert、NSLog、文字列フォーマット)
  4. パターンを適用したものの、直接 self 参照を混在させている
  5. 別の種類のリークがある(ブロックに関連しない)
「スキルが機能しなかった」と主張する前に必ず確認するチェックリスト
  • 4つの診断ブロック(Allocations、Memory Graph、ブロック検索、self 参照検索)をすべて実行した
  • Instruments でメモリ使用量がベースラインに戻らないことを確認した
  • ViewController がまだ割り当てられている(解放されていない)ことを確認した
  • 保持チェーン(ViewController を保持しているもの)を追跡した
  • self をキャプチャするブロックをすべて見つけた(ファイル内で [self をグローバル検索)
  • 隠れた self 参照(NSAssert、NSLog、文字列フォーマット)を確認した
  • 外側のブロックに weak-strong パターンを適用した
  • ネストされたブロックにも weak-strong パターンを適用した(すべてのネストレベル)
  • 直接の self 参照が残っていない(strongSelf のみ)ことを確認した
  • Instruments を再度実行し、メモリ使用量がベースラインに戻った
  • シミュレーターだけでなく、実機でテストした
  • 実行の合間に Xcode の派生データを消去した
すべてにチェックが付いているのに、まだリークする場合
  • ブロック以外のリーク(Core Data、タイマー、デリゲート、通知)があります
  • Instruments > Leaks インストゥルメントを使って、実際の循環参照を特定します
  • 2~3分間プロファイルします:画面を開き、閉じる操作を5回繰り返します
  • 「Leaks」パネルを確認します。解放されていないものが正確に表示されます
  • 所要時間:本当の原因を特定するまで15~30分
ブロックのリークではないと判明した場合
  • 「ブロックは問題なさそうだから、このままリリースしよう」と正当化しないでください
  • 実際のサイクルを見つけてください(デリゲート、タイマー、プロパティオブザーバー、通知などが原因の可能性があります)
  • 誤検知ではなく、実際の問題を修正してください
所要時間の目安
  • パターン 1:ブロックごとに 30 秒
  • パターン 2:ブロックごとに 1 分(隠れた self を監査)
  • パターン 3:ネストのレベルごとに 1 分
  • 行き詰まった場合のネスト診断:15~30 分
  • 単純なリークの合計:5~10 分

よくある間違い

❌ ガード条件を忘れる

  • strongSelf.property = value; を if (strongSelf) なしで使う
  • オブジェクトが解放されたときにクラッシュする
  • 修正:必ず if (strongSelf) { ... } を使ってください

❌ 同じブロック内で self と strongSelf を混在させる

  • self.flag = YES; [strongSelf doWork];
  • self を直接参照すると、このパターンの効果がなくなります
  • 修正:ブロック内では strongSelf だけを使ってください

❌ 外側のブロックにだけパターンを適用する

  • ネストしたブロックは依然として strongSelf を強参照します
  • リークは解消されません
  • 修正:すべてのブロックに weak-strong パターンを適用してください

❌ 「回避策」として __unsafe_unretained を使う

  • ❌ 禁止パターンです。安全ではなく、クラッシュの原因になります
  • オブジェクトが解放されたときにクラッシュします
  • 解決策ではなく、問題を悪化させます
  • 修正:代わりに weak-strong パターンを使ってください

❌ 隠れた self の参照を確認しない

  • ブロック内の NSLog(@"Value: %@", self.property)
  • weak-strong を適用した後もリークが残ります
  • 修正:NSAssert、NSLog、文字列フォーマットを監査してください

❌ 「小さなリークだから」と正当化する

  • ブロック 1 つのリークでも 100KB になることがあります
  • 画面が 50 個あれば、累積して 5MB になります
  • 最終的にメモリ不足でアプリがクラッシュします
  • 修正:すべてのブロックリークを修正し、正当化しないでください

❌ システムフレームワーク内のブロックは安全だと思い込む

  • UIView アニメーション、AFNetworking、dispatch、タイマー
  • いずれも self を参照するブロックを保持する可能性があります
  • 修正:参照元にかかわらず、weak-strong パターンを適用してください

❌ シミュレーターだけでテストする

  • シミュレーターのメモリプレッシャーは実機と異なります
  • 実機に負荷がかかるまでリークが現れないことがあります
  • 修正:実機で、サポート対象の最も古いモデルを使ってテストしてください

実際の影響

導入前 ブロックのメモリリークのデバッグに問題ごとに 2~3 時間

  • Allocations を実行しても、どこを見ればよいかわからない
  • あちこちを検索しても、明確な診断手順がない
  • 手当たり次第に修正を試し、どれかが効くことを期待する
  • サンクコストの誤謬に陥り、そのままリリースする
  • 顧客からクラッシュや動作低下の報告が届く

導入後 系統的な診断により 5~10 分

  • Allocations を実行し、メモリがベースラインに戻らないことを確認する
  • Memory Graph で何が保持されているかを正確に確認する
  • グローバル検索で self をキャプチャするブロックをすべて見つける
  • weak-strong パターンを適用する(ブロックごとに 30 秒)
  • Instruments でテストし、メモリがベースラインに戻ることを確認する
  • 完了

重要なポイント ブロックの保持サイクルは、weak-strong パターンで 100% 防止できます。例外や、強参照してもよい「特別なケース」はありません。


最終更新日:2025-11-30 ステータス:負荷シナリオで TDD テスト済み フレームワーク:Objective-C、ブロック(クロージャ)、ARC

コメント