Objective C trouve l'appelant de la méthode

90

Existe-t-il un moyen de déterminer la ligne de code à partir de laquelle un certain a methodété appelé?

ennuikiller
la source
Pourquoi veux-tu faire cela? Si c'est pour le débogage, il y a un ensemble de réponses tout à fait différent que si vous voulez le faire en production (pour lequel la réponse est plus probable "ne pas".)
Nicholas Riley
4
Je vais prendre la réponse de débogage
ennuikiller
3
Y a-t-il une réponse de production?
Hari Karam Singh

Réponses:

188

J'espère que cela aidera:

    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]);
intropédro
la source
1
Également créé une macro dans le fichier -Prefix.pch, puis l'exécute à partir du délégué d'application. Fait intéressant, l'appelant de la classe était: "<expurgé>"
Melvin Sovereign
4
dans mon cas, il n'y a rien à l'index 5. Ainsi, ce code a planté mon application. cela a fonctionné après avoir supprimé la dernière ligne. Néanmoins, c'est toujours tellement génial que ça vaut +1!
Brian
1
Cela fonctionne très bien, mais comment interpréter «l'appelant de ligne»? Dans mon cas, il montre un nombre, par exemple 91, mais pourquoi est-ce 91? Si je déplace l'instruction d'appel une instruction ci-dessous, elle affichera 136 ... Alors, comment ce nombre est-il calculé?
Maxim Chetrusca
@ Pétur S'il y a un effet sur les performances, il est négligeable, NSThread a déjà cette information, vous accédez simplement à un tableau et en créez un nouveau.
Oscar Gomez
Il fonctionne en mode débogage, mais après son archivage dans le package IPA, la pile d'appels ne fonctionne pas comme prévu. Je viens de recevoir "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" devrait être "Application AAMAgentAppDelegate: didFinishLaunchingWithOptions:"
Alanc Liu
50

Dans un code entièrement optimisé, il n'y a pas de moyen sûr à 100% de déterminer l'appelant d'une certaine méthode. Le compilateur peut utiliser une optimisation des appels de fin tandis que le compilateur réutilise effectivement la trame de pile de l'appelant pour l'appelé.

Pour voir un exemple de cela, définissez un point d'arrêt sur une méthode donnée à l'aide de gdb et regardez la trace. Notez que vous ne voyez pas objc_msgSend () avant chaque appel de méthode. C'est parce que objc_msgSend () fait un appel final à l'implémentation de chaque méthode.

Bien que vous puissiez compiler votre application non optimisée, vous auriez besoin de versions non optimisées de toutes les bibliothèques système pour éviter ce seul problème.

Et ce n'est qu'un problème; en effet, vous vous demandez "comment réinventer CrashTracer ou gdb?". Un problème très difficile sur lequel les carrières sont faites. À moins que vous ne vouliez que les "outils de débogage" soient votre carrière, je vous déconseille de suivre cette voie.

À quelle question essayez-vous vraiment de répondre?

bbum
la source
3
OH MON DIEU. Cela m'a ramené sur terre. Presque littéralement. Je résolvais un problème totalement indépendant. Merci Monsieur!
nimeshdesai
11

En utilisant la réponse fournie par intropedro , j'ai trouvé ceci:

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

qui me renverra simplement Classe et fonction d'origine:

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

ps - si la fonction est appelée à l'aide de performSelector, le résultat sera:

Origin: [NSObject performSelector:withObject:]
Guntis Treulands
la source
2
* Mais sachez que dans certains cas, il ne contient pas de nom de fonction, ni d'exécuter le sélecteur, et donc - L'appel de CALL_ORIGIN plante. (SO, je vous conseille - si vous comptez utiliser cet exemple, utilisez-le temporairement puis supprimez-le.)
Guntis Treulands
6

Je viens d'écrire une méthode qui fera cela pour vous:

- (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
la source
6

La version Swift 2.0 de la réponse de @ Intropedro pour référence;

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
la source
5

Si c'est pour des raisons de débogage, prenez l'habitude de mettre un NSLog(@"%s", __FUNCTION__);

Comme première ligne à l'intérieur de chaque méthode de vos classes. Ensuite, vous pouvez toujours connaître l'ordre des appels de méthode en regardant le débogueur.

Giovanni
la source
D'une manière ou d'une autre, le code n'apparaît pas correctement. Il y a deux traits de soulignement avant et après FUNCTION
Giovanni
essayez d'utiliser les échappements backtick (`) pour entourer votre code afin qu'il puisse être affiché correctement
howanghk
3
Ou mieux utiliser __PRETTY_FUNCTION__ qui prend également en charge Objective-C et affiche le nom de l'objet avec la méthode.
Ben Sinclair
4

Vous pouvez passer selfcomme l'un des arguments à la fonction, puis obtenir le nom de classe de l'objet appelant à l'intérieur:

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

//...

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

De cette façon, vous pouvez lui transmettre n'importe quel objet qui vous aiderait à déterminer où se situe le problème.

pckill
la source
3

Une version légèrement optimisée de la réponse fantastique 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
la source
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];

Dans la fenêtre de sortie, vous verrez quelque chose comme ce qui suit.

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

Vous pouvez également analyser cette chaîne pour extraire plus de données sur le cadre de pile.

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

Il a été tiré de Identify Calling Method dans iOS .

DannyBios
la source
2

La version Swift 4 de @Geoff H répond pour copier-coller ;]

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
la source
0

La version Swift 3 de @Geoff H répond pour référence:

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
la source