91

Is there a way to determine the line of code a certain method was called from?

pkamb
  • 33,281
  • 23
  • 160
  • 191
ennuikiller
  • 46,381
  • 14
  • 112
  • 137

12 Answers12

190

StackI hope that this helps:

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]);
pkamb
  • 33,281
  • 23
  • 160
  • 191
intropedro
  • 2,804
  • 1
  • 24
  • 25
  • 1
    Also made a macro in the -Prefix.pch file and then ran it from the app delegate. Interestingly, class caller was: "" – Melvin Sovereign Aug 06 '13 at 17:34
  • 5
    in my case, there is nothing at index 5. Thus this code crashed my app. it worked after removing the last line. Nonetheless, it's still so awesome that it's worth +1! – Brian Jan 22 '14 at 03:46
  • 1
    This works great, but how do we interpret the "line caller"? In my case, it shows a number, for example 91, but why is it 91? If I move the call one instruction below, it will show 136... So how is this number computed? – Maxim Chetrusca Feb 06 '14 at 19:51
  • @Pétur If there is an effect on performance it is negligible, NSThread already has that information, you are basically just accessing an array and creating a new one. – Oscar Gomez Jun 27 '14 at 14:02
  • 2
    It's working in debug mode, but after archiving it to IPA package, the call stack is not working as expected. I just got "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" should be "AAMAgentAppDelegate application:didFinishLaunchingWithOptions:" – Alanc Liu May 08 '19 at 08:55
  • How do I find the caller of the method when it crashes? It crashes on a method in a framework. And I don't think I am calling this method. It crashes only on iOS14 only and I am not able to find the origin. – nOOb iOS Aug 27 '20 at 09:32
  • 1
    Beware of this approach as it doesn't work reliably. Breaks in production even though it works fine in the debugger. – Ortwin Gentz Aug 02 '21 at 16:06
53

In fully optimized code, there is no 100% surefire way to determine the caller to a certain method. The compiler may employ a tail call optimization whereas the compiler effectively re-uses the caller's stack frame for the callee.

To see an example of this, set a breakpoint on any given method using gdb and look at the backtrace. Note that you don't see objc_msgSend() before every method call. That is because objc_msgSend() does a tail call to each method's implementation.

While you could compile your application non-optimized, you would need non-optimized versions of all of the system libraries to avoid just this one problem.

And this is just but one problem; in effect, you are asking "how do I re-invent CrashTracer or gdb?". A very hard problem upon which careers are made. Unless you want "debugging tools" to be your career, I would recommend against going down this road.

What question are you really trying to answer?

bbum
  • 162,346
  • 23
  • 271
  • 359
  • 3
    OH MY GOD. This brought me back to earth. Almost literally. I was solving a completely, unrelated problem. Thank you SIR! – nimeshdesai Nov 19 '14 at 07:21
11

Using answer provided by intropedro, I came up with this:

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

which will simply return me Original class and function:

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

p.s. - if function is called using performSelector, result will be:

Origin: [NSObject performSelector:withObject:]
Guntis Treulands
  • 4,764
  • 2
  • 50
  • 72
  • 2
    * But be aware, that in some cases, it does not contain function name, neither perform selector, and thus - Calling CALL_ORIGIN crashes. (SO, I advise - if you are gonna use this example, use it temporary and then remove it.) – Guntis Treulands Mar 23 '15 at 15:43
6

Just wrote a method that will do this for you:

- (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
  • 3,319
  • 2
  • 27
  • 43
6

The Swift 2.0 version of @Intropedro's answer for reference;

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
  • 3,107
  • 1
  • 28
  • 53
5

If it is for debbuging sake, get to the habit of putting a NSLog(@"%s", __FUNCTION__);

As the first line inside each method in your classes. Then you can always know the order of method calls from looking at the debugger.

Giovanni
  • 257
  • 5
  • 13
4

You can pass self as one of the arguments to the function and then get the classname of the caller object inside:

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

//...

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

This way you can pass it any object that would help you to determine where the problem might be.

pckill
  • 3,709
  • 36
  • 48
3

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

In the output window you will see something like the following.

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

You can also parse this string to extract more data about the 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

It was taken from Identify Calling Method in iOS.

Matt H
  • 6,422
  • 2
  • 28
  • 32
DannyBios
  • 81
  • 6
3

A slightly optimized version of @Roy Kronenfeld's fantastic answer:

- (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
  • 1,344
  • 1
  • 12
  • 20
2

The Swift 4 version of @Geoff H answer for copy and pasting ;]

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
  • 12,614
  • 4
  • 41
  • 62
0

The Swift 3 version of @Geoff H answer for reference:

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
  • 273
  • 4
  • 12
0

Back in the days there where no dot syntax in objective-C, so nowadays it could look like.

#define __UGLY__CALLEE__(idx) fprintf(stderr,"\n%s <- %s",__PRETTY_FUNCTION__,(NSThread.callStackSymbols.count>idx?((NSString*)NSThread.callStackSymbols[idx]).UTF8String:"no callStackSymbol with this index"))

just prints what is needed, no extra re-creation of NSArray or Mutables. Apart from the characters to output and an index to chose this lets you repeat with different stack symbols and prints without timestamp. Extra formatting the output does not only loose performance until you get what you need to know about your method calls it also makes the thing kinda un-flexible. And most important not introducing another method call to self just to ask for the last callee.

__UGLY__CALLEE__(1); results in...

-[Some inspectedMethod] <- 1   Appname                             0x00000001000e6cd2 -[SomeCallee method] + 1234

And as it is not pretty - it is called ugly.

Ol Sen
  • 3,163
  • 2
  • 21
  • 30