axiom-objc-block-retain-cycles

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Objective-C Block Retain Cycles

Objective-C Block循环引用

Overview

概述

Block retain cycles are the #1 cause of Objective-C memory leaks. When a block captures
self
and is stored on that same object (directly or indirectly through an operation/request), you create a circular reference: self → block → self. Core principle 90% of block memory leaks stem from missing or incorrectly applied weak-strong patterns, not genuine Apple framework bugs.
Block循环引用是Objective-C中内存泄漏的头号原因。当Block捕获
self
并存储在同一个对象上(直接或通过操作/请求间接存储)时,就会创建循环引用:self → block → self。核心原则:90%的Block内存泄漏源于weak-strong模式的缺失或错误应用,而非Apple框架的真正漏洞。

Red Flags — Suspect Block Retain Cycle

危险信号——怀疑存在Block循环引用

If you see ANY of these, suspect a block retain cycle, not something else:
  • Memory grows steadily over time during normal app use
  • UIViewController instances not deallocating (verified in Instruments)
  • Crash: "Sending message to deallocated instance" from network/async callback
  • Network requests or animations prevent view controller from closing
  • Weak reference becomes nil unexpectedly in a block
  • NSLog, NSAssert, or string formatting hiding self references
  • Completion handler fires after the view controller "should be gone"
  • FORBIDDEN Rationalizing as "It's probably normal memory usage"
    • Memory leaks are never "normal"
    • Apps should return to baseline memory after user dismisses a screen
    • Do not rationalize this as "good enough" or "monitor it later"
Critical distinction Block retain cycles accumulate silently. A single cycle might be 100KB, but after 50 screens viewed, you have 5MB of dead memory. MANDATORY: Test on real device (oldest supported model) after fixes, not just simulator.
如果出现以下任何一种情况,要怀疑是Block循环引用,而非其他问题:
  • 正常使用应用期间,内存持续稳步增长
  • UIViewController实例未被释放(通过Instruments验证)
  • 崩溃信息:"向已释放的实例发送消息",来自网络/异步回调
  • 网络请求或动画阻止视图控制器关闭
  • Block中的弱引用意外变为nil
  • NSLog、NSAssert或字符串格式化隐式引用了self
  • 视图控制器"本应已销毁"后,完成处理程序仍触发
  • 严禁将其合理化解释为"这可能是正常的内存使用"
    • 内存泄漏绝非"正常"
    • 用户关闭页面后,应用内存应回到基准水平
    • 不要将其敷衍为"足够好了"或"以后再监控"
关键区别:Block循环引用会静默累积。单个循环可能仅占用100KB内存,但在用户浏览50个页面后,就会产生5MB的无效内存。强制要求:修复后在真实设备(支持的最旧机型)上测试,而非仅在模拟器中测试。

Mandatory First Steps

强制第一步操作

ALWAYS run these FIRST (before changing code):
objc
// 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
务必先执行以下步骤(在修改代码之前):
objc
// 1. 使用Allocations工具识别泄漏
// 在Xcode中:Xcode > Open Developer Tool > Instruments
// 选择Allocations模板
// 执行操作(打开/关闭疑似存在Block问题的页面)
// 检查内存是否未回到基准水平
// 记录:"内存基准值:X MB,操作后:Y MB,仍未释放的对象:Z个"

// 2. 使用内存调试器追踪循环引用
// 运行应用,在疑似代码位置暂停
// Debug > Debug Memory Graph
// 搜索本应被释放的视图控制器
// 右键 > 显示内存图
// 查找指向self的箭头(循环引用)
// 记录:"ViewController被以下对象持有:[操作/Block/属性]"

// 3. 检查Block是否被赋值给self或self的属性
// 搜索:setBlock:, completion:, handler:, callback:
// 检查:Block是否存储在self.property中?
// 检查:Block是否被传递给会持有它的对象(如网络操作)?
// 记录:"Block被赋值给:[属性或操作]"

// 4. 搜索Block中的self引用
// 查找:[self method], self.property, self-> 访问
// 查找隐式的self引用:
//   - NSLog(@"Value: %@", self.property)
//   - NSAssert(self.isValid, @"message")
//   - 字符串格式化:@"Name: %@", self.name
// 记录:"Block中发现的self引用:[列表]"

// 示例输出:
// 内存未回到基准值 ✓
// ViewController被AFHTTPRequestOperation持有
// 操作持有successBlock
// Block引用self:[self updateUI], 包含self.property的NSLog
// → 诊断结果:确认存在Block循环引用

What this tells you

