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 + compareAndSwapOSAtomic 系列,更现代可用 std::atomic 封装。


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

苹果已废弃 OSSpinLock,不推荐 ;替代为 os_unfair_lockdispatch_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 用 weakblock 先 weak 再 strongTimer 用 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,可以简单快速的检测内测泄漏。

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

相关推荐
2501_9151063211 小时前
HTTPS 爬虫实战指南 从握手原理到反爬应对与流量抓包分析
爬虫·网络协议·ios·小程序·https·uni-app·iphone
2501_9160074711 小时前
iOS 上架技术支持全流程解析,从签名配置到使用 开心上架 的实战经验分享
android·macos·ios·小程序·uni-app·cocoa·iphone
QMY52052011 小时前
深度优先遍历策略
macos·jupyter·postman
非专业程序员Ping1 天前
一文读懂字体文件
ios·swift·assembly·font
wahkim1 天前
移动端开发工具集锦
flutter·ios·android studio·swift
2501_916007471 天前
提升 iOS 26 系统流畅度的实战指南,多工具组合监控
android·macos·ios·小程序·uni-app·cocoa·iphone
wangxiaosu1 天前
macos安装、更新、使用homebrew
macos·homebrew
hellojackjiang20111 天前
全面适配iOS 26液态玻璃,基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v10.2发布
ios·网络编程·即时通讯·im开发·rainbowchat
心灵宝贝1 天前
Mac版PDF Squeezer v4.5.1安装教程(DMG文件下载+详细步骤)
macos