Цель C найти вызывающего метода
Есть ли способ определить строку кода определенной method
был вызван из?
12 ответов
Stack Я надеюсь, что это поможет:
NSString *sourceString = [[NSThread callStackSymbols] objectAtIndex:1];
// Example: 1 UIKit 0x00540c89 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163
NSCharacterSet *separatorSet = [NSCharacterSet characterSetWithCharactersInString:@" -[]+?.,"];
NSMutableArray *array = [NSMutableArray arrayWithArray:[sourceString componentsSeparatedByCharactersInSet:separatorSet]];
[array removeObject:@""];
NSLog(@"Stack = %@", [array objectAtIndex:0]);
NSLog(@"Framework = %@", [array objectAtIndex:1]);
NSLog(@"Memory address = %@", [array objectAtIndex:2]);
NSLog(@"Class caller = %@", [array objectAtIndex:3]);
NSLog(@"Function caller = %@", [array objectAtIndex:4]);
В полностью оптимизированном коде нет 100% надежного способа определения вызывающего абонента для определенного метода. Компилятор может использовать оптимизацию хвостового вызова, тогда как компилятор эффективно повторно использует кадр стека вызывающего для вызываемого.
Чтобы увидеть пример этого, установите точку останова для любого данного метода, используя gdb, и посмотрите на обратную трассировку. Обратите внимание, что вы не видите objc_msgSend() перед каждым вызовом метода. Это потому, что objc_msgSend() выполняет хвостовой вызов для реализации каждого метода.
Хотя вы можете скомпилировать ваше приложение неоптимизированным, вам понадобятся неоптимизированные версии всех системных библиотек, чтобы избежать одной этой проблемы.
И это только одна проблема; фактически вы спрашиваете, "как мне изобрести CrashTracer или gdb?". Очень сложная проблема, на которой делается карьера. Если вы не хотите, чтобы "инструменты отладки" были вашей карьерой, я бы не советовал идти по этому пути.
На какой вопрос вы действительно пытаетесь ответить?
Используя ответ, предоставленный intropedro, я придумал это:
#define CALL_ORIGIN NSLog(@"Origin: [%@]", [[[[NSThread callStackSymbols] objectAtIndex:1] componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] objectAtIndex:1])
который просто вернет мне оригинальный класс и функцию:
2014-02-04 16:49:25.384 testApp[29042:70b] Origin: [LCallView addDataToMapView]
ps - если функция вызывается с помощью executeSelector, результатом будет:
Origin: [NSObject performSelector:withObject:]
Версия Swift 2.0 @Intropedro для справки;
let sourceString: String = NSThread.callStackSymbols()[1]
let separatorSet :NSCharacterSet = NSCharacterSet(charactersInString: " -[]+?.,")
let array = NSMutableArray(array: sourceString.componentsSeparatedByCharactersInSet(separatorSet))
array.removeObject("")
print("Stack: \(array[0])")
print("Framework:\(array[1])")
print("Memory Address:\(array[2])")
print("Class Caller:\(array[3])")
print("Method Caller:\(array[4])")
Просто написал метод, который сделает это за вас:
- (NSString *)getCallerStackSymbol {
NSString *callerStackSymbol = @"Could not track caller stack symbol";
NSArray *stackSymbols = [NSThread callStackSymbols];
if(stackSymbols.count >= 2) {
callerStackSymbol = [stackSymbols objectAtIndex:2];
if(callerStackSymbol) {
NSMutableArray *callerStackSymbolDetailsArr = [[NSMutableArray alloc] initWithArray:[callerStackSymbol componentsSeparatedByString:@" "]];
NSUInteger callerStackSymbolIndex = callerStackSymbolDetailsArr.count - 3;
if (callerStackSymbolDetailsArr.count > callerStackSymbolIndex && [callerStackSymbolDetailsArr objectAtIndex:callerStackSymbolIndex]) {
callerStackSymbol = [callerStackSymbolDetailsArr objectAtIndex:callerStackSymbolIndex];
callerStackSymbol = [callerStackSymbol stringByReplacingOccurrencesOfString:@"]" withString:@""];
}
}
}
return callerStackSymbol;
}
Если это ради пользы отладки, привыкни ставить NSLog(@"%s", __FUNCTION__);
Как первая строка внутри каждого метода в ваших классах. Тогда вы всегда сможете узнать порядок вызовов методов, посмотрев на отладчик.
Вы можете пройти self
в качестве одного из аргументов функции, а затем получить имя класса объекта вызывающего внутри:
+(void)log:(NSString*)data from:(id)sender{
NSLog(@"[%@]: %@", NSStringFromClass([sender class]), data);
}
//...
-(void)myFunc{
[LoggerClassName log:@"myFunc called" from:self];
}
Таким образом, вы можете передать ему любой объект, который поможет вам определить, где может быть проблема.
Немного оптимизированная версия фантастического ответа @Roy Kronenfeld:
- (NSString *)findCallerMethod
{
NSString *callerStackSymbol = nil;
NSArray<NSString *> *callStackSymbols = [NSThread callStackSymbols];
if (callStackSymbols.count >= 2)
{
callerStackSymbol = [callStackSymbols objectAtIndex:2];
if (callerStackSymbol)
{
// Stack: 2 TerribleApp 0x000000010e450b1e -[TALocalDataManager startUp] + 46
NSInteger idxDash = [callerStackSymbol rangeOfString:@"-" options:kNilOptions].location;
NSInteger idxPlus = [callerStackSymbol rangeOfString:@"+" options:NSBackwardsSearch].location;
if (idxDash != NSNotFound && idxPlus != NSNotFound)
{
NSRange range = NSMakeRange(idxDash, (idxPlus - idxDash - 1)); // -1 to remove the trailing space.
callerStackSymbol = [callerStackSymbol substringWithRange:range];
return callerStackSymbol;
}
}
}
return (callerStackSymbol) ?: @"Caller not found! :(";
}
@ennuikiller
//Add this private instance method to the class you want to trace from
-(void)trace
{
//Go back 2 frames to account for calling this helper method
//If not using a helper method use 1
NSArray* stack = [NSThread callStackSymbols];
if (stack.count > 2)
NSLog(@"Caller: %@", [stack objectAtIndex:2]);
}
//Add this line to the method you want to trace from
[self trace];
В окне вывода вы увидите что-то вроде следующего.
Звонящий: 2 MyApp 0x0004e8ae -[IINClassroomInit buildMenu] + 86
Вы также можете проанализировать эту строку, чтобы извлечь больше данных о кадре стека.
2 = Thread id
My App = Your app name
0x0004e8ae = Memory address of caller
-[IINClassroomInit buildMenu] = Class and method name of caller
+86 = Number of bytes from the entry point of the caller that your method was called
Это было взято из Определить метод вызова в iOS.
Версия Swift 4 @Geoff H отвечает за копирование и вставку;]
let sourceString: String = Thread.callStackSymbols[1]
let separatorSet :CharacterSet = CharacterSet(charactersIn: " -[]+?.,")
var array = Array(sourceString.components(separatedBy: separatorSet))
array = array.filter { $0 != "" }
print("Stack: \(array[0])")
print("Framework:\(array[1])")
print("Memory Address:\(array[2])")
print("Class Caller:\(array[3])")
print("Method Caller:\(array[4])")
В те дни в Objective-C не было точечного синтаксиса, поэтому сейчас это может выглядеть так.
#define __UGLY__CALLEE__(idx) fprintf(stderr,"\n%s <- %s",__PRETTY_FUNCTION__,(NSThread.callStackSymbols.count>idx?((NSString*)NSThread.callStackSymbols[idx]).UTF8String:"no callStackSymbol with this index"))
просто печатает то, что нужно, без лишнего воссоздания NSArray или Mutables. Помимо символов для вывода и индекса для выбора, это позволяет вам повторять с разными символами стека и печатать без отметки времени. Дополнительное форматирование вывода не только снижает производительность до тех пор, пока вы не получите то, что вам нужно знать о вызовах своих методов, но и делает это отчасти не гибким. И, что наиболее важно, не вводить другой вызов метода для self только для того, чтобы запросить последнего вызываемого.
__UGLY__CALLEE__(1);
приводит к ...
-[Some inspectedMethod] <- 1 Appname 0x00000001000e6cd2 -[SomeCallee method] + 1234
А как некрасиво - некрасиво называется.
Версия Swift 3 @Geoff H для справки:
let sourceString: String = Thread.callStackSymbols[1]
let separatorSet: CharacterSet = CharacterSet(charactersIn: " -[]+?.,")
let array = NSMutableArray(array: sourceString.components(separatedBy: separatorSet))
array.remove("")
print("Stack: \(array[0])")
print("Framework:\(array[1])")
print("Memory Address:\(array[2])")
print("Class Caller:\(array[3])")
print("Method Caller:\(array[4])")