一、简介

MTHawkeye 是美图 iOS 团队开源的一款调试辅助、性能优化辅助工具集,旨在帮助 iOS 开发者提升开发效率、辅助优化性能体验,是 iOS 开发者非常值得引入和学习的开源工具。

本文意在通过分析 MTHawkeye 的源码实现,帮助学习 iOS 开发技术及原理。

本文所分析的源码为 MTHawkeye 仓库 develop 分支 [4e2af4d](https://github.com/yujiuqie/MTHawkeye) 提交,不同递交代码可能存在部分差异。

二、工程结构

MTHawkeye 核心代码位于 MTHawkeye/MTHawkeye/ 文件夹,包含 Core、DefaultPlugins、EnergyPlugins、FLEXExtension、GraphicsPlugins、MemoryPlugins、NetworkPlugins、StackBacktrace、StorageMonitorPlugins、TimeConsumingPlugins、UISkeleton、Utils 12 个子文件夹。

MTHawkeye 通过这 12 个子文件夹中的代码完成包括性能监测、内存监测、网络监测、CPU监测、沙盒存储、图形监测以及 FLEX 扩展等服务。

文件夹 功能
TimeConsumingPlugins
MemoryPlugins
NetworkPlugins
EnergyPlugins
StorageMonitorPlugins
GraphicsPlugins
FLEXExtension
Core
DefaultPlugins
StackBacktrace
Utils
UISkeleton

接下来我们通过源码来分析这些功能的技术实现。

三、技术实现

3.1 Core 文件夹

Core 文件夹包含以下文件

文件 功能
MTHawkeyePlugin 插件协议,MTHawkeye 默认插件及扩展插件都必须遵循该协议
MTHawkeyeAppStat
MTHawkeyeAverageStorage
MTHawkeyeClient
MTHawkeyeDyldImagesStorage
MTHawkeyeStorage

3.1.1 MTHawkeyeUserDefaults

MTHawkeyeUserDefaults 是一个工具类,支持单例实现,主要实现对特定 key-value 的存储及监听回调,它的结构及核心方法描述如图:

名称 MTHawkeyeUserDefaults
类型 工具类
作用 实现了一套更加易于使用的 KVO 功能,对字段的监听更方便,被监听的值可以实时离线保存

源码分析和学习主要关注以下四个部分:

  • 核心方法 1
  • 核心方法 2
  • UserDefaults 监听及回调的管理
  • UserDefaults 离线存储

核心方法 1:针对 object 对象的 key 值执行监听,如果 key 值发生变化,则回调 handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)mth_addObserver:(NSObject *)object forKey:(NSString *)key withHandler:(MTHawkeyeUserDefaultChangedHandler)handler {
@synchronized(self.observerHandlers) {
NSMutableArray *handlers = [self.observerHandlers objectForKey:key];
if (handlers == nil) {
handlers = @[].mutableCopy;
[self.observerHandlers setObject:handlers forKey:key];
}

MTHUserDefaultsObserverInfo *observerInfo = [[MTHUserDefaultsObserverInfo alloc] init];
observerInfo.observer = (NSInteger)object;
observerInfo.key = key;
observerInfo.handler = handler;
[handlers addObject:observerInfo];
}
}

核心方法 2: 对指定 key 的 value 进行更新,会触发核心方法 1 中的 handler 回调,此方法内部实现未区分核心方法 1 中的 object 维度,因此所有监听了同样 key 的 object 对象均会回调 handler 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName {
id oldValue = [self objectForKey:defaultName];
if (oldValue == value)
return;

@synchronized(self.defaults) {
if (value)
[self.defaults setObject:value forKey:defaultName];
else
[self.defaults removeObjectForKey:defaultName];

[self.defaults writeToFile:[self cachePath] atomically:NO];
}

@synchronized(self.observerHandlers) {
NSEnumerator *keyEnumerator = [self.observerHandlers keyEnumerator];
NSString *key;
while ((key = [keyEnumerator nextObject])) {
if ([key isEqualToString:defaultName]) {
NSMutableArray<MTHUserDefaultsObserverInfo *> *handlers = [self.observerHandlers objectForKey:defaultName];
[handlers enumerateObjectsUsingBlock:^(MTHUserDefaultsObserverInfo *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
obj.handler(oldValue, value);
}];
break;
}
}
}
}

UserDefaults 监听及回调的管理:

监听对象和 key 以及 handler 通过 MTHUserDefaultsObserverInfo 类进行关联和管理,该类仅用于 MTHawkeyeUserDefaults 中

UserDefaults 离线存储:

通过 MTHawkeyeUserDefaults 存储的值会通过 [MTHawkeyeUtility hawkeyeStoreDirectory] 写入离线文件 preferences 中

1
2
3
- (NSString *)cachePath {
return [[MTHawkeyeUtility hawkeyeStoreDirectory] stringByAppendingPathComponent:@"preferences"];
}

3.1.2 MTHawkeyeStorage

MTHawkeyeStorage 官方介绍参考 hawkeye-storage-cn.md

四、存在问题

异常报错

1
Cannot initialize a parameter of type 'id<NSCopying> _Nonnull' with an rvalue of type 'Class'

解决方案:GitHub -

五、参考文档