iOS的多线程下数据安全和内存泄漏以及工具使用监测内存泄漏

iOS的NSMutableArray的add, remove,indexof如何确保在多线程的情况下安全?

NSMutableArray 的所有读写操作(addObject:、removeObject:、indexOfObject: 等)本身都不是线程安全的 。

要在多线程环境下安全使用,只有一条铁律:

"同一时间最多只能有一个线程在写,或者可以有多个线程同时读,但绝不能读写并发。"

下面给出 4 种 iOS 里真正常用、可落地的线程保护方案,按"性能 ➜ 代码量"排序,并附最小可运行示例。


一、最简方案:@synchronized 互斥锁

适用:并发量低、不想引入额外框架

成本:一次内核互斥,约 1-2 µs

复制代码
@interface ThreadSafeArray : NSObject
@property (nonatomic, strong) NSMutableArray *array;
@end

@implementation ThreadSafeArray
- (instancetype)init {
    self = [super init];
    _array = [NSMutableArray array];
    return self;
}
- (void)addObject:(id)obj {
    @synchronized(self.array) {
        [self.array addObject:obj];
    }
}
- (void)removeObject:(id)obj {
    @synchronized(self.array) {
        [self.array removeObject:obj];
    }
}
- (NSUInteger)indexOfObject:(id)obj {
    @synchronized(self.array) {
        return [self.array indexOfObject:obj];
    }
}
@end

二、高效方案:dispatch_barrier 读写锁

特点:读并发、写独占;性能比 @synchronized 高 2-3 倍

复制代码
@interface RWArray : NSObject
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, strong) dispatch_queue_t rwQueue; // CONCURRENT
@end

@implementation RWArray
- (instancetype)init {
    self = [super init];
    _array = [NSMutableArray array];
    _rwQueue = dispatch_queue_create("com.demo.rw", DISPATCH_QUEUE_CONCURRENT);
    return self;
}
// 读:可并发
- (NSUInteger)indexOfObject:(id)obj {
    __block NSUInteger idx = NSNotFound;
    dispatch_sync(self.rwQueue, ^{
        idx = [self.array indexOfObject:obj];
    });
    return idx;
}
// 写:独占
- (void)addObject:(id)obj {
    dispatch_barrier_async(self.rwQueue, ^{
        [self.array addObject:obj];
    });
}
- (void)removeObject:(id)obj {
    dispatch_barrier_async(self.rwQueue, ^{
        [self.array removeObject:obj];
    });
}
@end

三、无锁方案:immutable copy(读远大于写)

思路:底层永远持一个 不可变 NSArray,每次写都生成新数组再原子替换;读无锁。

复制代码
@interface CopyOnWriteArray : NSObject
@property (nonatomic, strong) NSArray *array; // 永远 immutable
@end

@implementation CopyOnWriteArray
- (instancetype)init {
    self = [super init];
    _array = @[];
    return self;
}
- (void)addObject:(id)obj {
    NSArray *newArray;
    do {
        NSArray *old = self.array;
        newArray = [old arrayByAddingObject:obj];
    } while (!OSAtomicCompareAndSwapPtr(old, newArray, (void **)&_array)); // 原子替换
}
- (NSUInteger)indexOfObject:(id)obj {
    // 读操作无锁
    return [self.array indexOfObject:obj];
}
@end

注:iOS10+ 可用 atomic + compareAndSwap 或 OSAtomic 系列,更现代可用 std::atomic 封装。


四、现成线程安全容器:OSAtomic + OSSpinLock(已废弃,仅作了解)

苹果已废弃 OSSpinLock,不推荐 ;替代为 os_unfair_lock 或 dispatch_semaphore。


五、一句话总结(面试秒答)

"NSMutableArray 本身不线程安全;
多读单写 用 dispatch_barrier 最均衡,小并发 直接 @synchronized,读远多于写 可 immutable copy 无锁;

核心原则:读写不能并发,写必须互斥。"

