ทำความเข้าใจกับ NSRunLoop


109

ใครสามารถอธิบายได้ว่าคือNSRunLoopอะไร? อย่างที่ฉันรู้NSRunLoopคือสิ่งที่เกี่ยวโยงกันNSThreadใช่ไหม สมมติว่าฉันสร้างเธรดเช่น

NSThread* th=[[NSThread alloc] initWithTarget:self selector:@selector(someMethod) object:nil];
[th start];

-(void) someMethod
{
    NSLog(@"operation");
}

หลังจากเธรดนี้เสร็จสิ้นการทำงานของเขาใช่ไหม ทำไมต้องใช้RunLoopsหรือใช้ที่ไหน? จากเอกสารของ Apple ฉันได้อ่านบางสิ่ง แต่ยังไม่ชัดเจนสำหรับฉันดังนั้นโปรดอธิบายให้ง่ายที่สุด


คำถามนี้มีขอบเขตกว้างเกินไป โปรดปรับแต่งคำถามของคุณให้มีความเฉพาะเจาะจงมากขึ้น
Jody Hagins

3
ตอนแรกฉันอยากรู้ว่ามันทำอะไรใน NSRunLoop ทั่วไปและมันเชื่อมต่อกับเธรดอย่างไร
taffarel

คำตอบ:


211

การวนรอบการทำงานเป็นนามธรรมที่ (เหนือสิ่งอื่นใด) มีกลไกในการจัดการแหล่งอินพุตของระบบ (ซ็อกเก็ตพอร์ตไฟล์แป้นพิมพ์เมาส์ตัวจับเวลา ฯลฯ )

NSThread แต่ละตัวมี run loop ของตัวเองซึ่งสามารถเข้าถึงได้โดยใช้วิธี currentRunLoop

โดยทั่วไปคุณไม่จำเป็นต้องเข้าถึง run loop โดยตรงแม้ว่าจะมีส่วนประกอบ (ระบบเครือข่าย) บางส่วนที่อาจช่วยให้คุณระบุได้ว่าจะใช้ run loop ใดสำหรับการประมวลผล I / O

การวนรอบการรันสำหรับเธรดที่กำหนดจะรอจนกว่าแหล่งอินพุตอย่างน้อยหนึ่งแหล่งมีข้อมูลหรือเหตุการณ์บางอย่างจากนั้นจึงเริ่มการทำงานของตัวจัดการอินพุตที่เหมาะสมเพื่อประมวลผลแหล่งอินพุตแต่ละรายการที่ "พร้อม"

หลังจากทำเช่นนั้นมันจะกลับไปที่ลูปประมวลผลอินพุตจากแหล่งต่างๆและ "นอน" หากไม่มีงานที่ต้องทำ

นั่นเป็นคำอธิบายระดับสูงทีเดียว (พยายามหลีกเลี่ยงรายละเอียดมากเกินไป)

แก้ไข

ความพยายามที่จะกล่าวถึงความคิดเห็น ฉันหักมันออกเป็นชิ้น ๆ

  • หมายความว่าฉันสามารถเข้าถึง / รันเพื่อรันลูปภายในเธรดได้เท่านั้นใช่ไหม

แน่นอน. NSRunLoop ไม่ใช่เธรดที่ปลอดภัยและควรเข้าถึงได้จากบริบทของเธรดที่กำลังรันลูปเท่านั้น

  • มีตัวอย่างง่ายๆในการเพิ่มเหตุการณ์เพื่อรันลูปหรือไม่?

หากคุณต้องการมอนิเตอร์พอร์ตคุณก็แค่เพิ่มพอร์ตนั้นในรันลูปจากนั้นรันลูปจะเฝ้าดูพอร์ตนั้นสำหรับกิจกรรม

- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode

คุณยังสามารถเพิ่มตัวจับเวลาอย่างชัดเจนด้วย

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • มันจะกลับไปวนซ้ำหมายความว่าอะไร?

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

  • run loop ไม่ทำงานเมื่อฉันเริ่มเธรดหรือไม่

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

  • เป็นไปได้ไหมที่จะเพิ่มเหตุการณ์บางอย่างในเธรดรันลูปนอกเธรด

ฉันไม่แน่ใจว่าคุณหมายถึงอะไรที่นี่ คุณไม่ได้เพิ่มเหตุการณ์ในการวนรอบการทำงาน คุณเพิ่มแหล่งอินพุตและแหล่งที่มาของตัวจับเวลา (จากเธรดที่เป็นเจ้าของลูปการรัน) จากนั้นวงวิ่งจะเฝ้าดูพวกเขาสำหรับกิจกรรม แน่นอนคุณสามารถให้อินพุตข้อมูลจากเธรดและกระบวนการอื่น ๆ ได้ แต่อินพุตจะถูกประมวลผลโดยรันลูปที่ตรวจสอบแหล่งข้อมูลเหล่านั้นบนเธรดที่กำลังรันลูปรัน

  • หมายความว่าบางครั้งฉันสามารถใช้ run loop เพื่อบล็อกเธรดได้ชั่วครั้งชั่วคราว

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