这些步骤的作用

  • Memory stays high → Leak confirmed, not false alarm
  • ViewController retained by operation → Block is the culprit
  • Block references self → Pattern: weak-strong needed
  • Hidden self in NSLog/NSAssert → Need to check ALL macro calls
  • No self references found → Maybe not a block cycle, investigate elsewhere
  • 内存居高不下 → 确认存在泄漏,而非误报
  • ViewController被操作持有 → Block是罪魁祸首
  • Block引用self → 需要应用weak-strong模式
  • NSLog/NSAssert中存在隐式self → 必须检查所有宏调用
  • 未发现self引用 → 可能不是Block循环引用,需从其他方面排查

MANDATORY INTERPRETATION

强制解读规则

Before changing ANY code, you must confirm ONE of these:
  1. If memory doesn't return to baseline AND ViewController still allocated → Block retain cycle exists
  2. If memory returns to baseline → Not a retain cycle, investigate other causes
  3. If cycle exists but you can't find self references → Check for hidden references (macros, indirect property access)
  4. If you find the cycle but don't understand the chain → Trace backward through retained objects in Memory Graph
在修改任何代码之前,必须确认以下情况之一:
  1. 如果内存未回到基准水平且ViewController仍未被释放 → 存在Block循环引用
  2. 如果内存回到基准水平 → 不存在循环引用,排查其他泄漏原因
  3. 如果存在循环引用但未找到self引用 → 检查隐式引用(宏、间接属性访问)
  4. 如果发现循环引用但不理解引用链 → 通过内存图反向追踪持有对象

If diagnostics are contradictory or unclear

如果诊断结果矛盾或不清晰

  • STOP. Do NOT proceed to patterns yet
  • Add more diagnostics: Print the object graph, list retained objects
  • Ask: "If memory is low, why is the ViewController still allocated?"
  • Run Instruments > Leaks instrument if memory graph is confusing
  • 停止操作。暂不要应用任何模式
  • 添加更多诊断:打印对象图,列出被持有的对象
  • 自问:"如果内存占用低,为什么ViewController仍未被释放?"
  • 如果内存图难以理解,运行Instruments > Leaks工具

Decision Tree

决策树

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?
怀疑存在Block内存泄漏?
├─ 关闭页面后内存仍居高不下?
│  ├─ 是
│  │  ├─ 内存图中ViewController仍存在?
│  │  │  ├─ 是 → 应用对应模式
│  │  │  └─ 否 → 不是Block循环引用,检查其他泄漏
│  │  └─ 否 → 不是泄漏,正常内存使用
│  │
│  └─ 崩溃信息:"向已释放的实例发送消息"?
│     ├─ 崩溃发生在Block/回调中?
│     │  ├─ 是 → Block捕获了weakSelf但已变为nil
│     │  │  └─ 应用模式4(防护条件错误或缺失)
│     │  └─ 否 → 其他类型崩溃,与Block无关
│     └─ 崩溃是否与时机相关(仅在设备上出现)?
│        └─ 是 → 弱引用时机问题,应用模式2
├─ Block是否被赋值给self或self.property?
│  ├─ 是 → 应用模式1(必须使用weak-strong)
│  ├─ 是否通过网络操作/定时器/动画赋值?
│  │  └─ 是 → 应用模式1(操作间接持有Block)
│  └─ Block是否被立即调用(内联执行)?
│     ├─ 是 → 可选择使用weak-strong(不会产生循环引用)
│     │  └─ 但建议与其他Block保持一致
│     └─ 否 → Block被存储或传递给异步方法 → 使用模式1
├─ 是否存在多层嵌套Block?
│  └─ 是 → 应用模式3(必须防护所有嵌套Block)
├─ Block中是否包含NSAssert、NSLog或涉及self的字符串格式化?
│  └─ 是 → 应用模式2(宏隐式引用了self)
└─ 已应用weak-strong模式但仍存在泄漏?
   ├─ 检查:是否所有位置都使用了weakSelf?
   ├─ 检查:是否混合使用了直接`self`引用?
   ├─ 检查:嵌套Block是否也做了防护?
   └─ 检查:是否使用了__unsafe_unretained?

Common Patterns

常见模式

Pattern Selection Rules (MANDATORY)

模式选择规则(强制)

Apply ONE pattern at a time, in this order

