目的Cはメソッドの呼び出し元を見つける


90

特定のものmethodが呼び出されたコード行を判別する方法はありますか?


なぜこれをしたいのですか?それは、デバッグのためだ場合、あなたは生産にそれをしたい場合は以下の回答の全く異なるセットがあります(答えは可能性が高い「ない」であるためには。)
ニコラス・ライリー

4
デバッグの答えを
取り上げ

3
本番の答えはありますか?
Hari Karam Singh

回答:


188

StackIこれが役立つことを願っています:

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

1
-Prefix.pchファイルでマクロを作成し、アプリのデリゲートから実行しました。興味深いことに、クラスの呼び出し元は次のとおりでした: "<編集済み>"
Melvin Sovereign

4
私の場合、インデックス5には何もありません。したがって、このコードは私のアプリをクラッシュさせました。最後の行を削除した後に機能しました。それにもかかわらず、それはまだ素晴らしいので、+ 1の価値があります!
ブライアン

1
これはうまく機能しますが、「回線発信者」をどのように解釈すればよいでしょうか。私の場合、91などの数字が表示されますが、なぜ91なのですか。呼び出しを1つ下の命令に移動すると、136と表示されます...では、この数値はどのように計算されますか?
Maxim Chetrusca

@Péturパフォーマンスへの影響が無視できる場合、NSThreadはすでにその情報を持っているので、基本的には配列にアクセスして新しい配列を作成するだけです。
オスカーゴメス

デバッグモードで動作していますが、IPAパッケージにアーカイブした後、コールスタックは期待どおりに動作しません。「callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136」、「_ Z8isxdigiti」は「AAMAgentAppDelegate application:didFinishLaunchingWithOptions:」
Alanc Liu

50

完全に最適化されたコードでは、特定のメソッドの呼び出し元を決定する100%確実な方法はありません。コンパイラは末尾呼び出しの最適化を採用する場合がありますが、コンパイラは呼び出し先のスタックフレームを呼び出し先に効果的に再利用します。

この例を確認するには、gdbを使用して任意のメソッドにブレークポイントを設定し、バックトレースを確認してください。すべてのメソッド呼び出しの前にobjc_msgSend()が表示されないことに注意してください。これは、objc_msgSend()が各メソッドの実装に対して末尾呼び出しを行うためです。

最適化されていないアプリケーションをコンパイルすることもできますが、この1つの問題だけを回避するには、すべてのシステムライブラリの最適化されていないバージョンが必要になります。

そして、これはただ一つの問題です。実際には、「CrashTracerまたはgdbを再発明するにはどうすればよいですか?」キャリアが作られる非常に難しい問題。あなたが「デバッグツール」をあなたのキャリアにしたいのでない限り、私はこの道を進むことをお勧めしません。

あなたは本当にどんな質問に答えようとしていますか?


3
何てことだ。これは私を地球に戻しました。ほとんど文字通り。私は完全に無関係な問題を解決していました。ありがとうございます!
nimeshdesai 2014年

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:]

2
*ただし、場合によっては、関数名が含まれておらず、セレクターも実行されないため、CALL_ORIGINを呼び出すとクラッシュすることに注意してください。(SO、私はお勧めします-この例を使用する場合は、一時的に使用してから削除してください。)
Guntis Treulands

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;
}

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

5

清酒用の場合は、 NSLog(@"%s", __FUNCTION__);

クラスの各メソッド内の最初の行として。そうすれば、デバッガーを見れば、いつでもメソッド呼び出しの順序を知ることができます。


どういうわけか、コードは正しく表示されていません。FUNCTIONの前後に2つのアンダースコアがあります
Giovanni

バックティックエスケープ( `)を使用してコードを囲み、適切に表示されるようにしてください
howanghk

3
または、Objective-Cもサポートし、メソッドとともにオブジェクト名を表示する__PRETTY_FUNCTION__を使用することをお勧めします。
Ben Sinclair

4

self引数の1つとして関数に渡し、呼び出し元オブジェクトのクラス名を内部で取得できます。

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

//...

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

このようにして、問題がどこにあるかを判断するのに役立つオブジェクトを渡すことができます。


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

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

出力ウィンドウに次のようなものが表示されます。

呼び出し元: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のIdentify Calling Methodから取得されました


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

0

参考のために@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])")
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.