web-dev-qa-db-fra.com

Objective C trouver l'appelant de la méthode

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

84
ennuikiller

StackI espère que cela aide:

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

Dans un code entièrement optimisé, il n’existe aucun moyen sûr à 100% de déterminer l’appelant d’une méthode donnée. Le compilateur peut utiliser une optimisation de l'appel final, tandis que le compilateur réutilise efficacement le cadre de pile de l'appelant pour l'appelé.

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

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

Et ce n’est qu’un problème; en fait, vous vous demandez "comment puis-je 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?

49
bbum

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 rendra simplement la classe et la fonction d'origine:

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

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

Origin: [NSObject performSelector:withObject:]
9
Guntis Treulands

La version Swift 2.0 de la réponse de @ Intropedro à titre de 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])")
6
Geoff H

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

En tant que première ligne de chaque méthode dans vos classes. Ensuite, vous pouvez toujours connaître l'ordre des appels de méthode en regardant le débogueur.

5
Giovanni

Je viens d'écrire une méthode qui le fera 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;
}
5
Roy K

Vous pouvez passer self comme un des arguments de 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 passer n'importe quel objet qui pourrait vous aider à déterminer où pourrait se trouver le problème.

4
pckill

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! :(";
}
2
Andrew

@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 davantage de données sur le cadre de la 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é extrait de Identify Calling Method in iOS .

2
DannyBios

La version Swift 4 de @ Geoff H answer 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])")
1
Andy Obusek

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