每次仅应用一种模式,按以下顺序

  1. Always start with Pattern 1 (Weak-Strong Basics)
    • If block assigned to self or self's properties → Pattern 1
    • If block passed to operation/request that retains it → Pattern 1
    • Only proceed to Pattern 2 if pattern still leaks
  2. Then Pattern 2 (Hidden self in Macros)
    • Only if memory still leaks after applying Pattern 1
    • Check for NSAssert, NSLog, string formatting
    • If found, apply Pattern 2
  3. Then Pattern 3 (Nested Blocks)
    • Only if block has nested callbacks
    • Each nested block needs its own guard
    • If found, apply Pattern 3
  4. Then Pattern 4 (Guard Condition Edge Cases)
    • Only if crash happens with weakSelf approach
    • Check guard condition is correct
    • Verify strongSelf used everywhere
  1. 始终从模式1开始(Weak-Strong基础)
    • 如果Block被赋值给self或self的属性 → 模式1
    • 如果Block被传递给会持有它的操作/请求 → 模式1
    • 只有应用模式1后仍存在泄漏,才继续使用模式2
  2. 然后是模式2(宏中的隐式self)
    • 仅在应用模式1后内存仍泄漏时使用
    • 检查NSAssert、NSLog、字符串格式化
    • 如果存在,应用模式2
  3. 然后是模式3(嵌套Block)
    • 仅当Block包含嵌套回调时使用
    • 每个嵌套Block都需要防护
    • 如果存在,应用模式3
  4. 最后是模式4(防护条件边缘情况)
    • 仅当使用weakSelf方法时发生崩溃时使用
    • 检查防护条件是否正确
    • 验证是否在所有位置使用了strongSelf

FORBIDDEN

严禁操作

  • ❌ Applying multiple patterns at once
  • ❌ Skipping Pattern 1 because "I already know weak-strong"
  • ❌ Using __unsafe_unretained as workaround
  • ❌ Using strong self "just this once"
  • ❌ Rationalizing: "The block is too small for a leak"

  • ❌ 同时应用多种模式
  • ❌ 因为"我已经了解weak-strong"而跳过模式1
  • ❌ 使用__unsafe_unretained作为"变通方案"
  • ❌ 偶尔使用strong self
  • ❌ 合理化解释:"这个Block太小,不会导致泄漏"

Pattern 1: Weak-Strong Pattern (MANDATORY)

模式1:Weak-Strong模式(强制)

PRINCIPLE Any block that captures
self
must use weak-strong pattern if block is retained by self (directly or transitively).
原则:如果Block被self直接或间接持有,任何捕获
self
的Block都必须使用weak-strong模式。

❌ WRONG (Creates retain cycle)

❌ 错误写法(产生循环引用)

objc
[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!
}];
objc
[self.networkManager GET:@"url" success:^(id response) {
    self.data = response;  // self被Block持有
    [self updateUI];       // Block被操作持有
} failure:^(NSError *error) {
    [self handleError:error];  // 循环引用产生!
}];

✅ CORRECT (Breaks the cycle)

✅ 正确写法(打破循环引用)

objc
__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];
    }
}];
objc
__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];
    }
}];

Why this works

工作原理

  1. __weak typeof(self) weakSelf = self;
    creates a weak reference outside the block
  2. Block captures weakSelf (weak reference), not self (strong reference)
  3. When block executes, convert to strongSelf (temporary strong ref)
  4. Check if strongSelf is nil (object was deallocated)
  5. Use strongSelf for the duration of the block
  6. strongSelf released when block exits → No cycle
  1. __weak typeof(self) weakSelf = self;
    在Block外部创建弱引用
  2. Block捕获weakSelf(弱引用),而非self(强引用)
  3. Block执行时,转换为strongSelf(临时强引用)
  4. 检查strongSelf是否为nil(对象已被释放)
  5. 在Block执行期间使用strongSelf
  6. Block退出时释放strongSelf → 无循环引用

Important details

