两种方法
1、machO文件中 通过 __objc_classlist
__objc_classrefs
对比
2、运行时获取到未使用的类(需要大量测试,或者线上测试)
两种方法都不能绝对识别准确,但合并起来准确率很高了,最终再手动确定
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #include <dlfcn.h> #include <mach-o/dyld.h> #include <mach-o/getsect.h>
/* 获取 runtime 未使用的类 */ NSMutableSet<NSString *> *runtimeUnusedClass() { Dl_info info; dladdr((const void *)&runtimeUnusedClass, &info); const uint64_t mach_header = (uint64_t)info.dli_fbase; const struct section_64 *classlist = getsectbynamefromheader_64((const struct mach_header_64 *)mach_header, "__DATA", "__objc_classlist"); if (classlist) { NSMutableSet *classlistUnused = [[NSMutableSet alloc] init]; // 遍历拿到所有的类 for (UInt64 addr = classlist->offset; addr < classlist->offset + classlist->size; addr += sizeof(const char **)) { uint64_t baseArrr = mach_header + addr; //获取类对象指针 uint64_t object_class_addr = *(uint64_t *)(baseArrr); // uint64_t object_class_isa = *(uint64_t *)(object_class_addr); //获取元类对象 uint64_t object_meta_class_addr; if (object_class_isa & (1 << 0)) { object_meta_class_addr = object_class_addr; } else { object_meta_class_addr = object_class_isa & 0x00007ffffffffff8ULL; } //偏移32个字节,拿到 class_data_bits 的地址 uint64_t class_data_bits_addr = object_meta_class_addr + 32; //取 class_data_bits 的值 uint64_t class_data_bits = *(uint64_t *)(class_data_bits_addr); //得到 class_rw_t 的指针 (class_rw_t *)(bits & FAST_DATA_MASK); uint64_t class_rw_addr = (class_data_bits & 0x00007ffffffffff8UL); //取指针的值,得到的第一个数据就是 flags uint64_t flags = *(uint64_t *)(class_rw_addr); // bool isInitialized = flags & (1 << 29); if (!isInitialized) { //打印类会导致类初始化,所以此方法只能使用一次 NSString *class = [NSString stringWithFormat:@"%@", (void *)object_class_addr]; [classlistUnused addObject:class]; } } return classlistUnused; } return nil; }
/* 获取 MachO 未使用的类 */ NSMutableSet<NSString *> *machOUnusedClass() { Dl_info info; dladdr((const void *)&machOUnusedClass, &info); const uint64_t mach_header = (uint64_t)info.dli_fbase; const struct section_64 *classlist = getsectbynamefromheader_64((const struct mach_header_64 *)mach_header, "__DATA", "__objc_classlist"); const struct section_64 *selfrefs = getsectbynamefromheader_64((const struct mach_header_64 *)mach_header, "__DATA", "__objc_classrefs"); if (classlist && selfrefs) { NSMutableSet *classlistSet = [[NSMutableSet alloc] init]; for (UInt64 addr = classlist->offset; addr < classlist->offset + classlist->size; addr += sizeof(const char **)) { uint64_t baseArrr = mach_header + addr; Class cls = (__bridge Class)(*(void **)(baseArrr)); NSString *clsString = [NSString stringWithFormat:@"%@",cls]; [classlistSet addObject:clsString]; } NSMutableSet *selfrefsSet = [[NSMutableSet alloc] init]; for (UInt64 addr = selfrefs->offset; addr < selfrefs->offset + selfrefs->size; addr += sizeof(const char **)) { uint64_t baseArrr = mach_header + addr; Class cls = (__bridge Class)(*(void **)(baseArrr)); while (cls) { [selfrefsSet addObject:[NSString stringWithFormat:@"%@",cls]]; cls = [cls superclass]; } } [classlistSet minusSet:selfrefsSet]; return classlistSet; } return nil; }
|
缺点
1、runtimeUnusedClass
方法只有在第一次调用有效,而且必须手动浏览所有的页面(可以考虑放到线上收集)
2、两种方法都是不准确的,但未使用的类一定在其中,最终需要手动再次确认。