iOS:跟踪已分配对象的数量

时间:2016-04-21 14:05:04

标签: ios

我们可以使用乐器进行各种分析。但是,许多程序员发现这个工具太复杂,太重,无法带来真正的价值。

是否有一种简单的方法可以跟踪特定类的所有对象,并且每个对象都知道究竟是谁分配它们并验证它们是否正确释放?

答案是肯定的!有一种方法,我将在下面的答案中进行演示

1 个答案:

答案 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 "#\$#"