Objetivo C encontrar o chamador do método

90

Existe uma maneira de determinar a linha de código a partir da qual um determinado methodfoi chamado?

Ennuikiller
fonte
Por que você quer fazer isso? Se for para depuração, há um conjunto de respostas bem diferente do que se você quiser fazer na produção (para o qual a resposta é mais provavelmente "não".)
Nicholas Riley
4
Vou pegar a resposta de depuração
ennuikiller,
3
Existe uma resposta de produção?
Hari Karam Singh

Respostas:

188

StackEu espero que isso ajude:

    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
fonte
1
Também criou uma macro no arquivo -Prefix.pch e a executou a partir do delegado do aplicativo. Curiosamente, o chamador da turma foi: "<reigido>"
Melvin Sovereign
4
no meu caso, não há nada no índice 5. Portanto, esse código travou meu aplicativo. funcionou depois de remover a última linha. No entanto, ainda é tão incrível que vale a pena +1!
Brian
1
Isso funciona muito bem, mas como interpretamos o "chamador de linha"? No meu caso, mostra um número, por exemplo 91, mas por que é 91? Se eu mover a chamada de uma instrução abaixo, ela mostrará 136 ... Então, como esse número é calculado?
Maxim Chetrusca
@ Pétur Se houver um efeito no desempenho, ele é insignificante, o NSThread já tem essa informação, você está basicamente apenas acessando um array e criando um novo.
Oscar Gomez
Ele está funcionando no modo de depuração, mas após arquivá-lo no pacote IPA, a pilha de chamadas não está funcionando conforme o esperado. Acabei de receber "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" deveria ser "AAMAgentAppDelegate application: didFinishLaunchingWithOptions:"
Alanc Liu
50

Em um código totalmente otimizado, não existe uma maneira 100% infalível de determinar o chamador de um determinado método. O compilador pode empregar uma otimização de chamada final, enquanto o compilador reutiliza efetivamente o frame de pilha do chamador para o receptor.

Para ver um exemplo disso, defina um ponto de interrupção em qualquer método usando gdb e observe o backtrace. Observe que você não vê objc_msgSend () antes de cada chamada de método. Isso ocorre porque objc_msgSend () faz uma chamada final para a implementação de cada método.

Embora seja possível compilar seu aplicativo não otimizado, você precisaria de versões não otimizadas de todas as bibliotecas do sistema para evitar apenas este problema.

E este é apenas um problema; na verdade, você está perguntando "como faço para reinventar o CrashTracer ou o gdb?". Um problema muito difícil sobre o qual as carreiras são construídas. A menos que você queira que suas "ferramentas de depuração" sejam sua carreira, eu não recomendaria seguir esse caminho.

Que pergunta você realmente está tentando responder?

bbum
fonte
3
OH MEU DEUS. Isso me trouxe de volta à terra. Quase literalmente. Eu estava resolvendo um problema completamente não relacionado. Obrigado SIR!
nimeshdesai
11

Usando a resposta fornecida por intropedro , eu vim com o seguinte:

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

que simplesmente me retornará a classe e função originais:

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

ps - se a função for chamada usando performSelector, o resultado será:

Origin: [NSObject performSelector:withObject:]
Guntis Treulands
fonte
2
* Mas esteja ciente, que em alguns casos, ele não contém o nome da função, nem o seletor de execução, e assim - Calling CALL_ORIGIN trava. (Portanto, eu aconselho - se você for usar este exemplo, use-o temporariamente e, em seguida, remova-o.)
Guntis Treulands
6

Acabei de escrever um método que fará isso por você:

- (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
fonte
6

A versão Swift 2.0 da resposta de @Intropedro para referência;

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
fonte
5

Se for para depurar, adquira o hábito de colocar um NSLog(@"%s", __FUNCTION__);

Como a primeira linha dentro de cada método em suas classes. Assim, você sempre pode saber a ordem das chamadas de método olhando para o depurador.

Giovanni
fonte
De alguma forma, o código não está aparecendo corretamente. Existem dois sublinhados antes e depois da FUNÇÃO
Giovanni
tente usar escapes de crase (`) para encerrar seu código para que ele possa ser exibido corretamente
howanghk
3
Ou melhor usar __PRETTY_FUNCTION__ que também oferece suporte a Objective-C e exibe o nome do objeto junto com o método.
Ben Sinclair,
4

Você pode passar selfcomo um dos argumentos para a função e, em seguida, obter o nome da classe do objeto chamador dentro de:

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

//...

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

Dessa forma, você pode passar para ele qualquer objeto que o ajude a determinar onde pode estar o problema.

pckill
fonte
3

Uma versão ligeiramente otimizada da resposta fantástica 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! :(";
}
Andrew
fonte
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];

Na janela de saída, você verá algo como o seguinte.

Chamador: 2 MyApp 0x0004e8ae - [IINClassroomInit buildMenu] + 86

Você também pode analisar essa string para extrair mais dados sobre o quadro de pilha.

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

Foi retirado de Identificar método de chamada no iOS .

DannyBios
fonte
2

A versão Swift 4 de @Geoff H responde por copiar e colar ;]

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
fonte
0

A versão Swift 3 de @Geoff H responde para referência:

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
fonte