我們可以使用儀器進行各種分析。但是許多程序員認爲這個工具太複雜,太重而不能帶來真正的價值。iOS:分配對象的跟蹤量
是否有一種簡單的方法來跟蹤特定類的所有對象,併爲每個對象確切知道誰正在分配它們並驗證它們是否被正確釋放?
答案是肯定的!有一種方法,我會在我的答案演示它下面
我們可以使用儀器進行各種分析。但是許多程序員認爲這個工具太複雜,太重而不能帶來真正的價值。iOS:分配對象的跟蹤量
是否有一種簡單的方法來跟蹤特定類的所有對象,併爲每個對象確切知道誰正在分配它們並驗證它們是否被正確釋放?
答案是肯定的!有一種方法,我會在我的答案演示它下面
跟蹤分配容易:
使用方法:你可以把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的類別,併爲alloc和dealloc設置了我們自己的版本。每個分配的對象被保存到NSMapTable對象中,它像字典一樣工作,但允許存儲具有弱指針的對象。
爲方便起見,我們在UIApplication下添加了兩個方法,如果創建了合適的頭文件,其他模塊可以使用它們。一種方法是格式化時間戳,另一種方法是讀取調用堆棧(僅適用於調試版本)。使用
提示: 如果您使用的是真實的設備和安裝idevicesyslog(brew install libimobiledevice
),您可以使用終端來查看所有配置的調試,像這樣:
idevicesyslog | grep "#\$#"