ในขณะที่UIScrollView
(หรือคลาสที่ได้รับมา) กำลังเลื่อนดูเหมือนว่าทุกสิ่งNSTimers
ที่กำลังทำงานอยู่จะหยุดชั่วคราวจนกว่าการเลื่อนจะเสร็จสิ้น
มีวิธีแก้ปัญหานี้หรือไม่? เธรด? การตั้งค่าลำดับความสำคัญ? อะไรมั้ย?
ในขณะที่UIScrollView
(หรือคลาสที่ได้รับมา) กำลังเลื่อนดูเหมือนว่าทุกสิ่งNSTimers
ที่กำลังทำงานอยู่จะหยุดชั่วคราวจนกว่าการเลื่อนจะเสร็จสิ้น
มีวิธีแก้ปัญหานี้หรือไม่? เธรด? การตั้งค่าลำดับความสำคัญ? อะไรมั้ย?
คำตอบ:
วิธีแก้ปัญหาที่ง่ายและใช้งานง่ายคือทำ:
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
สำหรับใครที่ใช้ Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
เป็นคำสั่งแรกแทนTimer.scheduleTimer()
เนื่องจากscheduleTimer()
เพิ่มตัวจับเวลาให้กับ runloop และการเรียกครั้งต่อไปเป็นการเพิ่มอีกครั้งใน runloop เดียวกัน แต่ใช้โหมดที่แตกต่างกัน อย่าทำงานเดียวกันซ้ำสอง
ใช่พอลพูดถูกนี่เป็นปัญหาการวนซ้ำ โดยเฉพาะคุณต้องใช้ประโยชน์จากวิธี NSRunLoop:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
นี่คือรุ่นที่รวดเร็ว
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
คุณต้องรันเธรดอื่นและรันลูปอื่นหากคุณต้องการให้ตัวจับเวลาเริ่มทำงานขณะเลื่อน เนื่องจากตัวจับเวลาได้รับการประมวลผลโดยเป็นส่วนหนึ่งของลูปเหตุการณ์หากคุณกำลังยุ่งอยู่กับการประมวลผลการเลื่อนมุมมองของคุณคุณจะไม่ได้รับรอบตัวจับเวลา แม้ว่าการลงโทษที่สมบูรณ์แบบ / แบตเตอรี่ของการรันตัวจับเวลาในเธรดอื่นอาจไม่คุ้มค่ากับการจัดการกรณีนี้
สำหรับทุกคนที่ใช้ Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
tl; dr runloop กำลังทำการเลื่อนดังนั้นจึงไม่สามารถจัดการกับเหตุการณ์ใด ๆ ได้อีกเว้นแต่คุณจะตั้งค่าตัวจับเวลาด้วยตนเองเพื่อให้สามารถเกิดขึ้นได้เมื่อ runloop จัดการกับเหตุการณ์การสัมผัส หรือลองใช้วิธีอื่นและใช้ GCD
ต้องอ่านสำหรับนักพัฒนา iOS ทุกคน ในที่สุดสิ่งต่างๆมากมายถูกดำเนินการผ่าน RunLoop
ได้มาจากเอกสารของ Appleเอกสารของแอปเปิ้ล
การวนรอบเป็นอย่างมากเหมือนกับชื่อของมัน เป็นการวนซ้ำที่เธรดของคุณป้อนและใช้เพื่อเรียกใช้ตัวจัดการเหตุการณ์เพื่อตอบสนองต่อเหตุการณ์ที่เข้ามา
เนื่องจากตัวจับเวลาและเหตุการณ์อื่น ๆ ตามระยะเวลาจะถูกส่งเมื่อคุณรันรันลูปการหลีกเลี่ยงลูปนั้นจะขัดขวางการส่งมอบเหตุการณ์เหล่านั้น ตัวอย่างทั่วไปของลักษณะการทำงานนี้เกิดขึ้นเมื่อใดก็ตามที่คุณใช้รูทีนการติดตามเมาส์โดยป้อนลูปและร้องขอเหตุการณ์ซ้ำ ๆ จากแอปพลิเคชัน เนื่องจากโค้ดของคุณจับเหตุการณ์โดยตรงแทนที่จะปล่อยให้แอปพลิเคชันส่งเหตุการณ์เหล่านั้นตามปกติตัวจับเวลาที่ใช้งานอยู่จะไม่สามารถเริ่มทำงานได้จนกว่ากิจวัตรการติดตามเมาส์ของคุณจะออกและส่งการควบคุมกลับไปยังแอปพลิเคชัน
สิ่งนี้เกิดขึ้นหลายครั้งโดยที่เราไม่เคยสังเกตเห็น ฉันหมายความว่าเราตั้งเวลาให้ยิงเวลา 10: 10: 10: 00 น แต่ runloop กำลังดำเนินการเหตุการณ์ซึ่งใช้เวลาจนถึง 10: 10: 10: 05 ดังนั้นตัวจับเวลาจะยิง 10: 10: 10: 06
ในทำนองเดียวกันถ้าตัวจับเวลาเริ่มทำงานเมื่อลูปรันอยู่ระหว่างการดำเนินการตามรูทีนตัวจัดการตัวจับเวลาจะรอจนกว่าจะถึงครั้งต่อไปที่รันลูปเพื่อเรียกใช้รูทีนตัวจัดการ หากลูปการวิ่งไม่ทำงานเลยตัวจับเวลาจะไม่เริ่มทำงาน
คุณสามารถกำหนดค่าตัวจับเวลาเพื่อสร้างเหตุการณ์เพียงครั้งเดียวหรือซ้ำ ๆ ตัวจับเวลาที่เกิดซ้ำจะกำหนดเวลาใหม่โดยอัตโนมัติตามเวลาการยิงที่กำหนดไว้ไม่ใช่เวลาการยิงจริง ตัวอย่างเช่นหากตัวจับเวลาถูกกำหนดให้เริ่มทำงานในช่วงเวลาใดเวลาหนึ่งและทุกๆ 5 วินาทีหลังจากนั้นเวลายิงที่กำหนดไว้จะตกตามช่วงเวลาเดิม 5 วินาทีเสมอแม้ว่าเวลาในการยิงจริงจะล่าช้าก็ตาม หากเวลายิงล่าช้ามากจนพลาดเวลาการยิงที่กำหนดไว้อย่างน้อยหนึ่งครั้งตัวจับเวลาจะยิงเพียงครั้งเดียวสำหรับช่วงเวลาที่พลาดไป หลังจากยิงไปในช่วงเวลาที่พลาดไปตัวจับเวลาจะถูกกำหนดใหม่สำหรับเวลาการยิงที่กำหนดไว้ถัดไป
คุณทำไม่ได้ ระบบปฏิบัติการจะเปลี่ยนแปลงตัวเองเพื่อคุณ eventTracking
เช่นเมื่อผู้ใช้ก๊อกแล้วโหมดเปลี่ยนไป default
เมื่อก๊อกใช้เสร็จแล้วโหมดกลับไป หากคุณต้องการให้บางสิ่งบางอย่างทำงานในโหมดเฉพาะก็ขึ้นอยู่กับคุณว่าจะเกิดอะไรขึ้น
tracking
เมื่อผู้ใช้เลื่อนการเรียกใช้โหมดห่วงกลายเป็น RunLoop ออกแบบมาเพื่อเปลี่ยนเกียร์ เมื่อตั้งค่าโหมดเป็นeventTracking
แล้วจะให้ความสำคัญ (จำไว้ว่าเรามีแกน CPU ที่ จำกัด ) เพื่อสัมผัสเหตุการณ์ นี่คือการออกแบบสถาปัตยกรรมโดยนักออกแบบระบบปฏิบัติการนี่คือการออกแบบสถาปัตยกรรมโดยนักออกแบบระบบปฏิบัติการ
ตามค่าเริ่มต้นตัวจับเวลาจะไม่ถูกกำหนดไว้ในtracking
โหมด มีกำหนดใน:
สร้างตัวจับเวลาและกำหนดเวลาบนลูปรันปัจจุบันใน โหมดเริ่มต้น
scheduledTimer
ใต้ไม่นี้:
RunLoop.main.add(timer, forMode: .default)
หากคุณต้องการให้ตัวจับเวลาของคุณทำงานเมื่อเลื่อนคุณต้องทำอย่างใดอย่างหนึ่ง:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode
RunLoop.main.add(timer, forMode: .tracking) // AND Do this
หรือเพียงแค่ทำ:
RunLoop.main.add(timer, forMode: .common)
ในที่สุดการทำอย่างใดอย่างหนึ่งข้างต้นหมายความว่าเธรดของคุณจะไม่ถูกบล็อกโดยเหตุการณ์การสัมผัส ซึ่งเทียบเท่ากับ:
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.
คุณอาจพิจารณาใช้ GCD เป็นตัวจับเวลาซึ่งจะช่วยให้คุณ "ป้องกัน" โค้ดของคุณจากปัญหาการจัดการลูป
สำหรับการไม่ทำซ้ำให้ใช้:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// your code here
}
สำหรับการจับเวลาซ้ำให้ใช้:
เจาะลึกจากการสนทนาที่ฉันมีกับ Daniel Jalkut:
คำถาม: GCD (เธรดพื้นหลัง) เช่น asyncAfter บนเธรดพื้นหลังถูกเรียกใช้งานนอก RunLoop ได้อย่างไร ความเข้าใจของฉันจากนี้คือทุกอย่างจะต้องดำเนินการภายใน RunLoop
ไม่จำเป็น - ทุกเธรดจะมีลูปการรันมากที่สุดเพียงหนึ่งครั้ง แต่สามารถมีศูนย์ได้หากไม่มีเหตุผลที่จะประสานการดำเนินการ "ความเป็นเจ้าของ" ของเธรด
เธรดคือความสามารถในการจ่ายระดับ OS ที่ช่วยให้กระบวนการของคุณสามารถแยกฟังก์ชันการทำงานออกจากบริบทการดำเนินการแบบขนานหลาย ๆ การรันลูปเป็นค่าใช้จ่ายในระดับเฟรมเวิร์กที่ช่วยให้คุณสามารถแยกเธรดเดียวเพิ่มเติมเพื่อให้สามารถใช้ร่วมกันได้อย่างมีประสิทธิภาพโดยใช้เส้นทางรหัสหลายเส้นทาง
โดยปกติถ้าคุณส่งบางสิ่งที่รันบนเธรดมันอาจจะไม่มี runloop เว้นแต่จะมีการเรียกใช้ [NSRunLoop currentRunLoop]
ซึ่งจะสร้างขึ้นโดยปริยาย
โดยสรุปแล้วโหมดต่างๆเป็นกลไกการกรองสำหรับอินพุตและตัวจับเวลา