重要细节

  • Declare weakSelf OUTSIDE the block, not inside
  • Use
    typeof(self)
    for type safety (works in both ARC and non-ARC)
  • Guard condition MUST use
    if (strongSelf)
    , not just declare it
  • Never use direct
    self
    inside the block once weakSelf is declared
  • Apply to EVERY block that captures self
  • ANY block that captures
    self
    must use weak-strong pattern
    • This includes:
      [self method]
      ,
      self.property
      ,
      self->ivar
    • Property access (
      self.property = value
      ) captures self just like method calls
  • Blocks passed to frameworks:
    • If framework documentation says 'block is called asynchronously' → Use weak-strong pattern (framework retains the block)
    • If framework documentation says 'block is called immediately' → Still safe to use weak-strong (better practice)
    • If unsure about framework behavior → Always use weak-strong (doesn't hurt)
  • 在Block外部声明weakSelf,而非内部
  • 使用
    typeof(self)
    保证类型安全(在ARC和非ARC环境均有效)
  • 必须使用
    if (strongSelf)
    作为防护条件,不能仅声明strongSelf
  • 声明weakSelf后,Block内部绝不能直接使用
    self
  • 对所有捕获self的Block应用此模式
  • 任何捕获
    self
    的Block都必须使用weak-strong模式
    • 包括:
      [self method]
      ,
      self.property
      ,
      self->ivar
    • 属性访问(
      self.property = value
      )与方法调用一样会捕获self
  • 传递给框架的Block:
    • 如果框架文档说明'Block会被异步调用' → 使用weak-strong模式(框架会持有Block)
    • 如果框架文档说明'Block会被立即调用' → 仍建议使用weak-strong(最佳实践)
    • 如果不确定框架行为 → 始终使用weak-strong(无负面影响)

Capturing variables (avoiding indirect self references)

捕获变量(避免间接self引用)

objc
// ✅ 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!
        });
    }
}];
When nesting blocks, extract simple values first, then pass them to the inner block. This avoids creating an indirect capture of self through property access.
Time cost 30 seconds per block

objc
// ✅ 安全:捕获从self中提取的简单值
__weak typeof(self) weakSelf = self;
[self.manager fetch:^(id response) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        NSString *name = strongSelf.name;  // 提取值
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Name: %@", name);  // 捕获的是字符串,而非self
        });
    }
}];

// ❌ 错误:在嵌套Block中直接捕获属性
__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);  // 再次捕获了strongSelf!
        });
    }
}];
嵌套Block时,先提取简单值,再传递给内部Block。这样可避免通过属性访问间接捕获self。
时间成本:每个Block约30秒

Pattern 2: Hidden self in Macros

模式2:宏中的隐式self

PRINCIPLE Macros like NSAssert, NSLog, and string formatting can secretly capture self. You must check them.
原则:NSAssert、NSLog和字符串格式化等宏会隐式捕获self,必须对其进行检查。

❌ WRONG (NSAssert captures self)

❌ 错误写法(NSAssert捕获了self)

objc
[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
objc
[self.button setTapAction:^{
    NSAssert(self.isValidState, @"状态必须有效");  // self被捕获!
    [self doWork];  // 另一个self引用
}];
// 即使你认为只有[self doWork]捕获了self,泄漏依然存在

✅ CORRECT (Check for hidden captures)

✅ 正确写法(检查隐式捕获)

objc
__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];
    }
}];
objc
__weak typeof(self) weakSelf = self;
[self.button setTapAction:^{
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // NSAssert仍通过strongSelf间接引用self
        NSAssert(strongSelf.isValidState, @"状态必须有效");
        [strongSelf doWork];
    }
}];

Common hidden self references

常见的隐式self引用

  • NSAssert(self.condition, ...)
    → Use strongSelf instead
  • NSLog(@"Value: %@", self.property)
    → Use strongSelf.property
  • NSError *error = [NSError errorWithDomain:@"MyApp" ...]
    → Safe, doesn't capture self
  • String formatting:
    @"Name: %@", self.name
    → Use strongSelf.name
  • Inline conditionals:
    self.flag ? @"yes" : @"no"
    → Use strongSelf.flag
  • 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

How to find them

排查方法

  1. Search block for all instances of
    self.
  2. Mark them:
    [self method]
    ,
    self.property
    ,
    self->ivar
  3. Check if any are inside macro calls (NSAssert, NSLog, etc.)
  4. Replace with strongSelf
Time cost 1 minute per block to audit

  1. 在Block中搜索所有
    self.
    实例
  2. 标记以下内容:
    [self method]
    ,
    self.property
    ,
    self->ivar
  3. 检查它们是否在宏调用(NSAssert、NSLog等)内部
  4. 替换为strongSelf
时间成本:每个Block约1分钟审计时间

Pattern 3: Nested Blocks (Each Needs Guard)

模式3:嵌套Block(每个都需要防护)

PRINCIPLE Nested blocks create a chain: outer block captures self, inner block captures outer block variable (which holds strongSelf), creating a new cycle. Each nested block needs its own weak-strong pattern.
原则:嵌套Block会创建引用链:外部Block捕获self,内部Block捕获外部Block的变量(持有strongSelf),从而产生新的循环引用。每个嵌套Block都需要自己的weak-strong模式。

❌ WRONG (Guarded outer block only)

❌ 错误写法(仅防护外部Block)

objc
__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];
        }];
    }
}];
objc
__weak typeof(self) weakSelf = self;
[self.manager fetchData:^(NSArray *result) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // 内部Block强引用了strongSelf!
        [strongSelf.analytics trackEvent:@"Fetched"
                              completion:^{
            strongSelf.cachedData = result;  // 依然是强引用!
            [strongSelf updateUI];
        }];
    }
}];

