Ziel C Aufruf der Methode finden

90

Gibt es eine Möglichkeit, die Codezeile zu bestimmen, aus der eine bestimmte methodaufgerufen wurde?

ennuikiller
quelle
Warum willst du das tun? Wenn es um das Debuggen geht, gibt es ganz andere Antworten als wenn Sie es in der Produktion tun möchten (für die die Antwort eher "nicht" lautet)
Nicholas Riley
4
Ich werde die Debugging-Antwort nehmen
ennuikiller
3
Gibt es eine Produktionsantwort?
Hari Karam Singh

Antworten:

188

StackIch hoffe, dass dies hilft:

    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
quelle
1
Außerdem wurde ein Makro in der Datei -Prefix.pch erstellt und dann vom App-Delegaten ausgeführt. Interessanterweise war Klassenanrufer: "<redacted>"
Melvin Sovereign
4
In meinem Fall gibt es bei Index 5 nichts. Somit hat dieser Code meine App zum Absturz gebracht. es funktionierte nach dem Entfernen der letzten Zeile. Trotzdem ist es immer noch so großartig, dass es +1 wert ist!
Brian
1
Das funktioniert gut, aber wie interpretieren wir den "Leitungsanrufer"? In meinem Fall wird eine Zahl angezeigt, z. B. 91, aber warum ist es 91? Wenn ich den Anruf um eine Anweisung weiter unten verschiebe, werden 136 angezeigt. Wie wird diese Nummer berechnet?
Maxim Chetrusca
@ Pétur Wenn sich dies auf die Leistung auswirkt, ist diese vernachlässigbar. NSThread verfügt bereits über diese Informationen. Sie greifen im Grunde nur auf ein Array zu und erstellen ein neues.
Oscar Gomez
Es funktioniert im Debug-Modus, aber nach der Archivierung im IPA-Paket funktioniert der Aufrufstapel nicht wie erwartet. Ich habe gerade "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136" erhalten, "_Z8isxdigiti" sollte "AAMAgentAppDelegate application: didFinishLaunchingWithOptions:" sein
Alanc Liu
50

In vollständig optimiertem Code gibt es keine 100% todsichere Möglichkeit, den Aufrufer für eine bestimmte Methode zu bestimmen. Der Compiler kann eine Tail-Call-Optimierung verwenden, während der Compiler den Stack-Frame des Aufrufers effektiv für den Angerufenen wiederverwendet.

Um ein Beispiel dafür zu sehen, setzen Sie mit gdb einen Haltepunkt für eine bestimmte Methode und sehen Sie sich die Rückverfolgung an. Beachten Sie, dass objc_msgSend () nicht vor jedem Methodenaufruf angezeigt wird. Dies liegt daran, dass objc_msgSend () einen Tail-Aufruf für die Implementierung jeder Methode ausführt.

Während Sie Ihre Anwendung nicht optimiert kompilieren könnten, würden Sie nicht optimierte Versionen aller Systembibliotheken benötigen, um nur dieses eine Problem zu vermeiden.

Und das ist nur ein Problem; Tatsächlich fragen Sie: "Wie erfinde ich CrashTracer oder gdb neu?". Ein sehr schwieriges Problem, auf dem Karrieren gemacht werden. Wenn Sie nicht möchten, dass "Debugging-Tools" Ihre Karriere sind, würde ich empfehlen, diesen Weg nicht einzuschlagen.

Welche Frage versuchst du wirklich zu beantworten?

bbum
quelle
3
OH MEIN GOTT. Dies brachte mich zurück auf die Erde. Fast wörtlich. Ich habe ein völlig unabhängiges Problem gelöst. Danke mein Herr!
Nimeshdesai
11

Mit der Antwort von intropedro kam ich auf Folgendes :

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

das wird mir einfach zurückgeben Ursprüngliche Klasse und Funktion:

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

ps - Wenn die Funktion mit performSelector aufgerufen wird, lautet das Ergebnis:

Origin: [NSObject performSelector:withObject:]
Guntis Treulands
quelle
2
* Beachten Sie jedoch, dass es in einigen Fällen keinen Funktionsnamen enthält und keine Auswahl ausführt. Daher stürzt der Aufruf von CALL_ORIGIN ab. (Also, ich rate - wenn Sie dieses Beispiel verwenden wollen, verwenden Sie es vorübergehend und entfernen Sie es dann.)
Guntis Treulands
6

Ich habe gerade eine Methode geschrieben, die dies für Sie erledigt:

- (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.
quelle
6

Die Swift 2.0-Version von @ Intropedros Antwort als Referenz;

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.
quelle
5

Wenn es um das Debbuging geht, gewöhnen Sie sich an, ein NSLog(@"%s", __FUNCTION__);

Als erste Zeile in jeder Methode in Ihren Klassen. Dann können Sie die Reihenfolge der Methodenaufrufe immer anhand des Debuggers erkennen.

Giovanni
quelle
Irgendwie wird der Code nicht richtig angezeigt. Es gibt zwei Unterstriche vor und nach FUNCTION
Giovanni
Versuchen Sie, Backtick-Escapezeichen (`) zu verwenden, um Ihren Code einzuschließen, damit er ordnungsgemäß angezeigt werden kann
howanghk
3
Oder verwenden Sie besser __PRETTY_FUNCTION__, das auch Objective-C unterstützt und den Objektnamen zusammen mit der Methode anzeigt.
Ben Sinclair
4

Sie können selfals eines der Argumente an die Funktion übergeben und dann den Klassennamen des Aufruferobjekts darin abrufen:

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

//...

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

Auf diese Weise können Sie jedes Objekt übergeben, mit dessen Hilfe Sie feststellen können, wo das Problem liegen könnte.

pckill
quelle
3

Eine leicht optimierte Version von @Roy Kronenfelds fantastischer Antwort:

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

Im Ausgabefenster sehen Sie ungefähr Folgendes.

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

Sie können diese Zeichenfolge auch analysieren, um weitere Daten zum Stapelrahmen zu extrahieren.

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

Es wurde aus der Anrufmethode identifizieren in iOS übernommen .

DannyBios
quelle
2

Die Swift 4-Version von @Geoff H antwortet zum Kopieren und Einfügen ;]

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

Die Swift 3-Version von @Geoff H als Referenz:

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
quelle