Objective C 메서드 호출자 찾기


90

특정 코드 method가 호출 된 줄을 확인하는 방법이 있습니까?


왜 이렇게 하시겠습니까? 이 디버깅의 경우 생산을 수행하려는 경우보다는 답변의 아주 다른 세트있다 (대답은 가능성이 "그렇지"하는가.)
니콜라스 라일리

4
나는 디버깅 대답을 할 것이다
ennuikiller 09-09-20

3
프로덕션에 대한 답변이 있습니까?
Hari Karam Singh

답변:


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 파일에 매크로를 만든 다음 앱 대리자에서 실행했습니다. 흥미롭게도 클래스 호출자는 "<수정 됨>"
Melvin Sovereign

4
제 경우에는 인덱스 5에 아무것도 없습니다. 따라서이 코드가 내 앱을 충돌 시켰습니다. 마지막 줄을 제거한 후에 작동했습니다. 그럼에도 불구하고 여전히 너무 멋져서 +1의 가치가 있습니다!
Brian

1
이것은 훌륭하게 작동하지만 "라인 발신자"를 어떻게 해석합니까? 제 경우에는 91과 같이 숫자가 표시되는데 왜 91입니까? 아래에서 호출을 한 명령으로 이동하면 136이 표시됩니다.이 숫자는 어떻게 계산됩니까?
Maxim Chetrusca 2014

@ Pétur 성능에 미치는 영향이 무시할 만하다면 NSThread는 이미 해당 정보를 가지고 있으며 기본적으로 배열에 액세스하여 새 배열을 만드는 것입니다.
Oscar Gomez

디버그 모드에서 작동하지만 IPA 패키지에 보관 한 후 호출 스택이 예상대로 작동하지 않습니다. 방금 "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti"는 "AAMAgentAppDelegate application : didFinishLaunchingWithOptions :"여야합니다.
Alanc Liu

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 호출이 충돌합니다. (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 전후에 두 개의 밑줄이 있습니다
Giovanni

제대로 표시 할 수 있도록 코드를 둘러싸 (`) 역 따옴표 이스케이프를 사용해보십시오
howanghk을

3
또는 Objective-C를 지원하고 메서드와 함께 개체 이름을 표시하는 __PRETTY_FUNCTION__을 사용하는 것이 좋습니다.
pronebird 2014-04-25

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

안녕하세요.

//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의 식별 호출 방법에서 가져 왔습니다 .


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.