iOS的常见的内存泄漏,比如Delegate 强引用循环,Block 自引用循环以及NSTimer 死循环的样式以及解决方法

iOS 面试里"内存泄漏"三板斧:delegate 强引用、block 自引用、Timer 死循环。


Delegate 强引用循环(最老但最易错)

泄漏代码

复制代码
@interface Boss : NSObject
@property (nonatomic, strong) id <WorkerDelegate> delegate; // ❌ 强引用
@end
@interface Worker : NSObject <WorkerDelegate>
@property (nonatomic, strong) Boss *boss;
@end

原因:Boss ↔ Worker 互相强引用,dealloc 永不被调用。

修复

复制代码
@property (nonatomic, weak) id <WorkerDelegate> delegate; // ✅ 弱引用

2025 加分点

"用 __weak typeof(self) weakSelf = self; 之外,还可开启 -fsanitize-address 在运行时检测循环引用;Xcode 15 Memory Graph 能直接可视化箭头。"


Block 自引用循环(ARC 下最常见)

泄漏代码

复制代码
self.someBlock = ^{
    NSLog(@"%@", self.name); // ❌ 直接捕获 self → 强引用
};

原因:block 拷贝到堆后强引用 self,self 又强引用 block。

修复模板(weak-strong dance)

复制代码
__weak typeof(self) weakSelf = self;
self.someBlock = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf; // 避免中途释放
    if (!strongSelf) return;
    NSLog(@"%@", strongSelf.name);
};

2025 新语法糖(Clang 15+)

复制代码
self.someBlock = ^(void) weakify(self) {
    strongify(self);
    NSLog(@"%@", self.name);
};

weakify / strongify 来自 libextobjc,2025 年 CocoaPods 仍活跃维护。


NSTimer 死循环(RunLoop 强引用 target)

泄漏代码

复制代码
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                  target:self
                                                selector:@selector(fire)
                                                userInfo:nil
                                                 repeats:YES];
}
- (void)dealloc {
    [self.timer invalidate]; // ❌ 永远不会执行
}

原因:RunLoop → Timer → self 强引用,self 无法释放。

经典修复(iOS 10 之前)

复制代码
- (void)viewWillDisappear:(BOOL)animated {
    [self.timer invalidate];
    self.timer = nil;
}

2025 推荐修复(iOS 10+ block API)

复制代码
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                              repeats:YES
                                                block:^(NSTimer * _Nonnull timer) {
    __strong typeof(weakSelf) ss = weakSelf;
    if (ss) [ss fire];
}];

block 版 timer 对 self 是 weak 捕获,无需手动 invalidate 也不会循环引用。


CADisplayLink / 通知中心忘记移除

泄漏代码