✅ CORRECT (Guard every nested block)

✅ 正确写法(防护每个嵌套Block)

objc
__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];
            }
        }];
    }
}];
objc
__weak typeof(self) weakSelf = self;
[self.manager fetchData:^(NSArray *result) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // 为内部Block声明新的弱引用
        __weak typeof(strongSelf) weakSelf2 = strongSelf;

        [strongSelf.analytics trackEvent:@"Fetched"
                              completion:^{
            typeof(strongSelf) strongSelf2 = weakSelf2;
            if (strongSelf2) {
                strongSelf2.cachedData = result;
                [strongSelf2 updateUI];
            }
        }];
    }
}];

Why this works

工作原理

  • Each nesting level needs its own weakSelf/strongSelf pair
  • Outer block: weakSelf → strongSelf
  • Inner block: weakSelf2 → strongSelf2
  • Each level is independent and safe
  • 每个嵌套层级都需要自己的weakSelf/strongSelf对
  • 外部Block:weakSelf → strongSelf
  • 内部Block:weakSelf2 → strongSelf2
  • 每个层级独立且安全

Important details

重要细节

  • Don't reuse the same weakSelf variable in nested blocks
  • Each nesting level gets a new pair (weakSelf2, strongSelf2)
  • Guard condition MANDATORY for each level
  • Use consistent naming: weakSelf, weakSelf2, weakSelf3 (for readability)
  • 不要在嵌套Block中重用同一个weakSelf变量
  • 每个嵌套层级使用新的变量对(weakSelf2, strongSelf2)
  • 每个层级都必须添加防护条件
  • 使用统一命名:weakSelf, weakSelf2, weakSelf3(提升可读性)

Common nested block patterns that need Pattern 3

需要应用模式3的常见嵌套Block场景

  • Completion handlers in callbacks
  • dispatch_async(queue, ^{ ... })
  • dispatch_after(time, queue, ^{ ... })
  • [NSTimer scheduledTimerWithTimeInterval:... block:^{ ... }]
  • [UIView animateWithDuration:... animations:^{ ... }]
Each of these is a block that might capture strongSelf, requiring its own weak-strong pattern.
  • 回调中的完成处理程序
  • dispatch_async(queue, ^{ ... })
  • dispatch_after(time, queue, ^{ ... })
  • [NSTimer scheduledTimerWithTimeInterval:... block:^{ ... }]
  • [UIView animateWithDuration:... animations:^{ ... }]
这些Block都可能捕获strongSelf,因此需要各自的weak-strong模式。

Example with dispatch_async

dispatch_async示例

objc
__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];
            }
        });
    }
}];
Time cost 1 minute per nesting level

objc
__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分钟

Pattern 4: Guard Condition Edge Cases

模式4:防护条件边缘情况

PRINCIPLE The guard condition
if (strongSelf)
must be correct. Common mistakes: forgetting the guard, wrong condition, or mixing self and strongSelf.
原则:防护条件
if (strongSelf)
必须正确。常见错误:忘记添加防护、条件错误、混合使用self和strongSelf。

❌ WRONG (Multiple guard failures)

❌ 错误写法(多种防护错误)

objc
__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];
    }
}];
objc
__weak typeof(self) weakSelf = self;
[self.button setTapAction:^{
    typeof(self) strongSelf = weakSelf;
    // 错误1:忘记添加防护条件
    self.counter++;  // 崩溃!self已被释放,访问已释放对象

    // 错误2:存在防护但使用了错误变量
    if (weakSelf) {
        [weakSelf doWork];  // weakSelf是弱引用,可能再次变为nil
    }

    // 错误3:混合使用self和strongSelf
    if (strongSelf) {
        self.flag = YES;  // 使用了self而非strongSelf!
        [strongSelf doWork];
    }
}];

✅ CORRECT (Proper guard and consistent usage)

✅ 正确写法(正确防护与统一使用)

objc
__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
}];
objc
__weak typeof(self) weakSelf = self;
[self.button setTapAction:^{
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // 正确:所有位置使用strongSelf,绝不使用self
        strongSelf.counter++;
        strongSelf.flag = YES;
        [strongSelf doWork];
    }
    // 如果strongSelf为nil,整个Block会优雅跳过
}];

Why this works