เช่นเดียวกับ run loop

ฉันขอแนะนำให้คุณอ่านเอกสารต่อไปนี้เกี่ยวกับ run loops:

https://developer.apple.com/documentation/foundation/nsrunloop

และวิธีใช้ภายในเธรด:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1


2
หมายความว่าฉันสามารถเข้าถึง / รันเพื่อรันลูปภายในเธรดได้เท่านั้นใช่ไหม มีตัวอย่างง่ายๆในการเพิ่มเหตุการณ์เพื่อรันลูปหรือไม่? มันจะกลับไปวนซ้ำหมายความว่าอะไร? run loop ไม่ทำงานเมื่อฉันเริ่มเธรดหรือไม่ เป็นไปได้ไหมที่จะเพิ่มเหตุการณ์บางอย่างในเธรดรันลูปนอกเธรด หมายความว่าบางครั้งฉันสามารถใช้ run loop เพื่อบล็อกเธรดเป็นเวลาได้หรือไม่?
taffarel

"ติดตั้งตัวจัดการสำหรับการดำเนินการ IO ใด ๆ (เช่นการกดปุ่ม) ที่สลีป" คุณหมายถึงว่าถ้าฉันยังคงกดปุ่มมันจะยังคงบล็อกเธรดไปชั่วครั้งชั่วคราว!
น้ำผึ้ง

ไม่สิ่งที่ฉันหมายถึงคือ runloop ไม่ประมวลผลเหตุการณ์ใหม่จนกว่าตัวจัดการจะเสร็จสิ้น หากคุณนอนหลับ (หรือดำเนินการบางอย่างที่ใช้เวลานาน) ในตัวจัดการลูปรันจะบล็อกจนกว่าตัวจัดการจะทำงานเสร็จ
Jody Hagins

@taffarel เป็นไปได้ไหมที่จะเพิ่มเหตุการณ์บางอย่างในเธรดรันลูปนอกเธรด หากนั่นหมายความว่า "ฉันสามารถทำให้โค้ดทำงานบนรันลูปของเธรดอื่นได้ตามต้องการ" คำตอบก็คือใช่ เพียงแค่โทรperformSelector:onThread:withObject:waitUntilDone:ผ่านNSThreadวัตถุและตัวเลือกของคุณจะถูกกำหนดเวลาบนรันลูปของเธรดนั้น
Mecki

12

การรันลูปคือสิ่งที่แยก แอปแบบโต้ตอบออกจาก เครื่องมือบรรทัดคำสั่ง

  • เครื่องมือบรรทัดคำสั่งจะเปิดขึ้นพร้อมกับพารามิเตอร์ดำเนินการคำสั่งจากนั้นออก
  • แอปแบบโต้ตอบรอการป้อนข้อมูลของผู้ใช้ตอบสนองจากนั้นรอต่อไป

จากที่นี่

พวกเขาอนุญาตให้คุณรอจนกว่าผู้ใช้จะแตะและตอบสนองตามนั้นรอจนกว่าคุณจะเสร็จสิ้นจัดการและใช้ผลลัพธ์รอจนกว่าคุณจะได้รับตัวจับเวลาและดำเนินการฟังก์ชัน หากคุณไม่มี runloop คุณจะไม่สามารถฟัง / รอการแตะของผู้ใช้คุณไม่สามารถรอจนกว่าจะมีการโทรผ่านเครือข่ายคุณจะไม่สามารถปลุกได้ภายใน x นาทีเว้นแต่คุณจะใช้DispatchSourceTimerหรือDispatchWorkItem

จากความคิดเห็นนี้ :

เธรดพื้นหลังไม่มี runloops ของตัวเอง แต่คุณสามารถเพิ่มได้ เช่นAFNetworking 2.xก็ทำได้ เป็นเทคนิคที่พยายามและเป็นจริงสำหรับ NSURLConnection หรือ NSTimer บนเธรดพื้นหลัง แต่เราไม่ได้ทำสิ่งนี้ด้วยตัวเองอีกต่อไปเนื่องจาก API รุ่นใหม่ไม่จำเป็นต้องทำเช่นนั้น แต่ดูเหมือนว่า URLSession จะทำเช่นนี่คือคำของ่ายๆโดยเรียกใช้ [ดูแผงด้านซ้ายของภาพ] ตัวจัดการการทำให้เสร็จสิ้นในคิวหลักและคุณจะเห็นว่ามีการวนรอบการทำงานบนเธรดพื้นหลัง


