Objetivo C encontrar llamador del método

90

¿Hay alguna forma de determinar la línea de código methoddesde la que se llamó a un determinado ?

ennuikiller
fuente
¿Por qué quieres hacer esto? Si es para depurar, hay un conjunto de respuestas bastante diferente que si desea hacerlo en producción (para lo cual es más probable que la respuesta sea "no").
Nicholas Riley
4
Tomaré la respuesta de depuración
ennuikiller
3
¿Existe una respuesta de producción?
Hari Karam Singh

Respuestas:

188

Stack Espero que esto ayude:

    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]);
intropedro
fuente
1
También hizo una macro en el archivo -Prefix.pch y luego la ejecutó desde el delegado de la aplicación. Curiosamente, la persona que llamó a la clase fue: "<redactado>"
Melvin Sovereign
4
en mi caso, no hay nada en el índice 5. Por lo tanto, este código bloqueó mi aplicación. funcionó después de eliminar la última línea. No obstante, ¡sigue siendo tan increíble que vale +1!
Brian
1
Esto funciona muy bien, pero ¿cómo interpretamos el "llamador de línea"? En mi caso, muestra un número, por ejemplo 91, pero ¿por qué es 91? Si muevo la llamada una instrucción a continuación, mostrará 136 ... Entonces, ¿cómo se calcula este número?
Maxim Chetrusca
@ Pétur Si hay un efecto en el rendimiento, es insignificante, NSThread ya tiene esa información, básicamente está accediendo a una matriz y creando una nueva.
Oscar Gomez
Funciona en modo de depuración, pero después de archivarlo en el paquete IPA, la pila de llamadas no funciona como se esperaba. Acabo de recibir "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" debería ser "AAMAgentAppDelegate application: didFinishLaunchingWithOptions:"
Alanc Liu
50

En un código totalmente optimizado, no existe una forma 100% segura de determinar quién llama a un método determinado. El compilador puede emplear una optimización de llamada de cola mientras que el compilador reutiliza efectivamente el marco de pila del llamador para el destinatario.

Para ver un ejemplo de esto, establezca un punto de interrupción en cualquier método dado usando gdb y observe el backtrace. Tenga en cuenta que no ve objc_msgSend () antes de cada llamada a un método. Eso es porque objc_msgSend () hace una llamada final a la implementación de cada método.

Si bien podría compilar su aplicación no optimizada, necesitaría versiones no optimizadas de todas las bibliotecas del sistema para evitar solo este problema.

Y este es solo un problema; en efecto, se está preguntando "¿cómo reinvento CrashTracer o gdb?". Un problema muy duro sobre el que se hacen las carreras. A menos que desee que las "herramientas de depuración" sean su carrera, le recomendaría no seguir este camino.

¿Qué pregunta estás realmente tratando de responder?

bbum
fuente
3
OH DIOS MÍO. Esto me trajo de regreso a la tierra. Casi literalmente. Estaba resolviendo un problema completamente ajeno. ¡Gracias Señor!
nimeshdesai
11

Usando la respuesta proporcionada por intropedro , se me ocurrió esto:

#define CALL_ORIGIN NSLog(@"Origin: [%@]", [[[[NSThread callStackSymbols] objectAtIndex:1] componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] objectAtIndex:1])

que simplemente me devolverá la clase y función originales:

2014-02-04 16:49:25.384 testApp[29042:70b] Origin: [LCallView addDataToMapView]

ps: si se llama a la función mediante performSelector, el resultado será:

Origin: [NSObject performSelector:withObject:]
Guntis Treulands
fuente
2
* Pero tenga en cuenta que, en algunos casos, no contiene el nombre de la función, ni realiza el selector y, por lo tanto, la llamada CALL_ORIGIN se bloquea. (Entonces, le aconsejo, si va a usar este ejemplo, utilícelo temporalmente y luego elimínelo).
Guntis Treulands
6

Acabo de escribir un método que hará esto por ti:

- (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;
}
Roy K
fuente
6

La versión Swift 2.0 de la respuesta de @ Intropedro como referencia;

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])")
Geoff H
fuente
5

Si es por depurar errores, adquiera el hábito de poner un NSLog(@"%s", __FUNCTION__);

Como la primera línea dentro de cada método en sus clases. Entonces siempre puede saber el orden de las llamadas al método mirando al depurador.

Giovanni
fuente
De alguna manera, el código no aparece correctamente. Hay dos guiones bajos antes y después de FUNCTION
Giovanni
intente usar escapes de
comillas invertidas
3
O mejor use __PRETTY_FUNCTION__ que también es compatible con Objective-C y muestra el nombre del objeto junto con el método.
Ben Sinclair
4

Puede pasar selfcomo uno de los argumentos a la función y luego obtener el nombre de clase del objeto llamador dentro:

+(void)log:(NSString*)data from:(id)sender{
    NSLog(@"[%@]: %@", NSStringFromClass([sender class]), data);
}

//...

-(void)myFunc{
    [LoggerClassName log:@"myFunc called" from:self];
}

De esta manera, puede pasarle cualquier objeto que le ayude a determinar dónde podría estar el problema.

pastilla
fuente
3

Una versión ligeramente optimizada de la fantástica respuesta de @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! :(";
}
Andrés
fuente
2

@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];

En la ventana de salida verá algo como lo siguiente.

Persona que llama: 2 MyApp 0x0004e8ae - [IINClassroomInit buildMenu] + 86

También puede analizar esta cadena para extraer más datos sobre el marco de la pila.

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

Se tomó de Identify Calling Method en iOS .

DannyBios
fuente
2

La versión Swift 4 de @Geoff H responde para copiar y pegar ;]

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])")
Andy Obusek
fuente
0

La versión Swift 3 de @Geoff H responde como referencia:

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])")
Carmelo Gallo
fuente