Czy istnieje sposób na określenie wiersza kodu, z którego method
został wywołany określony ?
Czy istnieje sposób na określenie wiersza kodu, z którego method
został wywołany określony ?
Odpowiedzi:
Mam nadzieję, że to pomoże:
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]);
W całkowicie zoptymalizowanym kodzie nie ma w 100% pewnego sposobu na określenie wywołującego określoną metodę. Kompilator może zastosować optymalizację wywołań końcowych, podczas gdy kompilator efektywnie ponownie wykorzystuje ramkę stosu wywołującego dla wywoływanego.
Aby zobaczyć przykład tego, ustaw punkt przerwania dla dowolnej metody za pomocą gdb i spójrz na ślad. Zauważ, że nie widzisz objc_msgSend () przed każdym wywołaniem metody. Dzieje się tak, ponieważ objc_msgSend () wykonuje wywołanie tail do implementacji każdej metody.
Chociaż możesz skompilować aplikację niezoptymalizowaną, potrzebujesz niezoptymalizowanych wersji wszystkich bibliotek systemowych, aby uniknąć tylko tego jednego problemu.
A to tylko jeden problem; w efekcie pytasz „jak ponownie wynaleźć CrashTracer lub gdb?”. Bardzo trudny problem, na którym opiera się kariera. Jeśli nie chcesz, aby „narzędzia do debugowania” były Twoją karierą, odradzałbym pójście tą drogą.
Na jakie pytanie naprawdę próbujesz odpowiedzieć?
Korzystając z odpowiedzi udzielonej przez intropedro , wymyśliłem to:
#define CALL_ORIGIN NSLog(@"Origin: [%@]", [[[[NSThread callStackSymbols] objectAtIndex:1] componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] objectAtIndex:1])
co po prostu zwróci mi oryginalną klasę i funkcję:
2014-02-04 16:49:25.384 testApp[29042:70b] Origin: [LCallView addDataToMapView]
ps - jeśli funkcja zostanie wywołana przy użyciu performSelector, wynikiem będzie:
Origin: [NSObject performSelector:withObject:]
Właśnie napisałem metodę, która zrobi to za Ciebie:
- (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;
}
Wersja Swift 2.0 odpowiedzi @ Intropedro w celach informacyjnych;
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])")
Jeśli jest to ze względu na debbugowanie, przywyknij do umieszczania pliku NSLog(@"%s", __FUNCTION__);
Jako pierwsza linia w każdej metodzie w Twoich klasach. Wtedy zawsze możesz poznać kolejność wywołań metod, patrząc na debuger.
Możesz przekazać self
jako jeden z argumentów do funkcji, a następnie pobrać nazwę klasy obiektu wywołującego wewnątrz:
+(void)log:(NSString*)data from:(id)sender{
NSLog(@"[%@]: %@", NSStringFromClass([sender class]), data);
}
//...
-(void)myFunc{
[LoggerClassName log:@"myFunc called" from:self];
}
W ten sposób możesz przekazać mu dowolny obiekt, który pomoże ci określić, gdzie może być problem.
Nieco zoptymalizowana wersja fantastycznej odpowiedzi @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];
W oknie wyjściowym zobaczysz coś takiego jak poniżej.
Dzwoniący: 2 MyApp 0x0004e8ae - [IINClassroomInit buildMenu] + 86
Możesz również przeanalizować ten ciąg, aby wyodrębnić więcej danych o ramce stosu.
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
Został zaczerpnięty z Identify Calling Method w iOS .
Odpowiedź @Geoff H w wersji Swift 4 do kopiowania i wklejania ;]
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])")
Odpowiedź @Geoff H w wersji Swift 3 w celach informacyjnych:
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])")