工作原理

  1. if (strongSelf)
    checks if object still exists
  2. If it does, strongSelf is a strong reference (safe)
  3. If it doesn't (object deallocated), block skips
  4. Using strongSelf everywhere prevents accidental self references
  1. if (strongSelf)
    检查对象是否仍存在
  2. 如果存在,strongSelf是强引用(安全)
  3. 如果不存在(对象已被释放),Block跳过执行
  4. 统一使用strongSelf可避免意外的self引用

Critical rules (MANDATORY, no exceptions)

强制规则(无例外)

  • ✅ ALWAYS check
    if (strongSelf)
    before using it
  • ✅ ALWAYS use strongSelf inside the if block, NEVER direct self
  • ✅ strongSelf is guaranteed valid for the entire block scope
  • ❌ NEVER use
    if (!strongSelf) return;
    (confuses logic)
  • ❌ NEVER skip the guard to "save code"
  • ❌ NEVER mix weakSelf and strongSelf access
  • ❌ NEVER use strongSelf without guard (GUARANTEED crash)
  • ✅ 使用strongSelf前必须检查
    if (strongSelf)
  • ✅ if代码块内部始终使用strongSelf,绝不直接使用self
  • ✅ strongSelf在整个Block作用域内保证有效
  • ❌ 绝不使用
    if (!strongSelf) return;
    (易混淆逻辑)
  • ❌ 绝不跳过防护条件以"简化代码"
  • ❌ 绝不混合使用weakSelf和strongSelf访问
  • ❌ 绝不未加防护就使用strongSelf(必然崩溃)

What happens if you get it wrong

错误后果

  • No guard: Crashes with "Sending message to deallocated instance"
  • Wrong condition: Object still deallocated, still crashes
  • Mixed self/strongSelf: One accidental self defeats entire pattern
  • Using strongSelf without guard: GUARANTEED crash when object is deallocated
  • 无防护条件:崩溃,错误信息为"向已释放的实例发送消息"
  • 条件错误:对象仍被释放,依然崩溃
  • 混合使用self/strongSelf:一次意外的self引用就会破坏整个模式
  • 未加防护使用strongSelf:对象被释放时必然崩溃

Inside the guard

防护条件内部

objc
if (strongSelf) {
    strongSelf.data1 = value1;
    [strongSelf doWork1];
    [strongSelf doWork2];  // All safe
}
// ❌ WRONG: Using strongSelf after guard ends
strongSelf.data = value2;  // CRASH! Outside guard
objc
if (strongSelf) {
    strongSelf.data1 = value1;
    [strongSelf doWork1];
    [strongSelf doWork2];  // 全部安全
}
// ❌ 错误:防护条件结束后使用strongSelf
strongSelf.data = value2;  // 崩溃!在防护外部

What NOT to do

严禁操作

objc
// ❌ 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
}
Time cost 10 seconds per block to verify guard is correct

objc
// ❌ 严禁:未加防护使用strongSelf必然崩溃
typeof(self) strongSelf = weakSelf;
strongSelf.data = value;  // 如果weakSelf为nil,必然崩溃!

// ✅ 强制:使用strongSelf前必须添加防护
if (strongSelf) {
    strongSelf.data = value;  // 安全
}
时间成本:每个Block约10秒验证防护条件正确性

Quick Reference Table

快速参考表

IssueCheckFix
Memory not returning to baselineDoes ViewController still exist in Memory Graph?Apply Pattern 1 (weak-strong)
Crash: "message to deallocated instance"Is guard condition missing or wrong?Apply Pattern 4 (correct guard)
Applied weak-strong but still leakingAre ALL self references using strongSelf?Check for mixed self/strongSelf
Block contains NSAssert or NSLogDo they reference self?Apply Pattern 2 (use strongSelf in macros)
Nested blocksIs weak-strong applied to EACH level?Apply Pattern 3 (guard every block)
Not sure if block creates cycleIs block assigned to self or self.property?If yes, apply Pattern 1

问题检查项修复方案
内存未回到基准水平内存图中ViewController是否仍存在?应用模式1(weak-strong)
崩溃:"向已释放的实例发送消息"防护条件是否缺失或错误?应用模式4(正确防护)
已应用weak-strong但仍泄漏所有self引用是否都使用了strongSelf?检查是否混合使用self/strongSelf
Block包含NSAssert或NSLog它们是否引用了self?应用模式2(宏中使用strongSelf)
嵌套Block是否每个层级都应用了weak-strong?应用模式3(防护每个Block)
不确定Block是否产生循环引用Block是否被赋值给self或self.property?如果是,应用模式1

When You're Stuck After 30 Minutes

30分钟后仍无法解决的情况

If you've spent >30 minutes and the leak still exists:
如果你已花费超过30分钟但泄漏仍存在:

STOP. You either

停止操作。你可能

  1. Skipped a mandatory diagnostic step (most common)
  2. Didn't apply weak-strong to ALL blocks (nested blocks missed)
  3. Have hidden self reference (NSAssert, NSLog, string format)
  4. Applied pattern but mixed in direct
    self
    references
  5. Have a different kind of leak (not block-related)
  1. 跳过了某个强制诊断步骤(最常见)
  2. 未对所有Block应用weak-strong(遗漏了嵌套Block)
  3. 存在隐式self引用(NSAssert、NSLog、字符串格式化)
  4. 应用了模式但混合使用了直接
    self
    引用
  5. 存在其他类型的泄漏(与Block无关)

MANDATORY checklist before claiming "skill didn't work"

声称"方法无效"前的强制检查清单

  • I ran all 4 diagnostic blocks (Allocations, Memory Graph, block search, self reference search)
  • I confirmed memory doesn't return to baseline in Instruments
  • I confirmed ViewController is still allocated (not deallocated)
  • I traced the retention chain (what's holding the ViewController?)
  • I found ALL blocks that capture self (global search:
    [self
    in the file)
  • I checked for hidden self references (NSAssert, NSLog, string formatting)
  • I applied weak-strong pattern to outer blocks
  • I applied weak-strong pattern to nested blocks (every nesting level)
  • I verified NO direct
    self
    references remain (only strongSelf)
  • I ran Instruments again and memory returned to baseline
  • I tested on real device, not just simulator
  • I cleared Xcode derived data between runs
  • 我已执行所有4个诊断步骤(Allocations、内存图、Block搜索、self引用搜索)
  • 我已确认Instruments中内存未回到基准水平
  • 我已确认ViewController仍未被释放(未完成dealloc)
  • 我已追踪了引用链(是什么持有了ViewController?)
  • 我已找到所有捕获self的Block(全局搜索文件中的
    [self
  • 我已检查了隐式self引用(NSAssert、NSLog、字符串格式化)
  • 我已对外部Block应用了weak-strong模式
  • 我已对嵌套Block应用了weak-strong模式(每个层级)
  • 我已验证不存在直接
    self
    引用(仅使用strongSelf)
  • 我已重新运行Instruments并确认内存回到基准水平
  • 我已在真实设备上测试,而非仅模拟器
  • 我已在多次运行之间清除了Xcode派生数据

If ALL boxes are checked and still leaking

如果所有检查项均已完成但仍泄漏

  • You have a non-block leak (Core Data, timer, delegate, notification)
  • Use Instruments > Leaks instrument to identify the actual cycle
  • Profile for 2-3 minutes: open screen, close screen, repeat 5 times
  • Look at "Leaks" panel—it shows exactly what's not being released
  • Time cost: 15-30 minutes to identify the real culprit
  • 你遇到的是非Block类型的泄漏(Core Data、定时器、代理、通知)
  • 使用Instruments > Leaks工具识别真正的循环引用
  • 分析2-3分钟:打开页面、关闭页面,重复5次
  • 查看"Leaks"面板——它会准确显示未被释放的对象
  • 时间成本:15-30分钟识别真正的问题根源

If you identify it's NOT a block leak

如果确认不是Block泄漏

  • Do not rationalize: "Maybe blocks are fine, I'll ship anyway"
  • Find the actual cycle (could be delegate, timer, property observer, notification)
  • Fix the real issue, not a false positive
  • 不要合理化解释:"也许Block没问题,我直接发布"
  • 找到真正的循环引用(可能是代理、定时器、属性观察者、通知)
  • 修复真正的问题,而非误判的问题

Time cost transparency

时间成本透明化

  • Pattern 1: 30 seconds per block
  • Pattern 2: 1 minute per block (audit for hidden self)
  • Pattern 3: 1 minute per nesting level
  • Nested diagnostics if stuck: 15-30 minutes
  • Total for straightforward leak: 5-10 minutes

  • 模式1:每个Block约30秒
  • 模式2:每个Block约1分钟(审计隐式self)
  • 模式3:每个嵌套层级约1分钟
  • 陷入困境时的嵌套诊断:15-30分钟
  • 简单泄漏修复总耗时:5-10分钟

Common Mistakes

常见错误

Forgetting the guard condition
  • strongSelf.property = value;
    without
    if (strongSelf)
  • Crash when object is deallocated
  • Fix: ALWAYS use
    if (strongSelf) { ... }
Mixing self and strongSelf in same block
  • self.flag = YES; [strongSelf doWork];
  • One direct
    self
    reference defeats the entire pattern
  • Fix: ONLY use strongSelf inside the block
Applying pattern to outer block only
  • Nested block still captures strongSelf strongly
  • Still leaks
  • Fix: Apply weak-strong to EVERY block
Using __unsafe_unretained as "workaround"
  • ❌ FORBIDDEN pattern—unsafe and crashes
  • Creates crashes when object is deallocated
  • Not a solution, worse problem
  • Fix: Use weak-strong pattern instead
Not checking for hidden self references
  • NSLog(@"Value: %@", self.property)
    in a block
  • Leak still exists even after applying weak-strong
  • Fix: Audit for NSAssert, NSLog, string formatting
Rationalizing "it's a small leak"
  • Single block leak might be 100KB
  • After 50 screens, accumulates to 5MB
  • Eventually app crashes from memory pressure
  • Fix: Fix every block leak, don't rationalize
Assuming blocks in system frameworks are safe
  • UIView animations, AFNetworking, dispatch, timers
  • ALL can retain blocks that reference self
  • Fix: Apply weak-strong pattern regardless of source
Testing only in simulator
  • Simulator memory pressure is different
  • Leak might not appear until real device under load
  • Fix: Test on real device, oldest supported model
忘记添加防护条件
  • strongSelf.property = value;
    未加
    if (strongSelf)
  • 对象被释放时崩溃
  • 修复:始终使用
    if (strongSelf) { ... }
在同一个Block中混合使用self和strongSelf
  • self.flag = YES; [strongSelf doWork];
  • 一次直接的
    self
    引用就会破坏整个模式
  • 修复:Block内部仅使用strongSelf
仅对外部Block应用模式
  • 嵌套Block仍强引用strongSelf
  • 泄漏依然存在
  • 修复:对每个Block应用weak-strong
使用__unsafe_unretained作为"变通方案"
  • ❌ 严禁使用此模式——不安全且会崩溃
  • 对象被释放时会崩溃
  • 这不是解决方案,而是更严重的问题
  • 修复:改用weak-strong模式
未检查隐式self引用
  • Block中存在
    NSLog(@"Value: %@", self.property)
  • 应用weak-strong后泄漏依然存在
  • 修复:审计NSAssert、NSLog、字符串格式化
合理化解释"这是小泄漏"
  • 单个Block泄漏可能仅100KB
  • 50个页面后累积为5MB
  • 最终应用会因内存压力崩溃
  • 修复:修复所有Block泄漏,不要合理化
假设系统框架中的Block是安全的
  • UIView动画、AFNetworking、dispatch、定时器
  • 所有这些都可能持有引用self的Block
  • 修复:无论来源如何,都应用weak-strong模式
仅在模拟器中测试
  • 模拟器的内存压力与真实设备不同
  • 泄漏可能仅在真实设备负载下出现
  • 修复:在真实设备(支持的最旧机型)上测试

Real-World Impact

实际业务影响

Before Block memory leak debugging 2-3 hours per issue
  • Run Allocations, not sure what to look at
  • Search everywhere, no clear diagnostic path
  • Try random fixes, hope one works
  • Ship anyway after sunk cost fallacy
  • Customer reports crashes or slowdown
After 5-10 minutes with systematic diagnosis
  • Run Allocations, confirm memory not returning to baseline
  • Memory Graph shows exactly what's retained
  • Find all blocks capturing self with global search
  • Apply weak-strong pattern (30 seconds per block)
  • Test in Instruments, memory returns to baseline
  • Done
Key insight Block retain cycles are 100% preventable with weak-strong pattern. There are no exceptions, no "special cases" where strong self is acceptable.

Last Updated: 2025-11-30 Status: TDD-tested with pressure scenarios Framework: Objective-C, blocks (closure), ARC
修复前:Block内存泄漏调试每个问题需2-3小时
  • 运行Allocations,但不知道该关注什么
  • 到处搜索,没有清晰的诊断路径
  • 尝试随机修复,寄希望于其中一个有效
  • 因沉没成本谬误选择发布
  • 用户反馈崩溃或卡顿
修复后:系统化诊断仅需5-10分钟
  • 运行Allocations,确认内存未回到基准水平
  • 内存图准确显示持有对象
  • 通过全局搜索找到所有捕获self的Block
  • 应用weak-strong模式(每个Block约30秒)
  • 重新运行Instruments,确认内存回到基准水平
  • 完成修复
核心洞察:Block循环引用通过weak-strong模式可100%预防。不存在例外情况,没有任何场景下使用strong self是可接受的。

最后更新:2025-11-30 状态:通过压力场景的TDD测试 框架:Objective-C, blocks(闭包), ARC