复制代码
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
[self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
// 忘记 remove

修复

复制代码
- (void)dealloc {
    [self.link invalidate]; // 必须
}

2025 通用模板(NSHashTable 弱引用)

复制代码
@interface TimerHolder : NSObject
@property (nonatomic, strong) CADisplayLink *link;
@end
@implementation TimerHolder
- (void)start {
    __weak typeof(self) w = self;
    self.link = [CADisplayLink displayLinkWithTarget:w selector:@selector(step)];
    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)dealloc { [self.link invalidate]; }
@end

2025 检测工具一键定位

工具 用法 2025 新特性
Xcode Memory Graph 运行 → Debug Navigator → Memory → 蓝色箭头 自动标出"循环引用"紫色闪电
Instruments Leaks Profile → Leaks → Call Tree → Invert 可导出 .trace 与 CI 集成
Sanitizer Address Scheme → Diagnostics → Address Sanitizer 运行时检测 UAF & 循环引用
FBRetainCycleDetector CocoaPods 引入 2024 支持 Swift/ObjC 混编

"内存泄漏三板斧:
delegate 用 weak ,block 先 weak 再 strong ,Timer 用 block API 或及时 invalidate ;

配合 Memory Graph 紫色箭头,10 秒定位循环引用。"

iOS 使用Instruments的Leaks工具,定位内存泄露的代码

环境

Xcode Version 10.2.1 (10E1001)

iPhone Version12.1.3

使用流程

启动

暂停

红色x表示存在内存泄露的地方。如果查找到了内存泄露,可以点击暂停,进入第4步

选择 Leaks > Call Tree

勾选

双击,即可定位内存泄露代码。

按照如上配置完成后,有时候我们仍然无法定位到内存泄露的具体代码,你可以尝试如下配置,开启debug环境下的dsYM:

利用 Xcode 内存表(Debug Memory Graph)检测内测泄漏

前言

平常我们都会用 Instrument 的 Leaks / Allocations 或其他一些开源库进行内存泄露的排查,但它们都存在各种问题和不便,

在这个 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限。

今天介绍一种简单直接的检测内测泄漏的方法:Debug Memory Graph

就是这货:

正文

我最近的项目中,退出登录后(跳转到登录页),发现首页控制器没有被销毁,依旧能接收通知。

退出登录代码:

代码语言:javascript

代码运行次数:0

运行

AI代码解释

复制代码
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Login" bundle:[NSBundle mainBundle]];
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.window.rootViewController = [storyboard instantiateViewControllerWithIdentifier:@"LoginVC"];

很明显发生了循环引用导致的内测泄漏。

接下来就使用 Debug Memory Graph 来查看内测泄漏了。

运行程序

首先启动 Xcode 运行程序。

Debug Memory Graph

点击 Debug Memory Graph 按钮后,可以看到红框内的是当前内存中存在的对象。其中,绿色的就是视图控制器。

这样,我们随时都可以查看内测中存在的对象,换句话说,就是可以通过观察 Memory Graph 查看内测泄漏。

调试你的App

继续运行你的程序

然后对App进行调试、push、pop 操作,再次点击 Debug Memory Graph 按钮。那些该释放而依旧在内测中的 控制器 或 对象 就能一一找出来了。

接下来,只要进入对应的控制器找到内测泄漏的代码就OK了,一般是Block里引用了 self,改为 weakSelf 就解决了。

代码语言:javascript

代码运行次数:0

运行

AI代码解释

复制代码
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self;

WS(weakSelf)
sView.btnBlock = ^(NSInteger idx){
    [weakSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:idx] withRowAnimation:UITableViewRowAnimationAutomatic];
};

结语

就这样,利用 Debug Memory Graph,可以简单快速的检测内测泄漏。

一般由两个对象循环引用的内测泄漏是比较好发现的,如果是由三个及其三个以上的对象形成的大的循环引用,就会比较难排查了。

相关推荐
开心就好202515 小时前
uni-app 本地打包 iOS,在 Windows 上使用 KXBuild 产出 ipa 并上架的完整流程
后端·ios
调试人生的显微镜15 小时前
跨平台抓包工具怎么选?TraceEagle抓包鹰 的四种跨平台能力
后端·ios
Khalil三省17 小时前
MacBook 刘海显示歌词:让 Mac 也有灵动岛歌词
macos·开源·效率工具·音乐
2501_9159090617 小时前
Ipa Guard 防护上架,混淆、重签名和回归测试怎么做
android·ios·小程序·https·uni-app·iphone·webview
木头分享19 小时前
2026淘宝、京东双11活动全自动脚本丨IOS+安卓
ios
茶底世界之下19 小时前
红花太抢眼,蓝天又想留下:用 Awaking 比较两种减饱和
ios
桌管小李21 小时前
终端安全一体化实战:AI 审计、行为分析、零信任、防拍照与远程运维全解析
运维·服务器·网络·人工智能·windows·安全·macos
2501_916008891 天前
Windows 上写第一个 SwiftUI App,KXBuild 空工程起步,跑到 iPhone 真机
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915918411 天前
iOS 上架注意事项有哪些?Appuploader 的四个阶段自查清单
android·ios·小程序·https·uni-app·iphone·webview
Piink1 天前
Mac使用Vscode教程
ide·vscode·macos