2016-04-21 104 views
0

我們可以使用儀器進行各種分析。但是許多程序員認爲這個工具太複雜,太重而不能帶來真正的價值。iOS:分配對象的跟蹤量

是否有一種簡單的方法來跟蹤特定類的所有對象,併爲每個對象確切知道誰正在分配它們並驗證它們是否被正確釋放?

答案是肯定的!有一種方法,我會在我的答案演示它下面

回答

0

跟蹤分配容易:

使用方法:你可以把150行代碼下面到一個名爲AllocTracker文件.m將它拖動到您的項目文件中。 使用Xcode右側窗格中的複選框在編譯目標中啓用/禁用它。

你會得到什麼? 啓用時,此模塊將跟蹤所有對象的分配和釋放,並記錄它們。 (它可以很容易地修改爲跟蹤其他類)。

除了記錄每個分配和釋放,它會定期(當前每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的類別,併爲allocdealloc設置了我們自己的版本。每個分配的對象被保存到NSMapTable對象中,它像字典一樣工作,但允許存儲具有弱指針的對象。

爲方便起見,我們在UIApplication下添加了兩個方法,如果創建了合適的頭文件,其他模塊可以使用它們。一種方法是格式化時間戳,另一種方法是讀取調用堆棧(僅適用於調試版本)。使用

提示: 如果您使用的是真實的設備和安裝idevicesyslogbrew install libimobiledevice),您可以使用終端來查看所有配置的調試,像這樣:

idevicesyslog | grep "#\$#"