axiom-objc-block-retain-cycles by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-objc-block-retain-cyclesBlock 循环引用是 Objective-C 内存泄漏的首要原因。当一个 block 捕获了 self 并且被存储在该对象上(直接或通过操作/请求间接存储)时,你就创建了一个循环引用:self → block → self。核心原则 90% 的 block 内存泄漏源于缺失或错误应用的弱引用-强引用模式,而非真正的 Apple 框架错误。
如果你看到以下任何情况,请怀疑是 block 循环引用,而非其他问题:
关键区别 Block 循环引用会悄无声息地累积。单个循环可能只有 100KB,但在查看了 50 个屏幕后,你将拥有 5MB 的无效内存。强制要求:修复后在真实设备(支持的最旧型号)上测试,而不仅仅是模拟器。
始终首先运行这些(在更改代码之前):
// 1. 使用 Allocations 工具识别泄漏
// 在 Xcode 中:Xcode > Open Developer Tool > Instruments
// 选择 Allocations 模板
// 执行一个操作(打开/关闭包含可疑 block 的屏幕)
// 检查内存是否未恢复到基线水平
// 记录:"Memory baseline: X MB, after action: Y MB, still allocated: Z objects"
// 2. 使用 Memory Debugger 追踪循环
// 运行应用,在可疑代码位置暂停
// Debug > Debug Memory Graph
// 搜索应该被释放的视图控制器
// 右键点击 > Show memory graph
// 查找指向 self 的箭头(循环)
// 记录:"ViewController retained by: [operation/block/property]"
// 3. 检查 block 是否被赋值给 self 或 self 的属性
// 搜索:setBlock:, completion:, handler:, callback:
// 检查:block 是否存储在 self.property 中?
// 检查:block 是否被传递给会保留它的东西(网络操作)?
// 记录:"Block assigned to: [property or operation]"
// 4. 在 block 中搜索 self 引用
// 查找:[self method], self.property, self-> 访问
// 查找隐藏的 self 引用:
// - NSLog(@"Value: %@", self.property)
// - NSAssert(self.isValid, @"message")
// - 格式字符串:@"Name: %@", self.name
// 记录:"self references found in block: [list]"
// 示例输出:
// 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
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在更改任何代码之前,你必须确认以下情况之一:
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 开始(弱引用-强引用基础)
然后是模式 2(宏中隐藏的 self)
然后是模式 3(嵌套 Blocks)
然后是模式 4(保护条件边缘情况)
原则 任何捕获 self 的 block,如果该 block 被 self 保留(直接或传递性地),则必须使用弱引用-强引用模式。
[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];
}
}];
__weak typeof(self) weakSelf = self; 在 block 外部创建一个弱引用typeof(self) 以确保类型安全(在 ARC 和非 ARC 中都有效)if (strongSelf),而不仅仅是声明它selfself 的 block 都必须使用弱引用-强引用模式
[self method]、self.property、self->ivarself.property = value)捕获 self,就像方法调用一样// ✅ 安全:捕获从 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); // 捕获了 STRING,而非 self
});
}
}];
// ❌ 错误:在嵌套 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); // 再次捕获 strongSelf!
});
}
}];
当嵌套 blocks 时,首先提取简单值,然后将它们传递给内部 block。这避免了通过属性访问间接捕获 self。
时间成本 每个 block 30 秒
原则 像 NSAssert、NSLog 和字符串格式化这样的宏可能会秘密地捕获 self。你必须检查它们。
[self.button setTapAction:^{
NSAssert(self.isValidState, @"State must be valid"); // self captured!
[self doWork]; // Another self reference
}];
// 即使你认为只有 [self doWork] 捕获了 self,泄漏仍然存在
__weak typeof(self) weakSelf = self;
[self.button setTapAction:^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
// NSAssert 仍然通过 strongSelf 间接引用 self
NSAssert(strongSelf.isValidState, @"State must be valid");
[strongSelf doWork];
}
}];
NSAssert(self.condition, ...) → 使用 strongSelf 替代NSLog(@"Value: %@", self.property) → 使用 strongSelf.propertyNSError *error = [NSError errorWithDomain:@"MyApp" ...] → 安全,不捕获 self@"Name: %@", self.name → 使用 strongSelf.nameself.flag ? @"yes" : @"no" → 使用 strongSelf.flagself. 的实例[self method]、self.property、self->ivar时间成本 每个 block 审核 1 分钟
原则 嵌套 blocks 创建了一个链:外部 block 捕获 self,内部 block 捕获外部 block 变量(该变量持有 strongSelf),从而创建了一个新的循环。每个嵌套 block 都需要自己的弱引用-强引用模式。
__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) {
// 为内部 block 声明新的弱引用
__weak typeof(strongSelf) weakSelf2 = strongSelf;
[strongSelf.analytics trackEvent:@"Fetched"
completion:^{
typeof(strongSelf) strongSelf2 = weakSelf2;
if (strongSelf2) {
strongSelf2.cachedData = result;
[strongSelf2 updateUI];
}
}];
}
}];
dispatch_async(queue, ^{ ... })dispatch_after(time, queue, ^{ ... })[NSTimer scheduledTimerWithTimeInterval:... block:^{ ... }][UIView animateWithDuration:... animations:^{ ... }]这些中的每一个都是一个可能捕获 strongSelf 的 block,需要其自己的弱引用-强引用模式。
__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 分钟
原则 保护条件 if (strongSelf) 必须是正确的。常见错误:忘记保护、条件错误,或混合使用 self 和 strongSelf。
__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];
}
}];
__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 优雅地跳过
}];
if (strongSelf) 检查对象是否仍然存在if (strongSelf)if (!strongSelf) return;(混淆逻辑)if (strongSelf) {
strongSelf.data1 = value1;
[strongSelf doWork1];
[strongSelf doWork2]; // 全部安全
}
// ❌ 错误:在保护结束后使用 strongSelf
strongSelf.data = value2; // 崩溃!在保护外部
// ❌ 禁止:没有保护的 strongSelf 保证崩溃
typeof(self) strongSelf = weakSelf;
strongSelf.data = value; // 如果 weakSelf 是 nil,则崩溃!
// ✅ 强制:在使用 strongSelf 之前始终保护
if (strongSelf) {
strongSelf.data = value; // 安全
}
时间成本 每个 block 验证保护是否正确需要 10 秒
| 问题 | 检查 | 修复 |
|---|---|---|
| 内存未恢复到基线水平 | ViewController 在 Memory Graph 中是否仍然存在? | 应用模式 1(弱引用-强引用) |
| 崩溃:"message to deallocated instance" | 保护条件是否缺失或错误? | 应用模式 4(正确的保护) |
| 应用了弱引用-强引用但仍然泄漏 | 是否所有 self 引用都使用了 strongSelf? | 检查是否混合使用了 self/strongSelf |
| Block 包含 NSAssert 或 NSLog | 它们是否引用了 self? | 应用模式 2(在宏中使用 strongSelf) |
| 嵌套 blocks | 弱引用-强引用是否应用于每个级别? | 应用模式 3(保护每一个 block) |
| 不确定 block 是否创建循环 | Block 是否被赋值给 self 或 self.property? | 如果是,应用模式 1 |
如果你已经花费了 >30 分钟并且泄漏仍然存在:
self 引用[self)self 引用保留(只有 strongSelf)❌ 忘记保护条件
strongSelf.property = value; 没有 if (strongSelf)if (strongSelf) { ... }❌ 在同一 block 中混合使用 self 和 strongSelf
self.flag = YES; [strongSelf doWork];self 引用会破坏整个模式❌ 仅将模式应用于外部 block
❌ 使用 __unsafe_unretained 作为"变通方法"
❌ 不检查隐藏的 self 引用
NSLog(@"Value: %@", self.property)❌ 合理化"这是一个小泄漏"
❌ 假设系统框架中的 blocks 是安全的
❌ 仅在模拟器中测试
之前 Block 内存泄漏调试每个问题 2-3 小时
之后 通过系统化诊断 5-10 分钟
关键见解 Block 循环引用通过弱引用-强引用模式是 100% 可预防的。没有例外,没有"特殊情况"可以接受强引用 self。
最后更新 : 2025-11-30 状态 : 使用压力场景进行 TDD 测试 框架 : Objective-C, blocks (闭包), ARC
每周安装数
87
仓库
GitHub Stars
590
首次出现
Jan 21, 2026
安全审计
安装在
opencode72
codex67
claude-code66
gemini-cli65
cursor65
github-copilot62
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.
If you see ANY of these, suspect a block retain cycle, not something else:
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.
ALWAYS run these FIRST (before changing code):
// 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
Before changing ANY code, you must confirm ONE of these:
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?
Always start with Pattern 1 (Weak-Strong Basics)
Then Pattern 2 (Hidden self in Macros)
Then Pattern 3 (Nested Blocks)
Then Pattern 4 (Guard Condition Edge Cases)
PRINCIPLE Any block that captures self must use weak-strong pattern if block is retained by self (directly or transitively).
[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];
}
}];
__weak typeof(self) weakSelf = self; creates a weak reference outside the blocktypeof(self) for type safety (works in both ARC and non-ARC)if (strongSelf), not just declare itself inside the block once weakSelf is declaredself must use weak-strong pattern
[self method], self.property, self->ivarself.property = value) captures self just like method calls// ✅ 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
PRINCIPLE Macros like NSAssert, NSLog, and string formatting can secretly capture self. You must check them.
[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];
}
}];
NSAssert(self.condition, ...) → Use strongSelf insteadNSLog(@"Value: %@", self.property) → Use strongSelf.propertyNSError *error = [NSError errorWithDomain:@"MyApp" ...] → Safe, doesn't capture self@"Name: %@", self.name → Use strongSelf.nameself.flag ? @"yes" : @"no" → Use strongSelf.flagself.[self method], self.property, self->ivarTime cost 1 minute per block to audit
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.
__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];
}
}];
}
}];
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.
__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
PRINCIPLE The guard condition if (strongSelf) must be correct. Common mistakes: forgetting the guard, wrong condition, or mixing self and 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
}];
if (strongSelf) checks if object still existsif (strongSelf) before using itif (!strongSelf) return; (confuses logic)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
}
Time cost 10 seconds per block to verify guard is correct
| Issue | Check | Fix |
|---|---|---|
| Memory not returning to baseline | Does 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 leaking | Are ALL self references using strongSelf? | Check for mixed self/strongSelf |
| Block contains NSAssert or NSLog | Do they reference self? | Apply Pattern 2 (use strongSelf in macros) |
| Nested blocks | Is weak-strong applied to EACH level? | Apply Pattern 3 (guard every block) |
| Not sure if block creates cycle | Is block assigned to self or self.property? | If yes, apply Pattern 1 |
If you've spent >30 minutes and the leak still exists:
self references[self in the file)self references remain (only strongSelf)❌ Forgetting the guard condition
strongSelf.property = value; without if (strongSelf)if (strongSelf) { ... }❌ Mixing self and strongSelf in same block
self.flag = YES; [strongSelf doWork];self reference defeats the entire pattern❌ Applying pattern to outer block only
❌ Using __unsafe_unretained as "workaround"
❌ Not checking for hidden self references
NSLog(@"Value: %@", self.property) in a block❌ Rationalizing "it's a small leak"
❌ Assuming blocks in system frameworks are safe
❌ Testing only in simulator
Before Block memory leak debugging 2-3 hours per issue
After 5-10 minutes with systematic diagnosis
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
Weekly Installs
87
Repository
GitHub Stars
590
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode72
codex67
claude-code66
gemini-cli65
cursor65
github-copilot62
TanStack Query v5 完全指南:React 数据管理、乐观更新、离线支持
2,500 周安装