我们可以使用乐器进行各种分析。但是,许多程序员发现这个工具太复杂,太重,无法带来真正的价值。
是否有一种简单的方法可以跟踪特定类的所有对象,并且每个对象都知道究竟是谁分配它们并验证它们是否正确释放?
答案是肯定的!有一种方法,我将在下面的答案中进行演示
答案 0 :(得分:0)
轻松跟踪分配:
如何使用:您可以将以下150行代码放入名为 AllocTracker.m 的文件中,并将拖动添加到您的项目中文件。 使用Xcode右侧窗格中的复选框在编译目标中启用/禁用它。
你会得到什么? 启用后,此模块将跟踪 UIImage 对象的所有分配和解除分配并记录它们。 (可以很容易地修改它以跟踪其他类。)
除了记录每个分配和释放之外,它还会定期(当前每15秒)转储当前分配的所有对象,并添加一些信息和分配它们的调用堆栈。
增值是多少? 此代码用于大型项目中,以消除在不事先通知的情况下分配的孤立对象,从而显着减少应用程序的内存占用并修复内存泄漏。
以下是AllocTracker.m的代码:
#define TRACK_ALLOCATIONS
#ifdef TRACK_ALLOCATIONS
#import <UIKit/UIKit.h>
#define TIMER_INTERVAL 15
@implementation UIApplication(utils)
+(NSString *)dateToTimestamp:(NSDate *)date
{
if (date == nil) {
date = [NSDate date];
}
static NSDateFormatter *dateFormatter = nil;
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setDateFormat:@"HH:mm:ss.S"];
}
NSString *ts = [dateFormatter stringFromDate:date];
return ts;
}
+(NSString*) getCaller:(int)stackDepth
{
#ifndef DEBUG
return @"NON DBG";
#else
NSArray *symbols = [NSThread callStackSymbols];
int lastIndex = (int)(symbols.count - 1);
if (lastIndex < 3) {
return @"NO DATA";
}
NSMutableString *result = [NSMutableString string];
int foundCount = 0;
for (int ix=3; ix <= lastIndex; ix++) {
NSString *line = symbols[ix];
NSRange rng1 = [line rangeOfString:@"["];
if (rng1.location == NSNotFound) {
continue;
}
NSRange rng2 = [line rangeOfString:@"]"];
NSString *caller = [line substringWithRange:NSMakeRange(rng1.location+1, rng2.location-rng1.location-1)];
if (foundCount > 0) { //not first
[result appendString:@"<--"];
}
[result appendString:caller];
if (++foundCount == stackDepth) {
break;
}
}
return (foundCount > 0) ? result : @"NO SYMBOL";
#endif
}
@end
@implementation UIImage(memoryTrack)
static NSMapTable *g_allocsMap;
static NSTimer *g_tmr;
static NSDate *g_lastDump = nil;
+(void)gotTimer:(NSTimer *)timer
{
[self dumpAllocs];
}
+(void)startTimer
{
static int count = 0;
g_tmr = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:@selector(gotTimer:) userInfo:@(count++) repeats:YES];
NSLog(@"starting timer %i", count);
}
+(void)cancelTimer
{
[g_tmr invalidate];
g_tmr = nil;
}
+(void)dumpAllocs
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
NSMutableString *str = [NSMutableString string];
[str appendString:@"\n#$# ========== Non-freed UIImages =========\n"];
NSMutableArray *sorted = [NSMutableArray array];
//make sure map is not changed while enumerating
static int s_ts_start = -1;
@synchronized (g_allocsMap) {
NSEnumerator *keysEnum = [g_allocsMap keyEnumerator];
UIImage *img;
while (img = [keysEnum nextObject]) {
NSString *value = [g_allocsMap objectForKey:img];
if (value) { //might be nulled because declared as weak
NSUInteger memUsed = CGImageGetHeight(img.CGImage) * CGImageGetBytesPerRow(img.CGImage);
NSString *objData = [NSString stringWithFormat:@"mem=%5ikb, size=%4ix%-4i", (int)(memUsed/1024), (int)img.size.width, (int)img.size.height];
NSString *line = [NSString stringWithFormat:@"%p - %@ [%@]\n", img, objData, value];
if (s_ts_start<0) {
s_ts_start = (int)[line rangeOfString:@"["].location + 1;
}
if (line.length > (s_ts_start+10)) {
[sorted addObject:line];
}
}
}
}
if (sorted.count > 0) {
[sorted sortUsingComparator: ^NSComparisonResult(NSString *s1, NSString *s2)
{
//we expect '0x15a973700 - mem=3600kb, size=640x360 [16:14:27.5: UIIma...'
NSString *ts1 = [s1 substringWithRange:NSMakeRange(s_ts_start, 10)];
NSString *ts2 = [s2 substringWithRange:NSMakeRange(s_ts_start, 10)];
return [ts1 compare:ts2];
}];
int ix = 0;
for (NSString *line in sorted) {
[str appendFormat:@"#$# %3i) %@", ix++, line];
}
}
[str appendString:@"#$# ======================================================\n"];
NSLog(@"%@", str);
});
}
+(instancetype)alloc
{
NSString *caller = [UIApplication getCaller:4];
@synchronized (self) {
id obj = [super alloc];
NSLog(@"#$# UIImage alloc: [%p], caller=[%@]", obj, caller);
NSDate *now = [NSDate date];
NSString *value = [NSString stringWithFormat:@"%@: %@", [UIApplication dateToTimestamp:now], caller];
if (!g_allocsMap) {
g_allocsMap = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory];
}
[g_allocsMap setObject:value forKey:obj];
if (!g_lastDump) {
[self startTimer];
g_lastDump = now;
}
return obj;
}
}
-(void)dealloc
{
NSLog(@"#$# UIImage dealloc: [%@]", self);
}
@end
#endif //TRACK_ALLOCATIONS
如何运作? 我们创建了一个UIImage类,并为 alloc 和 dealloc 设置了我们自己的版本。每个分配的对象都保存在一个NSMapTable对象中,该对象的工作方式类似于字典,但允许存储带有弱指针的对象。
为方便起见,我们在UIApplication下添加了两个方法,如果创建了适当的头文件,其他模块可以使用它们。一种方法是格式化时间戳,另一种方法用于读取调用堆栈(仅适用于调试版本)。
使用提示:
如果您使用真实设备并安装 idevicesyslog (brew install libimobiledevice
),您可以使用终端查看所有分配调试,如下所示:
idevicesyslog | grep "#\$#"