特定のmethod
が呼び出されたコード行を特定する方法はありますか?
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]);
完全に最適化されたコードでは、特定のメソッドの呼び出し元を決定する100%確実な方法はありません。コンパイラーは、呼び出し先のスタックフレームを効果的に再利用するのに対し、コンパイラーは末尾呼び出しの最適化を使用できます。
この例を確認するには、gdbを使用して特定のメソッドにブレークポイントを設定し、バックトレースを見てください。すべてのメソッド呼び出しの前にobjc_msgSend()が表示されないことに注意してください。これは、objc_msgSend()が各メソッドの実装に対して末尾呼び出しを行うためです。
アプリケーションを最適化せずにコンパイルすることもできますが、この1つの問題を回避するには、すべてのシステムライブラリの最適化されていないバージョンが必要になります。
そして、これはただ一つの問題です。実際には、「CrashTracerまたはgdbを再発明するにはどうすればよいですか?」キャリアが作られる非常に難しい問題。 「デバッグツール」をあなたのキャリアにしたくない場合は、この道を進むことをお勧めします。
本当に答えようとしている質問は何ですか?
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:]
Swift 2.0リファレンス用の@Intropedroの答えのバージョン;
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])")
デバッグ目的のためなら、NSLog(@"%s", __FUNCTION__);
を置く習慣を身につけてください
クラスの各メソッド内の最初の行として。そうすれば、デバッガーを見て、メソッド呼び出しの順序をいつでも知ることができます。
これを行うメソッドを作成しました:
- (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;
}
関数の引数の1つとしてself
を渡し、内部の呼び出し元オブジェクトのクラス名を取得できます。
+(void)log:(NSString*)data from:(id)sender{
NSLog(@"[%@]: %@", NSStringFromClass([sender class]), data);
}
//...
-(void)myFunc{
[LoggerClassName log:@"myFunc called" from:self];
}
このようにして、問題がどこにあるかを判断するのに役立つオブジェクトを渡すことができます。
@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! :(";
}
しゅう
//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の呼び出しメソッドを識別する から取得しました。
Swift 4 copy and paste @;に対する@Geoff Hの回答)
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])")
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])")