90

有没有办法确定method调用某个代码的行?

4

12 回答 12

189

Stack我希望这会有所帮助:

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]);
于 2012-03-07T14:55:00.297 回答
52

在完全优化的代码中,没有 100% 可靠的方法来确定某个方法的调用者。编译器可以使用尾调用优化,而编译器有效地为被调用者重用调用者的堆栈帧。

要查看此示例,请使用 gdb 在任何给定方法上设置断点并查看回溯。请注意,您不会在每次方法调用之前看到 objc_msgSend()。那是因为 objc_msgSend() 对每个方法的实现进行了尾调用。

虽然您可以编译未优化的应用程序,但您需要所有系统库的未优化版本才能避免这一问题。

这只是一个问题;实际上,您是在问“我如何重新发明 CrashTracer 或 gdb?”。一个非常困难的问题,职业生涯是在这个问题上产生的。除非您希望“调试工具”成为您的职业,否则我建议您不要走这条路。

你真正想回答什么问题?

于 2009-09-20T16:52:15.123 回答
11

使用intropedro提供的答案,我想出了这个:

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

这将简单地返回我原来的类和函数:

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

ps - 如果使用 performSelector 调用函数,结果将是:

Origin: [NSObject performSelector:withObject:]
于 2014-02-04T14:56:17.703 回答
6

刚刚写了一个方法可以为你做到这一点:

- (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;
}
于 2015-09-08T12:43:08.170 回答
6

@Intropedro 的答案的 Swift 2.0 版本供参考;

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])")
于 2015-11-07T18:07:16.233 回答
5

如果是为了调试,养成放一个 NSLog(@"%s", __FUNCTION__);

作为类中每个方法的第一行。然后你总是可以通过查看调试器知道方法调用的顺序。

于 2012-10-11T00:01:31.183 回答
4

您可以将self作为参数之一传递给函数,然后在内部获取调用者对象的类名:

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

//...

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

通过这种方式,您可以将任何可以帮助您确定问题所在的对象传递给它。

于 2013-07-04T08:47:03.513 回答
3

@ennukiller

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

在输出窗口中,您将看到类似以下内容。

来电者:2 MyApp 0x0004e8ae -[IINClassroomInit buildMenu] + 86

您还可以解析此字符串以提取有关堆栈帧的更多数据。

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

它取自iOS 中的识别调用方法

于 2013-12-26T03:08:35.597 回答
3

@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! :(";
}
于 2015-11-16T07:29:27.507 回答
2

@Geoff H 的 Swift 4 版本的复制和粘贴答案;]

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])")
于 2018-02-09T16:45:26.490 回答
0

Swift 3 版本的@Geoff H 答案供参考:

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])")
于 2017-03-24T10:32:12.667 回答
0

回到过去,objective-C 中没有点语法,所以现在它看起来像。

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

只打印需要的内容,无需额外重新创建 NSArray 或 Mutables。除了要输出的字符和要选择的索引之外,您还可以使用不同的堆栈符号重复并打印没有时间戳。对输出进行额外的格式化不仅会降低性能,直到您了解您需要了解的有关方法调用的信息,它也会使事情变得不灵活。最重要的是不要引入另一个方法调用self只是为了请求最后一个被调用者。

__UGLY__CALLEE__(1);结果是...

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

而且因为它不漂亮 - 它被称为丑陋。

于 2021-06-16T23:51:11.223 回答