2016-04-21 2 views
0

Мы можем использовать приборы для различных видов анализа. Но многие программисты считают этот инструмент слишком сложным и слишком тяжелым, чтобы принести реальную ценность.iOS: Отслеживание количества выделенных объектов

Есть ли простой способ отслеживания всех объектов определенного класса и для каждого знать, кто именно их выделял, и проверить, правильно ли они освобождаются?

Ответ да! есть способ, и я демо это в моем ответе ниже

ответ

0

распределений Tracking легко:

Как использовать: можно поместить 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 "#\$#"

Смежные вопросы