docs: image url

This commit is contained in:
FantasticLBP
2026-01-02 10:28:57 +08:00
parent 7ac7513900
commit 7843661458
29 changed files with 719 additions and 719 deletions

View File

@@ -21,7 +21,7 @@
假设我们计算有`128MB`内存程序A需要`10MB`程序B需要`100MB`程序C需要`20MB`。如果我们需要同时运行程序A和B那么比较直接的做法是将内存的`前10MB`分配给程序A`10MB~110MB`分配给B。
<img src="./../assets/IOSMemoryWithPhysisMemory.png" style="zoom:20%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/IOSMemoryWithPhysisMemory.png" style="zoom:20%" />
但存在以下问题:
@@ -51,7 +51,7 @@
比如A需要`10M`,就假设有`0x00000000``0x00A00000`大小的虚拟空间,然后从物理内存分配一个相同大小的空间,比如是`0x00100000``0x00B00000`。操作系统来设置这个映射函数,实际的地址转换由硬件完成。如果越界,硬件就会判断这是一个非法访问,拒绝这个地址请求,并上报操作系统或监控程序。
<img src="./../assets/iOSMemoryParagraph.png" style="zoom:20%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOSMemoryParagraph.png" style="zoom:20%" />
这样一来利用**分段**的方式可以解决之前的**地址空间不隔离**和**程序运行地址不确定**
@@ -78,13 +78,13 @@
<img src="./../assets/iOSMemoryPage.png" style="zoom:20%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOSMemoryPage.png" style="zoom:20%" />
保护页也是页映射的目的之一,简单地说就是每个页可以设置权限属性,谁可以修改,谁可以访问,而且只有操作系统有权修改这些属性,那么操作系统就可以做到保护自己和保护进程。
虚拟存储的实现需要硬件支持几乎所有CPU都采用称为**MMU的部件来进行页的映射**
<img src="./../assets/iOSVirtualMemoryVisitPhysisMemoryViaMMU.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOSVirtualMemoryVisitPhysisMemoryViaMMU.png" style="zoom:40%" />
在页映射模式下,`CPU`发出的是`Virtual Address`,即我们程序看到的是`虚拟地址`。经过`MMU`转换以后就变成了`Physical Address`。一般`MMU`集成在`CPU`内部,不会以独立的部件存在。
@@ -102,7 +102,7 @@ NSTimer、CADisplayLink 的 基础 API `[NSTimer scheduledTimersWithTimeInterval
栈、堆、BSS、数据段、代码段
<img src="./../assets/iOS-MemoryLayout.png" style="zoom:50%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOS-MemoryLayout.png" style="zoom:50%" />
@@ -131,7 +131,7 @@ BSS段bss segment通常用来存储程序中未被初始化的全局变
代码段code segment编译之后的代码。通常是指用来存储程序可执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定并且内存区域通常属于只读某些架构也允许代码段为可写即允许修改程序。
![内存](./../assets/ram.png)
![内存](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ram.png)
上 Demo 验证
@@ -210,7 +210,7 @@ Tagged Pointer 格式下,指针值不再是有效抵制,而是表示值。
当对 TaggedPointer 数据调用方法的时候objc_msgSend 能识别出如果是 Tagged Pointer比如 NSNumber 的 intValue 方法,直接从指针提取数据,节省了调用开销。所以使用了 TaggedPointer 技术不仅节约内存空间,又能提高方法查找速度。
<img src="./../assets/TaggedPointerStructure.png" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerStructure.png" />
@@ -311,7 +311,7 @@ Tagged Pointer 也就是一个伪指针,对象的指针中存储的数据变
Demo
<img src="./../assets/TaggedPointerSaveMemoryUsage.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerSaveMemoryUsage.png" style="zoom:30%" />
在 64 位的 cpu 下,如果使用 Tagged Pointer 技术的话,则 NSNumber 对象的值直接存储在了指针中系统不会为其在堆上分配内存可以节省很多内存开销。此时NSNumber 对象的指针中存储的数据变成了 Tag + Data 的形式Tag 为特殊标记用于区分NSNumber、NSDate、NSString 等小内存对象的类型Data 为具体的值)。这样使用一个 NSNumber 对象只需要在栈中开辟 8 个字节的指针内存。当栈中 8 个字节的指针内存不够存储数据时,才会再将 NSNumber 对象存储到堆中
@@ -331,7 +331,7 @@ Demo
路径Xcode - Edit Scheme - Run - Arguments - Environment Variables - 添加环境变量 `OBJC_DISABLE_TAG_OBFUSCATION` 设置为 YES 即可。
<img src="./../assets/XcodeDisableTaggedPointerConfuse.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/XcodeDisableTaggedPointerConfuse.png" style="zoom:30%" />
@@ -500,7 +500,7 @@ _objc_decodeTaggedPointer_noPermute_withObfuscator(const void * _Nullable ptr, u
}
```
<img src="./../assets/TaggedPointerDecode.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerDecode.png" style="zoom:40%" />
@@ -508,7 +508,7 @@ _objc_decodeTaggedPointer_noPermute_withObfuscator(const void * _Nullable ptr, u
#### Tagged Pointer 与 isa
<img src="./../assets/TaggedPointerNSNumberDemo.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerNSNumberDemo.png" style="zoom:40%" />
通过参考 objc 源码,针对对象指针进行解密后发现:
@@ -539,7 +539,7 @@ b 也就是11二进制为 `1011`Tagged Pointer 中iOS 侧第一位是 T
3 区分数据类型。具体是什么数据类型,继续做个实验看看
<img src="./../assets/TaggedPointerDataTypeDemo.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerDataTypeDemo.png" style="zoom:40%" />
@@ -560,7 +560,7 @@ b 也就是11二进制为 `1011`Tagged Pointer 中iOS 侧第一位是 T
Objc 源码中NSInteger、NSUInteger 都是别名。初始化 NSNumber 的时候用的是 `NSNumber numberWithInteger:<#(NSInteger)#>`
<img src="./../assets/ObjcNSIntegerIsLong.png" style="zoom:30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ObjcNSIntegerIsLong.png" style="zoom:30%" >
@@ -638,7 +638,7 @@ Tagged Pointer 如何区分是较小的对象,比如 NSString、NSDate、NSNum
验证下
<img src="./../assets/TaggedPointerKind.png" style="zoom:30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerKind.png" style="zoom:30%" >
可以看到:
@@ -651,11 +651,11 @@ Tagged Pointer 如何区分是较小的对象,比如 NSString、NSDate、NSNum
下面是针对 double 包装成 NSNumber 的 Tagged Pointer 指针结构拆分:
<img src="./../assets/NSNumberTaggedPointerStructure.png" style="zoom:90%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSNumberTaggedPointerStructure.png" style="zoom:90%" />
<img src="./../assets/NSNumberAddressToBinary.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSNumberAddressToBinary.png" style="zoom:30%" />
@@ -861,7 +861,7 @@ Demo1
运行该代码会 Crash报错信息如下
<img src="./../assets/TaggedPointerCrash.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerCrash.png" style="zoom:30%" />
@@ -884,11 +884,11 @@ Demo1
改法1将 property 改为 **atomic** 修饰的。
<img src="./../assets/TaggedPointerCrashFix1.png" style="zoom:50%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerCrashFix1.png" style="zoom:50%" />
改法2对 name 加锁
<img src="./../assets/TaggedPointerCrashFix2.png" style="zoom:50%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerCrashFix2.png" style="zoom:50%" />
@@ -896,7 +896,7 @@ Demo1
Demo2
<img src="./../assets/TaggedPointerWillNotCrash.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerWillNotCrash.png" style="zoom:30%" />
@@ -952,7 +952,7 @@ NSString、NSMutableString 继承关系如下:
<img src="./../assets/NSStringClassClusterAndTaggedPointer.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSStringClassClusterAndTaggedPointer.png" style="zoom:30%" />
通过 `[[NSString alloc] initWithString:@"**"]` 方式创建的 NSString 字符串
@@ -1022,27 +1022,27 @@ iOS 中使用引用计数来管理 OC 对象的内存。一个新创建的 OC
调用 retain/copy 会让 OC 对象的引用计数 +1调用 release 会让 OC 对象的引用计数 -1。
<img src="./../assets/PersonAndCatMRCIssue.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue.png" style="30%" >
可以看到,如果我们提前将 cat 释放了,那后续赋值给 person 的 _cat 成员变量就没法使用了,因为已经释放了,否则就会造成 `EXC_BAD_ACCESS`。这样子太不灵活了。需要改进下:
调用 setCat 的时候,对传入的 cat 进行 retain引用计数 +1谁用谁管理同样的最后在 Person 对象释放的时候对 cat 进行 release引用计数 -1.
<img src="./../assets/PersonAndCatMRCIssue1.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue1.png" style="30%" >
但上面的代码不完美,还是存在问题。假设 cat1、cat2 2个对象当作参数调用2次 setCat 方法,如果 setCat 方法内部不做处理会导致第2次调用 setCat 后,之前调用时传入的 cat1 会无法释放。
<img src="./../assets/PersonAndCatMRCIssue2.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue2.png" style="30%" >
修改下。调用 setCat 方法时,对之前的 _cat 调用 release对旧的引用计数-1再对新传入的对象调用 retain让引用计数+1然后赋值
<img src="./../assets/PersonAndCatMRCIssue3.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue3.png" style="30%" >
上面的代码还是存在问题,会造成僵尸对象问题
<img src="./../assets/PersonAndCatMRCIssue4.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue4.png" style="30%" >
分析下 cat 的引用计数情况:
@@ -1054,7 +1054,7 @@ iOS 中使用引用计数来管理 OC 对象的内存。一个新创建的 OC
改进
<img src="./../assets/PersonAndCatMRCIssue5.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue5.png" style="30%" >
@@ -1217,7 +1217,7 @@ NSLog(@"array2 --- %zd", array2.retainCount);
Demo3
<img src="./../assets/NSMutableArrayCopyIssue.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSMutableArrayCopyIssue.png" style="zoom:30%" />
会发现发生了 crash。问题是因为
@@ -1605,7 +1605,7 @@ class StripedMap {
### 引用计数表
<img src="./../assets/ReferenceCountintStructure.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ReferenceCountintStructure.png" style="zoom:40%" />
@@ -1613,7 +1613,7 @@ class StripedMap {
weak_table_t 结构如下:
<img src="./../assets/WeakReferenceCountTable.png" style="zoom:70%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WeakReferenceCountTable.png" style="zoom:70%" />
```c++
#define WEAK_INLINE_COUNT 4
@@ -1708,7 +1708,7 @@ objc_destoryWeak(&obj);
上 Demo
<img src="./../assets/ObjcStoreWeakWhenUseWeak.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ObjcStoreWeakWhenUseWeak.png" style="zoom:30%" />
可以看到当一个 weak 指针被赋值的时候,底层调用了 `objc_initWeak`,跟踪查看 objc 源码
@@ -2549,7 +2549,7 @@ void sel_init(size_t selrefCount){
在 gone 处加断点,利用 runtime 查看类中的方法信息
![](./../assets/cxx_destructDemo1.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/cxx_destructDemo1.png)
发现存在 `.cxx_destruct` 方法。
@@ -2584,7 +2584,7 @@ void sel_init(size_t selrefCount){
@end
```
![](./../assets/cxx_destructdemo3.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/cxx_destructdemo3.png)
Tips@property 会自动生成成员变量,另外类后面加 `{}` 在内部也可以加成员变量,假如成员变量是对象类型,比如 NSString则叫实例变量。
@@ -2598,7 +2598,7 @@ Tips@property 会自动生成成员变量,另外类后面加 `{}` 在内部
在 gone 的地方加断点,输入 `watchpoint set variable p->_name`,则会将 `_name` 实例变量加入 watchpoint当变量被修改时会触发断点可以看出从某个值变为 0x0也就是 nil。此时边上调用堆栈显示在 `objc_storestrong` 方法中,被设置为 nil.
![](./../assets/cxx_destructDemo2.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/cxx_destructDemo2.png)
@@ -2843,7 +2843,7 @@ Person *person2 = [Person personWithName:@"FantasticLBP"];
隐式调用工厂方法
<img src="./../assets/ARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
@@ -2853,7 +2853,7 @@ Person *person2 = [Person personWithName:@"FantasticLBP"];
如何修改?加一个 bridge 即可。
<img src="./../assets/FixARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/FixARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
由于 ARC 没有加 retain。所以 `person = (__bridge id)result;` 这里完成了对象的 retain。ARC 在退出方法的作用域时给对象加上release。前后对应内存正确。
@@ -2964,7 +2964,7 @@ class AutoreleasePoolPage {
- 每个 AutoreleasePoolPage 对象占用 4096 16的3次方0x2000字节内存除了用来存放它内部的成员变量内部成员固定有7个56个字节即 `0x18`, `0x1000 + 0x38 = 0x1038` ),剩下的空间用来存放 autorelease 对象的地址
- 所有的 AutoreleasePoolPage 对象通过**双向链表**的形式连接在一起。child 指向下一个 AutoreleasePoolPage 对象parent 指向上一个 AutoreleasePoolPage 对象
![](./../assets/autoreleasepool.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/autoreleasepool.png)
```objectivec
id * begin() {
@@ -3288,7 +3288,7 @@ class AutoreleasePoolPage : private AutoreleasePoolPageData {
举个例子for 循环创建1000个 Person 对象,用 autorelease 修饰,如何工作?
<img src="./../assets/AutoreleasePoolPageWithForIterator.png" style="zoom:80%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AutoreleasePoolPageWithForIterator.png" style="zoom:80%" />
分析:
@@ -3327,7 +3327,7 @@ int main(int argc, const char * argv[]) {
main 方法内部3个 autoreleasepool 底层怎么样工作的?
<img src="./../assets/AutoreleasePoolMoreItem.png" style="zoom:60%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AutoreleasePoolMoreItem.png" style="zoom:60%" />
分析:
@@ -4226,7 +4226,7 @@ iOS 在主线程的 Runloop **通用模式Common Modes** 中注册 **1 个
结合 RunLoop 运行图
![](./../assets/RunLoop-SourceCode.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoop-SourceCode.png)
- 01 通知 Observer 进入 Loop 会调用 `objc_autoreleasePoolPush`
@@ -4373,7 +4373,7 @@ Cocoa 框架中,很多类方法用于返回 autorelease 对象。
### NSTimer、CSDisplayLink 中的内存泄露
#### CADisplayLink 内存泄漏
<img src="./../assets/CSDisplayLinkMemoryLeak.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/CSDisplayLinkMemoryLeak.png" style="zoom:30%" />
可以看到 CADisplayLink 和 VCVC 和 CADisplayLink 互相持有,造成内存泄漏,没有释放。即使页面离开,定时器还在继续运行,不断打印。
@@ -4387,11 +4387,11 @@ NSTimer 的基础 API `[NSTimer scheduledTimersWithTimeInterval:1 repeat:YES blo
Demo 如下:
<img src="./../assets/NSTimerMemoeryLeakDemo.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSTimerMemoeryLeakDemo.png" style="zoom:30%" />
但是当使用 `[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerTask) userInfo:nil repeats:NO];` repeats 为 NO 的时候,好像不会内存泄漏。这是为什么?
<img src="./../assets/NSTimerMemoeryNotLeakWhenRepeatNO.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSTimerMemoeryNotLeakWhenRepeatNO.png" style="zoom:30%" />
@@ -4563,7 +4563,7 @@ Demo 如下:
##### 改用 block 的方式替换 API不再持有 target
<img src="./../assets/NSTimerFixMemoryLeakIssueByBlockAPI.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSTimerFixMemoryLeakIssueByBlockAPI.png" style="zoom:30%" />
该种方式,控制器 self强引用 timertimer 强引用 blockblock 弱引用 self3者没有形成环。
@@ -4623,7 +4623,7 @@ TimerTarget.target(weak) -> VC
}
```
<img src="./../assets/NSTimerMemoryLeakFixedByProxy.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSTimerMemoryLeakFixedByProxy.png" style="zoom:30%" />
解决方案2使用专门处理消息转发的 NSProxy 类
@@ -4631,7 +4631,7 @@ TimerTarget.target(weak) -> VC
##### NSProxy 闪亮登场
<img src="./../assets/NSTimerMemoryLeakFixedByNSProxy.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSTimerMemoryLeakFixedByNSProxy.png" style="zoom:30%" />
可以看到使用 NSProxy 也可以解决 NSTimer 和 VC 循环引用的问题。但注意:继承自 NSProxy 的类,不能 init。
@@ -4645,7 +4645,7 @@ QA自己写的继承自 NSObject 的代理对象和继承自 NSProxy 的代
看一段神奇的代码
<img src="./../assets/NSProxyAndNSObjectMethodImpl.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSProxyAndNSObjectMethodImpl.png" style="zoom:30%" />
为什么打印出 `0 1`
@@ -4684,7 +4684,7 @@ QA自己写的继承自 NSObject 的代理对象和继承自 NSProxy 的代
<img src="./../assets/RunLoop-SourceCode.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoop-SourceCode.png" style="zoom:30%" />
假设一个 NSTimer 被加到 RunLoop 开头NSTimer 执行周期为1sRunLoop 前面任务繁重,第一次走完一个完整的 RunLoop 需要0.4s,然后从头检测 NSTimer 有没有到时间,发现还没到继续执行 RunLoop 后续逻辑。后面遇到卡顿任务了,第二次 RunLoop 用了0.5s,然后从头检测 NSTimer 有没有到时间0.4+0.5还不到时间,继续跑,第三次 RunLoop 比较轻松耗时0.2s再判断定时器时间有没有到则此次已经0.4+0.5+0.2=1.1s了,此时 NSTimer 的事件被执行,此时精确度已经不够了(每次 RunLoop 的执行时间不固定)
@@ -4854,7 +4854,7 @@ dispatch_semaphore_t semaphore_;
说明:直接 `performSelector` 存在警告,可以告诉编译器忽略警告。可以在 Xcode 点开警告,查看详情,复制 `[]` 里面的字符串去忽略警告
![](./../assets/ignoreXcodewarning.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ignoreXcodewarning.png)
@@ -5035,11 +5035,11 @@ NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
再来2个实验
<img src="./../assets/NSDictionaryCopyTheKey.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSDictionaryCopyTheKey.png" style="zoom:30%" />
分析:可以看到 p1 的地址,在刚初始化后,和当作 key 加入到 NSDictionary 后,地址发生了变化。对 p1 执行了 copy 操作。
<img src="./../assets/NSMapTableNSDictionaryMemoryControl.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSMapTableNSDictionaryMemoryControl.png" style="zoom:30%" />
分析:
@@ -5090,7 +5090,7 @@ NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
这段代码运行会 crash信息如下
![](./../assets/NSErrorZombieCrash.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSErrorZombieCrash.png)
原因是 NSError 构造方法内部会加 autorelease。源码如下
@@ -5153,7 +5153,7 @@ MRC 下的 `[(id)(object) autorelease]` 等价于 ARC 下的 `id __autoreleasing
我写了个僵尸对象检测工具,效果如下
![](./../assets/ZombieSniffer.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ZombieSniffer.png)
可以定位僵尸对象,并且打印出具体堆栈,并模拟系统行为调用 `abort` 。对监控原理和工具实现感兴趣的可以查看这里[带你打造一套 APM 监控系统-内存监控之野指针/内存泄漏监控](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.74.md#zombieSniffer)