วัตถุประสงค์ C ค้นหาผู้เรียกใช้วิธีการ


90

มีวิธีการกำหนดบรรทัดของรหัสที่methodถูกเรียกมาหรือไม่?


ทำไมคุณถึงต้องการทำเช่นนี้? หากมีไว้สำหรับการดีบักจะมีชุดคำตอบที่แตกต่างจากที่คุณต้องการทำในขั้นตอนการผลิต (ซึ่งคำตอบนั้นน่าจะ "ไม่" มากกว่า)
Nicholas Riley

4
ฉันจะรับคำตอบในการแก้ไขข้อบกพร่อง
ennuikiller

3
มีคำตอบด้านการผลิตหรือไม่?
Hari Honor

คำตอบ:


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 แล้วเรียกใช้จากผู้ร่วมประชุมแอป ที่น่าสนใจผู้โทรในชั้นเรียนคือ: "<redacted>"
Melvin Sovereign

4
ในกรณีของฉันไม่มีอะไรที่ดัชนี 5 ดังนั้นรหัสนี้ทำให้แอปของฉันขัดข้อง มันใช้งานได้หลังจากลบบรรทัดสุดท้าย ถึงกระนั้นมันก็ยังยอดเยี่ยมมากจนมีค่า +1!
Brian

1
วิธีนี้ใช้งานได้ดี แต่เราจะตีความ "line caller" อย่างไร? ในกรณีของฉันมันแสดงตัวเลขเช่น 91 แต่ทำไมถึงเป็น 91 ถ้าฉันย้ายคำสั่งการโทรหนึ่งคำสั่งด้านล่างมันจะแสดง 136 ... แล้วตัวเลขนี้คำนวณอย่างไร?
Maxim Chetrusca

@ Péturหากมีผลต่อประสิทธิภาพเล็กน้อย NSThread มีข้อมูลนั้นอยู่แล้วโดยพื้นฐานแล้วคุณเพียงแค่เข้าถึงอาร์เรย์และสร้างอาร์เรย์ใหม่
Oscar Gomez

มันทำงานในโหมดดีบัก แต่หลังจากเก็บถาวรไปยังแพ็คเกจ IPA แล้ว call stack จะไม่ทำงานตามที่คาดไว้ ฉันเพิ่งได้รับ "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" ควรเป็น "AAMAgentAppDelegate application: didFinishLaunchingWithOptions:"
Alanc Liu

50

ในโค้ดที่ปรับให้เหมาะสมอย่างสมบูรณ์ไม่มีวิธีที่แน่นอน 100% ในการกำหนดผู้โทรไปยังวิธีการบางอย่าง คอมไพเลอร์อาจใช้การเพิ่มประสิทธิภาพการโทรหางในขณะที่คอมไพเลอร์ใช้สแต็กเฟรมของผู้โทรซ้ำสำหรับการเรียกอีกครั้ง

หากต้องการดูตัวอย่างนี้ให้ตั้งค่าเบรกพอยต์สำหรับวิธีการใด ๆ โดยใช้ gdb และดูที่ backtrace โปรดทราบว่าคุณไม่เห็น objc_msgSend () ก่อนการเรียกใช้ทุกวิธี นั่นเป็นเพราะ objc_msgSend () เรียกหางไปยังการใช้งานแต่ละวิธี

ในขณะที่คุณสามารถรวบรวมแอปพลิเคชันของคุณโดยไม่ได้รับการปรับให้เหมาะสมคุณจะต้องมีไลบรารีระบบทั้งหมดที่ไม่ได้รับการปรับให้เหมาะสมเพื่อหลีกเลี่ยงปัญหานี้

และนี่เป็นเพียงปัญหาเดียว คุณกำลังถามว่า "ฉันจะประดิษฐ์ CrashTracer หรือ gdb ขึ้นมาใหม่ได้อย่างไร" ปัญหาที่ยากมากในการประกอบอาชีพ ถ้าคุณไม่ต้องการ "เครื่องมือแก้ไขจุดบกพร่อง" เป็นอาชีพของคุณฉันขอแนะนำให้คุณไม่ต้องไปตามเส้นทางนี้

คำถามอะไรที่คุณพยายามตอบจริงๆ


3
โอ้พระเจ้า. สิ่งนี้ทำให้ฉันกลับสู่โลก เกือบตามตัวอักษร ฉันกำลังแก้ปัญหาที่สมบูรณ์และไม่เกี่ยวข้อง ขอบคุณ SIR!
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
Giovanni

ลองใช้ backtick escape (`) เพื่อใส่โค้ดของคุณเพื่อให้สามารถแสดงได้อย่างถูกต้อง
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


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.