当前位置: 首页 > news >正文

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.0target:selfselector:@selector(fire)userInfo:nilrepeats: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.0repeats:YESblock:^(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 LeaksProfile → Leaks → Call Tree → Invert可导出 .trace 与 CI 集成
Sanitizer AddressScheme → Diagnostics → Address Sanitizer运行时检测 UAF & 循环引用
FBRetainCycleDetectorCocoaPods 引入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,可以简单快速的检测内测泄漏。

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

http://www.dtcms.com/a/511415.html

相关文章:

  • 『CMake』关于使用CMake构建项目时的现代/传统指令
  • 请被人做网站怎么做倒计时网站
  • App开发框架调研对比
  • Ubuntu下载以及安装详解以及应用安装
  • 亚马逊云代理:AWS的EC2, S3, RDS,Lambda具体简介
  • 2640. QYQ在艾泽拉斯
  • 基于 React + TypeScript + Fabric.js 构建一个封面生成器网站
  • 营销型电子商务网站品牌建设与推广思路
  • 更新网站 seo公司的管理方式与管理方法
  • BZV49-C22,115稳压二极管 NXP安世半导体 工业电源芯片 芯片解析
  • 职场发展—如何避雷垃圾公司
  • 【Linux篇】软链接vs硬链接:Linux文件系统中的两种引用机制
  • C++ list核心接口与实战技巧
  • 微服务框架
  • 网站模块结构图wordpress调用栏目名称
  • 算法学习记录03——二叉树学习笔记:从两道题看透后序位置的关键作用​
  • Rust高性能分布式任务调度系统开发实践:从设计到性能优化
  • go tools安装
  • 阿里云代理商:如何给阿里云配置网络ACL?
  • 阿里巴巴 Java 开发手册解读:DO、DTO、BO、AO、VO、Query 的区别与用法
  • 接口测试如何做
  • 记录日常日志
  • 【LeetCode_876_2.02】快慢指针在链表中的简单应用
  • LOOP套LOOP,双LOOP优化,效率提升近30倍
  • iOS 混淆实战 多工具组合完成 IPA 混淆、加固与工程化落地(iOS混淆|IPA加固|无源码混淆|Ipa Guard|Swift Shield)
  • 计算机毕设java中学生心理健康管理系统 中学生心理健康管理的Java平台解决方案 Java技术驱动的中学心理健康管理系统研发
  • 模重复平方计算法
  • 温州网站制作套餐.net网站开发是什么对象开发
  • Tuning——CC调试(适用高通)
  • 【AI智能体开发】什么是LLM?如何在本地搭建属于自己的Ai智能体?