โดยเฉพาะเกี่ยวกับ: "เธรดเบื้องหลังไม่มีรันลูปของตัวเอง" ตัวจับเวลาต่อไปนี้ไม่สามารถเริ่มทำงานสำหรับการจัดส่งasync :

class T {
    var timer: Timer?

    func fireWithoutAnyQueue() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
            print("without any queue") // success. It's being ran on main thread, since playgrounds begin running from main thread
        })
    }

    func fireFromQueueAsnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — async") // failed to print
            })
        }
    }

    func fireFromQueueSnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.sync {
            timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — sync") // success. Weird. Read my possible explanation below
            })
        }
    }

    func fireFromMain() {
        DispatchQueue.main.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from main queue — sync") //success
            })
        }
    }
}

ฉันคิดว่าสาเหตุที่syncบล็อกทำงานก็เพราะ:

โดยปกติแล้วบล็อกการซิงค์จะถูกดำเนินการจากภายในคิวซอร์ส ในตัวอย่างนี้คิวต้นทางคือคิวหลักคิวใด ๆคือคิวปลายทาง

เพื่อทดสอบว่าฉันเข้าสู่ระบบRunLoop.currentภายในทุกการจัดส่ง

การจัดส่งการซิงค์มีrunloop เดียวกันกับคิวหลัก ในขณะที่ RunLoop ภายในบล็อก async เป็นอินสแตนซ์ที่แตกต่างจากที่อื่น คุณอาจกำลังคิดว่าเหตุใดจึงRunLoop.currentส่งคืนค่าที่แตกต่างกัน ไม่ใช่ค่าแชร์ !? คำถามดีมาก! อ่านเพิ่มเติม:

โน๊ตสำคัญ:

คุณสมบัติชั้น currentไม่ได้เป็นตัวแปรทั่วโลก

ส่งคืนรันลูปสำหรับเธรดปัจจุบัน

ตามบริบท มันมองเห็นได้เฉพาะภายในขอบเขตของเช่นด้ายจัดเก็บกระทู้ท้องถิ่น สำหรับข้อมูลเพิ่มเติมเกี่ยวที่เห็นว่าที่นี่

นี่เป็นปัญหาที่ทราบเกี่ยวกับตัวจับเวลา คุณไม่มีปัญหาเดียวกันถ้าคุณใช้DispatchSourceTimer


8

RunLoops เป็นเหมือนกล่องที่มีสิ่งต่างๆเกิดขึ้น

โดยพื้นฐานแล้วใน RunLoop คุณจะไปประมวลผลเหตุการณ์บางอย่างแล้วย้อนกลับ หรือย้อนกลับหากไม่ประมวลผลเหตุการณ์ใด ๆ ก่อนที่จะหมดเวลา คุณสามารถพูดได้ว่าคล้ายกับ NSURLConnections แบบอะซิงโครนัสการประมวลผลข้อมูลในพื้นหลังโดยไม่รบกวนลูปปัจจุบันของคุณและในขณะเดียวกันคุณต้องใช้ข้อมูลพร้อมกัน ซึ่งสามารถทำได้ด้วยความช่วยเหลือของ RunLoop ซึ่งทำให้แบบอะซิงโครนัสของคุณNSURLConnectionและให้ข้อมูลในเวลาโทร คุณสามารถใช้ RunLoop ดังนี้:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) {
    loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
}

ใน RunLoop นี้ก็จะทำงานจนกว่าคุณจะเสร็จสมบูรณ์บางส่วนของงานอื่น ๆ ของคุณและตั้งYourBoolFlagการเท็จ

ในทำนองเดียวกันคุณสามารถใช้ในเธรดได้

หวังว่านี่จะช่วยคุณได้


0

การรันลูปเป็นส่วนหนึ่งของโครงสร้างพื้นฐานพื้นฐานที่เกี่ยวข้องกับเธรด run loop คือลูปการประมวลผลเหตุการณ์ที่คุณใช้เพื่อจัดกำหนดการงานและประสานงานการรับเหตุการณ์ที่เข้ามา จุดประสงค์ของการวนซ้ำคือการทำให้เธรดของคุณไม่ว่างเมื่อมีงานที่ต้องทำและทำให้เธรดของคุณเข้าสู่โหมดสลีปเมื่อไม่มี

จากที่นี่


คุณสมบัติที่สำคัญที่สุดของ CFRunLoop คือ CFRunLoopModes CFRunLoop ทำงานร่วมกับระบบ“ Run Loop Sources” ซอร์สถูกลงทะเบียนบนรันลูปสำหรับโหมดหนึ่งหรือหลายโหมดและรันลูปเองถูกสร้างขึ้นเพื่อรันในโหมดที่กำหนด เมื่อเหตุการณ์มาถึงแหล่งที่มาเหตุการณ์จะถูกจัดการโดย run loop ก็ต่อเมื่อโหมดต้นทางตรงกับโหมดปัจจุบันของ run loop

จากที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.