FLEX菜单里具有一个功能,就是可以查看应用里在堆中的内存,虽然这个功能比不上xcode自带的memory graph(不能查看引用关系),不过某些时候还是有点用处的,因为好奇它的实现翻了下它的源码,基本上就是c函数的一些封装。
-[FLEXLiveObjectsTableViewController reloadTableData]
控制器拿到内存数据通过这个方法,它的实现如下:
1.
unsigned int classCount = 0;
Class *classes = objc_copyClassList(&classCount);
CFMutableDictionaryRef mutableCountsForClasses = CFDictionaryCreateMutable(NULL, classCount, NULL, NULL);
for (unsigned int i = 0; i < classCount; i++) {
CFDictionarySetValue(mutableCountsForClasses, (__bridge const void *)classes[i], (const void *)0);
}
2.
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)actualClass);
instanceCount++;
CFDictionarySetValue(mutableCountsForClasses, (__bridge const void *)actualClass, (const void *)instanceCount);
}];
3.
NSMutableDictionary *mutableCountsForClassNames = [NSMutableDictionary dictionary];
for (unsigned int i = 0; i < classCount; i++) {
Class class = classes[i];
NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)(class));
if (instanceCount > 0) {
NSString *className = @(class_getName(class));
[mutableCountsForClassNames setObject:@(instanceCount) forKey:className];
}
}
free(classes);
self.instanceCountsForClassNames = mutableCountsForClassNames;
[self updateTableDataForSearchFilter];
- 创建一个CFMutableDictionaryRef的字典之所以用这个,是因为它是以类名为键,实例的个数为值的,如果用nsnumber创建会有堆上的额外开销
- 读取内存中的全部对象,给CFMutableDictionaryRef赋值
- 最后将CFMutableDictionaryRef 转化为NSDictionary
这里在重要的步骤是2,下面把这个方法展开
+[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:]
if (!block) {
return;
}
1.
[self updateRegisteredClasses];
vm_address_t *zones = NULL;
unsigned int zoneCount = 0;
2.
kern_return_t result = malloc_get_all_zones(mach_task_self(), &memory_reader, &zones, &zoneCount);
if (result == KERN_SUCCESS) {
for (unsigned int i = 0; i < zoneCount; i++) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
3.
if (zone->introspect && zone->introspect->enumerator) {
zone->introspect->enumerator(mach_task_self(), (__bridge void *)(block), MALLOC_PTR_IN_USE_RANGE_TYPE, zones[i], &memory_reader, &range_callback);
}
}
}
1.更新已经注册的类,将其放在registeredClasses
里面,其方法比较简单,如下:
+ (void)updateRegisteredClasses
{
if (!registeredClasses) {
registeredClasses = CFSetCreateMutable(NULL, 0, NULL);
} else {
CFSetRemoveAllValues(registeredClasses);
}
unsigned int count = 0;
Class *classes = objc_copyClassList(&count);
for (unsigned int i = 0; i < count; i++) {
CFSetAddValue(registeredClasses, (__bridge const void *)(classes[i]));
}
free(classes);
}
2.malloc_get_all_zones
获取在内存中的所有对象区域,放在zones
里面,数量放到zoneCount
里面,mach_task_self()
应该是当前线程的意思,而memory_reader
是一个需要实现的函数,这里的实现如下:
static kern_return_t memory_reader(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory)
{
*local_memory = (void *)remote_address;
return KERN_SUCCESS;
}
*local_memory = (void *)remote_address
应该是将读出来的对象放入内存的意思,注释掉会崩溃,返回KERN_SUCCESS
代表读取成功(返回KERN_FAILURE
就不会读取到zones
里面)
3.读取了对象区域之后,还需要挨个解析成对象,主要还是调用malloc_introspection_t
里面的enumerator
方法,它传入多个参数:
- 代表当前进程,
- 给回调的参数,这里传入block
-
MALLOC_PTR_IN_USE_RANGE_TYPE
表示指向堆的指针(在这个场景下只能选这种) -
zones[i]
代表传入的地址 -
memory_reader
是读取内存的函数,这里复用了步骤2的函数 -
range_callback
回调函数指针,读取完后就会调用这个方法
range_callback
的实现
static void range_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount)
{
flex_object_enumeration_block_t block = (__bridge flex_object_enumeration_block_t)context;
if (!block) {
return;
}
for (unsigned int i = 0; i < rangeCount; i++) {
vm_range_t range = ranges[i];
flex_maybe_object_t *tryObject = (flex_maybe_object_t *)range.address;
Class tryClass = NULL;
#ifdef __arm64__
extern uint64_t objc_debug_isa_class_mask WEAK_IMPORT_ATTRIBUTE;
tryClass = (__bridge Class)((void *)((uint64_t)tryObject->isa & objc_debug_isa_class_mask));
#else
tryClass = tryObject->isa;
#endif
if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
block((__bridge id)tryObject, tryClass);
}
}
}
显然,它是通过range.address
拿到对象的,flex_maybe_object_t
就是oc对象的一个结构模拟,其实只是为了拿isa
来知道这是个什么类而已。拿到类之后,判断这个类是已经注册过的话,就通过block回调出去。