L'obiettivo C trova il chiamante del metodo


90

C'è un modo per determinare la riga di codice da cui è methodstato chiamato un determinato ?


Perchè vuoi fare questo? Se è per il debug, c'è un insieme di risposte abbastanza diverso rispetto a se lo desideri in produzione (per il quale la risposta è più probabile "non".)
Nicholas Riley,

4
Prenderò la risposta di debug
ennuikiller

3
C'è una risposta alla produzione?
Hari Karam Singh

Risposte:


188

Mi auguro che questo aiuti:

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

1
Inoltre ha creato una macro nel file -Prefix.pch e poi l'ha eseguita dal delegato dell'app. È interessante notare che il partecipante al corso era: "<redacted>"
Melvin Sovereign

4
nel mio caso, non c'è niente all'indice 5. Quindi questo codice ha bloccato la mia app. ha funzionato dopo aver rimosso l'ultima riga. Tuttavia, è ancora così fantastico che vale +1!
Brian

1
Funziona benissimo, ma come interpretiamo il "chiamante di linea"? Nel mio caso, mostra un numero, ad esempio 91, ma perché è 91? Se sposto la chiamata di un'istruzione di seguito, mostrerà 136 ... Allora come viene calcolato questo numero?
Maxim Chetrusca

@ Pétur Se l'effetto sulle prestazioni è trascurabile, NSThread dispone già di tali informazioni, in pratica stai solo accedendo a un array e creandone uno nuovo.
Oscar Gomez

Funziona in modalità debug, ma dopo averlo archiviato nel pacchetto IPA, lo stack di chiamate non funziona come previsto. Ho appena ricevuto "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" dovrebbe essere "AAMAgentAppDelegate application: didFinishLaunchingWithOptions:"
Alanc Liu

50

Nel codice completamente ottimizzato, non esiste un modo sicuro al 100% per determinare il chiamante a un determinato metodo. Il compilatore può impiegare un'ottimizzazione della chiamata di coda mentre il compilatore riutilizza efficacemente lo stack frame del chiamante per il chiamato.

Per vedere un esempio di ciò, impostare un punto di interruzione su un dato metodo utilizzando gdb e guardare il backtrace. Nota che non vedi objc_msgSend () prima di ogni chiamata al metodo. Questo perché objc_msgSend () esegue una chiamata in coda all'implementazione di ciascun metodo.

Sebbene tu possa compilare la tua applicazione non ottimizzata, avresti bisogno di versioni non ottimizzate di tutte le librerie di sistema per evitare solo questo problema.

E questo è solo un problema; in effetti, stai chiedendo "come faccio a reinventare CrashTracer o gdb?". Un problema molto difficile su cui si fondano le carriere. A meno che tu non voglia che gli "strumenti di debug" siano la tua carriera, ti consiglio di non seguire questa strada.

A quale domanda stai davvero cercando di rispondere?


3
OH MIO DIO. Questo mi ha riportato sulla terra. Quasi letteralmente. Stavo risolvendo un problema completamente non correlato. Grazie Signore!
nimeshdesai

11

Usando la risposta fornita da intropedro , mi è venuta questa:

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

che mi restituirà semplicemente la classe e la funzione originali:

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

ps - se la funzione viene chiamata utilizzando performSelector, il risultato sarà:

Origin: [NSObject performSelector:withObject:]

2
* Ma tieni presente che in alcuni casi non contiene il nome della funzione, né esegue il selettore, e quindi - La chiamata a CALL_ORIGIN si blocca. (Quindi, ti consiglio - se vuoi usare questo esempio,
usalo

6

Ho appena scritto un metodo che lo farà per te:

- (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;
}

6

La versione Swift 2.0 della risposta di @ Intropedro per riferimento;

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])")

5

Se è per amor di debuttante, prendi l'abitudine di mettere a NSLog(@"%s", __FUNCTION__);

Come prima riga all'interno di ogni metodo nelle tue classi. Quindi puoi sempre conoscere l'ordine delle chiamate ai metodi guardando il debugger.


In qualche modo il codice non viene visualizzato correttamente. Ci sono due trattini bassi prima e dopo FUNCTION
Giovanni

prova a usare gli
escape backtick

3
O meglio usare __PRETTY_FUNCTION__ che supporta anche Objective-C e visualizza il nome dell'oggetto insieme al metodo.
Ben Sinclair

4

Puoi passare selfcome uno degli argomenti alla funzione e quindi ottenere il nome della classe dell'oggetto chiamante all'interno:

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

//...

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

In questo modo puoi passargli qualsiasi oggetto che ti aiuti a determinare dove potrebbe essere il problema.


3

Una versione leggermente ottimizzata della fantastica risposta di @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! :(";
}

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

Nella finestra di output vedrai qualcosa di simile al seguente.

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

Puoi anche analizzare questa stringa per estrarre più dati sullo stack frame.

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

È stato preso da Identify Calling Method in iOS .


2

La versione Swift 4 di @Geoff H risponde per copia e incolla ;]

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])")

0

La versione Swift 3 di @Geoff H risponde come riferimento:

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])")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.