Завдання C - пошук абонента методу


90

Чи є спосіб визначити рядок коду, з methodякого викликали певного ?


Чому ви хочете це зробити? Якщо мова йде про налагодження, існує набір відповідей, який відрізняється від того, якщо ви хочете це зробити у виробництві (для якого відповідь швидше за все не потрібно).
Ніколас Райлі,


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 додаток: didFinishLaunchingWithOptions:"
Alanc Liu

50

У повністю оптимізованому коді немає 100% надійного способу визначити абонента за певним методом. Компілятор може використовувати оптимізацію хвостового виклику, тоді як компілятор ефективно повторно використовує кадр стека абонента для виклику.

Щоб побачити приклад цього, встановіть точку зупинки для будь-якого даного методу за допомогою gdb і подивіться на зворотну трасування. Зверніть увагу, що ви не бачите objc_msgSend () перед кожним викликом методу. Це тому, що objc_msgSend () робить хвіст-виклик реалізації кожного методу.

Хоча ви могли скомпілювати свою програму неоптимізованою, вам знадобляться неоптимізовані версії всіх системних бібліотек, щоб уникнути лише цієї однієї проблеми.

І це лише одна проблема; по суті, ви запитуєте "як мені по-новому винайти CrashTracer або gdb?". Дуже важка проблема, на якій роблять кар'єру. Якщо ви не хочете, щоб "інструменти налагодження" були вашою кар'єрою, я б рекомендував не йти цим шляхом.

На яке питання ви насправді намагаєтесь відповісти?


3
БОЖЕ МІЙ. Це повернуло мене на землю. Майже буквально. Я вирішував абсолютно непов’язану проблему. Дякую вам сер!
німешдесай

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 - якщо функція викликається за допомогою perforSelector, результат буде:

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

Версія 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])")

5

Якщо це для налагодження, візьміть звичку ставити NSLog(@"%s", __FUNCTION__);

Як перший рядок всередині кожного методу у ваших класах. Тоді ви завжди можете дізнатися порядок викликів методів, переглядаючи налагоджувач.


Якось код відображається неправильно. Є два підкреслення до та після ФУНКЦІЇ
Джованні

спробуйте скористатися екранованими
екранами backtick

3
Або краще використовувати __PRETTY_FUNCTION__, який також підтримує Objective-C і відображає ім'я об'єкта разом із методом.
Бен Сінклер,

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

Це було взято з методу Identify Calling Method у iOS .


2

Версія Swift 4 відповіді @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])")

0

Відповідь 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])")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.