【问题标题】:Objective-C find caller of methodObjective-C 查找方法的调用者
【发布时间】:2009-09-20 15:57:03
【问题描述】:

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

【问题讨论】:

  • 为什么要这样做?如果是用于调试,则与您想在生产环境中进行调试的答案完全不同(答案很可能是“不要”。)
  • 我来调试答案
  • 有生产答案吗?

标签: ios objective-c debugging nsthread


【解决方案1】:

堆栈我希望这会有所帮助:

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

【讨论】:

  • 还在 -Prefix.pch 文件中创建了一个宏,然后从应用程序委托中运行它。有趣的是,类调用者是:“
  • 就我而言,索引 5 处没有任何内容。因此,此代码使我的应用程序崩溃。删除最后一行后它起作用了。尽管如此,它仍然非常棒,值得+1!
  • 这很好用,但是我们如何解释“line caller”呢?就我而言,它显示一个数字,例如 91,但为什么是 91?如果我将 call one 指令移到下面,它会显示 136... 那么这个数字是如何计算的呢?
  • @Pétur 如果对性能有影响,则可以忽略不计,NSThread 已经拥有该信息,您基本上只是访问一个数组并创建一个新数组。
  • 它在调试模式下工作,但在将其归档到 IPA 包后,调用堆栈无法按预期工作。我刚刚得到“callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136”,“_Z8isxdigiti”应该是“AAMAgentAppDelegate application:didFinishLaunchingWithOptions:”
【解决方案2】:

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

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

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

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

你真正想回答什么问题?

【讨论】:

  • 天哪。这让我回到了地球。几乎是字面意思。我正在解决一个完全不相关的问题。谢谢先生!
【解决方案3】:

使用 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]

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

Origin: [NSObject performSelector:withObject:]

【讨论】:

  • * 但请注意,在某些情况下,它不包含函数名称,也不执行选择器,因此 - 调用 CALL_ORIGIN 会崩溃。 (所以,我建议 - 如果您要使用此示例,请暂时使用它,然后将其删除。)
【解决方案4】:

刚刚编写了一个可以为您执行此操作的方法:

- (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】:

    @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])")
    

    【讨论】:

      【解决方案6】:

      如果是为了调试,请养成使用NSLog(@"%s", __FUNCTION__);的习惯

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

      【讨论】:

      • 不知何故代码显示不正确。 FUNCTION前后有两个下划线
      • 尝试使用反引号转义符 (`) 将代码括起来,以便正确显示
      • 或者更好的使用__PRETTY_FUNCTION__,它也支持Objective-C并显示对象名称和方法。
      【解决方案7】:

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

      +(void)log:(NSString*)data from:(id)sender{
          NSLog(@"[%@]: %@", NSStringFromClass([sender class]), data);
      }
      
      //...
      
      -(void)myFunc{
          [LoggerClassName log:@"myFunc called" from:self];
      }
      

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          @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
          

          取自Identify Calling Method in iOS

          【讨论】:

            【解决方案10】:

            @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])")
            

            【讨论】:

              【解决方案11】:

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

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

              【讨论】:

                【解决方案12】:

                过去在 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
                

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2023-03-29
                  • 2015-03-11
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-06-14
                  • 2011-05-26
                  • 2011-01-29
                  相关资源
                  最近更新 更多