Цель C найти вызывающего метода

90

Есть ли способ определить, из какой строки кода methodбыла вызвана определенная ?

эннуикиллер
источник
почему ты хочешь сделать это? Если это для отладки, есть совсем другой набор ответов, чем если бы вы хотели сделать это в производственной среде (для которого ответ, скорее всего, «не делайте».)
Николас Райли
4
Я возьму отладочный ответ
ennuikiller
3
Есть производственный ответ?
Хари Карам Сингх,

Ответы:

188

Я надеюсь, что это поможет:

    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, а затем запустил его из делегата приложения. Интересно, что звонивший был: «<отредактировано>»
Мелвин Соверен
4
в моем случае с индексом 5 ничего нет. Таким образом, этот код разрушил мое приложение. он работал после удаления последней строки. Тем не менее, это все еще настолько круто, что стоит +1!
Брайан
1
Это прекрасно работает, но как мы интерпретируем "вызывающего абонента"? В моем случае это число, например 91, но почему оно 91? Если я переместу вызов на одну инструкцию ниже, будет показано 136 ... Так как же вычисляется это число?
Максим Четруска
@ Pétur Если влияние на производительность незначительно, NSThread уже имеет эту информацию, вы просто обращаетесь к массиву и создаете новый.
Оскар Гомес
Он работает в режиме отладки, но после его архивирования в пакет IPA стек вызовов не работает должным образом. Я только что получил «callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136», «_Z8isxdigiti» должно быть «AAMAgentAppDelegate application: didFinishLaunchingWithOptions:»
Алан Лю
50

В полностью оптимизированном коде нет 100% верного способа определить вызывающего абонента определенного метода. Компилятор может использовать оптимизацию хвостового вызова, тогда как компилятор эффективно повторно использует фрейм стека вызывающего объекта для вызываемого.

Чтобы увидеть пример этого, установите точку останова для любого заданного метода с помощью gdb и посмотрите на трассировку. Обратите внимание, что вы не видите objc_msgSend () перед каждым вызовом метода. Это потому, что objc_msgSend () выполняет хвостовой вызов каждой реализации метода.

Хотя вы можете скомпилировать свое приложение без оптимизации, вам понадобятся неоптимизированные версии всех системных библиотек, чтобы избежать только этой проблемы.

И это всего лишь одна проблема; по сути, вы спрашиваете: «Как мне заново изобрести CrashTracer или gdb?». Очень сложная проблема, на которой строятся карьеры. Если вы не хотите, чтобы вашей карьерой были «инструменты отладки», я бы не рекомендовал идти по этому пути.

На какой вопрос вы действительно пытаетесь ответить?

бомж
источник
3
БОЖЕ МОЙ. Это вернуло меня на землю. Практически буквально. Я решал совершенно не связанную с этим проблему. Спасибо, сэр!
nimeshdesai
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 дает сбой. (Итак, я советую - если вы собираетесь использовать этот пример, используйте его временно, а затем удалите.)
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 и после него есть два символа подчеркивания
Джованни
попробуйте использовать экранирование с помощью обратных кавычек (`), чтобы заключить ваш код, чтобы он мог отображаться правильно
howanghk
3
Или лучше использовать __PRETTY_FUNCTION__, который также поддерживает Objective-C и отображает имя объекта вместе с методом.
pronebird
4

Вы можете передать в selfкачестве одного из аргументов функции, а затем получить имя класса вызывающего объекта внутри:

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

DannyBios
источник
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])")
Кармело Галло
источник