Tujuan C menemukan pemanggil metode


90

Apakah ada cara untuk menentukan baris kode dari mana seseorang methoddipanggil?


Mengapa Anda ingin melakukan ini? Jika itu untuk debugging, ada serangkaian jawaban yang cukup berbeda daripada jika Anda ingin melakukannya dalam produksi (yang jawabannya lebih mungkin "jangan".)
Nicholas Riley

4
Saya akan mengambil jawaban debugging
ennuikiller

3
Apakah ada jawaban produksi?
Hari Karam Singh

Jawaban:


188

Saya harap ini membantu:

    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
Juga membuat makro di file -Prefix.pch dan kemudian menjalankannya dari delegasi aplikasi. Menariknya, penelepon kelas adalah: "<redacted>"
Melvin Sovereign

4
dalam kasus saya, tidak ada apa pun di indeks 5. Jadi kode ini merusak aplikasi saya. itu bekerja setelah menghapus baris terakhir. Meskipun demikian, ini masih sangat luar biasa sehingga bernilai +1!
Brian

1
Ini berfungsi dengan baik, tetapi bagaimana kita menafsirkan "pemanggil baris"? Dalam kasus saya, ini menunjukkan angka, misalnya 91, tetapi mengapa 91? Jika saya memindahkan panggilan satu instruksi di bawah ini, itu akan menunjukkan 136 ... Jadi bagaimana nomor ini dihitung?
Maxim Chetrusca

@ PĂ©tur Jika ada efek pada kinerja, itu dapat diabaikan, NSThread sudah memiliki informasi itu, Anda pada dasarnya hanya mengakses sebuah array dan membuat yang baru.
Oscar Gomez

Ini berfungsi dalam mode debug, tetapi setelah mengarsipkannya ke paket IPA, tumpukan panggilan tidak berfungsi seperti yang diharapkan. Saya baru saja mendapat "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" seharusnya menjadi "Aplikasi AAMAgentAppDelegate: didFinishLaunchingWithOptions:"
Alanc Liu

50

Dalam kode yang dioptimalkan sepenuhnya, tidak ada cara yang 100% pasti untuk menentukan pemanggil ke metode tertentu. Kompilator dapat menggunakan pengoptimalan panggilan ekor sedangkan kompilator secara efektif menggunakan kembali kerangka tumpukan pemanggil untuk callee.

Untuk melihat contohnya, setel breakpoint pada metode apa pun yang diberikan menggunakan gdb dan lihat lacak balik. Perhatikan bahwa Anda tidak melihat objc_msgSend () sebelum setiap panggilan metode. Itu karena objc_msgSend () melakukan panggilan ekor ke implementasi setiap metode.

Meskipun Anda dapat mengompilasi aplikasi Anda dengan tidak dioptimalkan, Anda memerlukan versi yang tidak dioptimalkan dari semua pustaka sistem untuk menghindari masalah ini saja.

Dan ini hanyalah satu masalah; Akibatnya, Anda bertanya "bagaimana cara menemukan kembali CrashTracer atau gdb?". Masalah yang sangat sulit di mana karier dibuat. Kecuali jika Anda ingin "alat debugging" menjadi karier Anda, saya sarankan untuk tidak melakukan hal ini.

Pertanyaan apa yang sebenarnya ingin Anda jawab?


3
YA TUHAN. Ini membawa saya kembali ke bumi. Hampir secara harfiah. Saya sedang memecahkan masalah yang sama sekali tidak terkait. Terima kasih Pak!
nimeshdesai

11

Menggunakan jawaban yang diberikan oleh intropedro , saya menemukan ini:

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

yang hanya akan mengembalikan saya kelas dan fungsi Asli:

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

ps - jika fungsi dipanggil menggunakan performSelector, hasilnya adalah:

Origin: [NSObject performSelector:withObject:]

2
* Namun perlu diketahui, bahwa dalam beberapa kasus, itu tidak berisi nama fungsi, juga tidak melakukan pemilih, dan dengan demikian - Memanggil CALL_ORIGIN lumpuh. (JADI, saya sarankan - jika Anda akan menggunakan contoh ini, gunakan sementara dan kemudian hapus.)
Guntis Treulands

6

Baru saja menulis metode yang akan melakukan ini untuk Anda:

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

Versi Swift 2.0 dari jawaban @ Intropedro untuk referensi;

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

Jika itu untuk debbuging, biasakan menempatkan file NSLog(@"%s", __FUNCTION__);

Sebagai baris pertama di dalam setiap metode di kelas Anda. Kemudian Anda selalu bisa mengetahui urutan panggilan metode dari melihat debugger.


Entah bagaimana kode tersebut tidak muncul dengan benar. Ada dua garis bawah sebelum dan sesudah FUNCTION
Giovanni

coba gunakan backtick escapes (`) untuk menyertakan kode Anda sehingga dapat ditampilkan dengan benar
howanghk

3
Atau lebih baik gunakan __PRETTY_FUNCTION__ yang juga mendukung Objective-C dan menampilkan nama objek bersama dengan metode.
pronebird

4

Anda dapat meneruskan selfsebagai salah satu argumen ke fungsi tersebut dan kemudian mendapatkan nama kelas dari objek pemanggil di dalamnya:

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

//...

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

Dengan cara ini Anda dapat memberikan objek apa pun yang akan membantu Anda menentukan di mana letak masalahnya.


3

Versi jawaban fantastis @Roy Kronenfeld yang sedikit dioptimalkan:

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

@nnuik

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

Di jendela keluaran Anda akan melihat sesuatu seperti berikut.

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

Anda juga dapat mengurai string ini untuk mengekstrak lebih banyak data tentang bingkai tumpukan.

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

Itu diambil dari Identify Calling Method di iOS .


2

Jawaban @Geoff H versi Swift 4 untuk menyalin dan menempel ;]

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

Jawaban @Geoff H versi Swift 3 untuk referensi:

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])")
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.