บทความชุดนี้จะพูดถึง operator share() ใน ReactiveX ว่ามีที่มาที่ไปอย่างไร ทำไมต้องใช้ และจะใช้งานมันได้อย่างไร โดยแบ่งเนื้อหาออกเป็นสองส่วนดังนี้
- [Rx.share()] — Part1 — ทำไม่ต้อง share ไม่ share ได้ไหม?
- [Rx.share()] — Part2 — share มาจากไหน?
ในบทความที่แล้ว เราได้พูดถึงการทำงานภาพรวมของ share() กันไปแล้วว่ามีมันพฤติกรรมอย่างไร และเรายังได้กล่าวถึงการทำงานของ publsih() และ connect() ควบคู่ไปด้วยเพื่อให้เห็นภาพการทำงานของ share() ได้ชัดเจนขึ้น ในบทความนี้จึงอยากจะมาเจาะลึกถึงการทำงานของ share() เพื่อดูว่ามี Operators อะไรบ้างที่เกี่ยวข้อง ซึ่งจะขอไล่ไปทีละตัวดังนี้
ConnectableObservable
ก่อนอื่นต้องขอทวนเจ้าตัวนี้ก่อน เพราะมันคือกุญแจไปสู่ทุก Operators ที่จะกล่าวถึงหลังจากนี้ โดยเจ้า ConnectableObservable คือ Observable ที่จะไม่ปล่อย item ใดๆ เมื่อถูก subscribe แต่จะปล่อย item ก็ต่อเมื่อมีการเรียกคำสั่ง connect
เกิดขึ้น ซึ่งพฤติกรรมนี้จะแตกต่างจาก Observable ปกติทั่วไปที่จะเริ่มทำงานทันทีเมื่อมีการ subscribe
เกิดขึ้น
Publish
publish()
คือ operator ที่ใช้เปลี่ยน Observable ธรรมดาๆให้กลายเป็น ConnectableObservable ซึ่งเราได้อธิบายการทำงานของมันไปแล้วในบทความที่แล้ว
Connect
connect()
คือ Operator ที่ใช้คู่กับ ConnectableObservable ซึ่งหากเปรียบ ConnectableObservable เหมือนแทงค์น้ำแล้วละก้อ เจ้า connect
ก็เปรียบเสมือนวาล์วปล่อยน้ำนั่นเอง โดย ConnectableObservable จะเริ่มปล่อย item ออกเมื่อเราสั่งคำสั่ง connect()
ลองทวนตัวอย่างเดิมกันอีกสักรอบ
Output:
Observable: Proceed at time: 1518431395795
Observer1: Got result of time: 1518431395795
Observer2: Got result of time: 1518431395795
จะเห็นว่า Observable จะทำงานแค่ครั้งเดียวแล้วแชร์ข้อมูลที่ได้ออกไปให้กับ Observer ทั้งสองตัว
autoConnect
autoConnect()
เป็น Operator ที่เป็นขั้น advance ของ connect()
โดยแทนที่เราจะต้องเรียกคำสั่ง connect()
เพื่อให้ ConnectableObservable เริ่มปล่อย Item แต่ autoConnect()
จะทำงานต่างกันนิดหน่อยตรงที่จะเริ่มปล่อย item ทันทีเมื่อมีคนมา subscribe ซึ่งพฤติกรรมจะคล้ายกับการทำงานของ Observable แบบปกติทั่วไป แต่จะต่างกันตรงที่ Observable จะทำงานแค่ครั้งเดียวเมื่อเกิดการ subscribe
ถึงตรงนี้หลายคนอาจจะสงสัยว่าการทำงานของมัน ไม่ต่างอะไรจาก share()
ที่เราได้เรียนรู้กันไปในบทความที่แล้ว ถ้าเราดูแค่ผลลัพธ์ที่ได้ การทำงานก็คงไม่ต่างกัน แต่หากเจาะลึกลงไปถึงหลักการทำงาน เราจะเห็นถึงความแตกต่าง และคาดเดาได้ถึงเหตุการณ์ที่อาจจะเกิดขึ้น ถ้าหากอ่านไปเรื่อยๆจะเจอคำตอบเองครับ
จากโค้ดตัวอย่างการใช้ connect()
ด้านบนหากเราลองเปลี่ยนมาใช้ autoConnect()
จะได้โค้ดหน้าตาประมาณนี้
Output:
Observable: Proceed at time: 1518431395795
Observer1: Got result of time: 1518431395795
จากโค้ดจะเห็นว่าเราไม่จำเป็นต้องเรียกคำสั่ง connect()
ในตอนท้ายอีกแล้ว แต่ผลลัพธ์ที่ได้จะไม่มี log ของObserver2
เกิดขึ้น เนื่องจากการ subscribe
ของ Observer1
จะ trigger ให้ Observable ทำงานทันที ทำให้ Observer2
เข้ามา subscribe
ไม่ทัน จึงทำให้ไม่ได้รับ Item ที่ปล่อยออกมานั่นเอง (เหมือน share() ไหมละครับ)
refCount
refCount()
เป็นอีกหนึ่ง operator ที่เราต้องรู้จักและเป็นตัวแปลสำคัญในการอธิบายการทำงานของ share()
โดย refCount()
คือ Operator ที่ทำงานเหมือนกับ autoConnect()
นั่นคือ หากมีการ subscribe
เกิดขึ้น ConnectableObservable จะเริ่มปล่อย item ทันที
Output:
Observable: Proceed at time: 1518680270882
Observer1: Got result of time: 1518680270882
จากโค้ดจะเห็นว่า ผมลัพธ์ที่ได้ไม่ต่างจากการใช้งาน autoConnect()
ซึ่งเจ้า autoConnect()
ก็ดันดูเหมือนกับ share()
ซะด้วยสิ!
แล้ว
refCount()
ต่างยังไงกับautoConnect()
อย่างไร?
autoConnect Vs refCount
ถึงแม้จุดเริ่มต้นการทำงานของ autoConnect()
และ refCount()
อาจจะเหมือนกัน แต่ตอนจบมันไม่เหมือนกันครับ
refCount()
จะเรียกconnect()
ให้ทันทีที่มีคนมา subscribe เหมือนautoConnect()
แต่จะทำงานต่างจากautoConnect()
ตรงที่ ConnectableObservable จะหยุดการทำงานทันทีเมื่อไม่มีใคร subscribe มันอยู่ ในขณะที่autoConnect()
นั้น ConnectableObservable ยังคงทำงานต่อไป ถึงแม้จะไม่มีใคร subscribe มันแล้วก็ตาม
ลองดูตัวอย่างโค้ดด้านล่างประกอบครับ
subscribe1: output: 0
subscribe1: output: 1
subscribe1: output: 2
postDelayed: dispose 'subscribe1' after 3 seconds
postDelayed: Subscribing again after 5 seconds
subscribe2: output: 5
subscribe2: output: 6
subscribe2: output: 7
subscribe2: output: 8
จากผลลัพธ์ของการใช้ autoConnect()
ด้านบน เราจะเห็นได้ว่า ถึงแม้เราหยุด subscribe intervalObs
ไปแล้วในวินาทีที่ 3 แต่ intervalObs
ก็ยังคงทำงานต่อไป แม้ไม่มีใคร subscribe มันอยู่ ซึ่งจะสังเกตุได้จากผลลัพธ์ของการ subscribe ครั้งที่สอง ที่ตัวเลขเพิ่มขึ้นเรื่อยๆ แทนที่จะเริ่มนับใหม่ ซึ่ง intervalObs
จะหยุดทำงานก็ต่อเมื่อเราสั่ง dispose หรือมี error เกิดขึ้น
ทีนี้เรามาลองดูผลลัพธ์ของการใช้ refCount()
กับบ้าง โดยใช้ code ชุดเดียวกับ เพียงแค่เปลี่ยนจาก autoConnect()
มาเป็น RefCount()
val intervalObs = Observable.interval(1, 1, TimeUnit.SECONDS)
.publish().refCount()
ซึ่งผลลัพธ์ที่ได้จะออกมาหน้าตาประมาณนี้ครับ
subscribe1: output: 0
subscribe1: output: 1
subscribe1: output: 2
postDelayed: dispose 'subscribe1' after 3 seconds
postDelayed: Subscribing again after 5 seconds
subscribe2: output: 0
subscribe2: output: 1
subscribe2: output: 2
subscribe2: output: 3
subscribe2: output: 4
จากผลลัพธ์เราจะเห็นว่า ConnectableObservable จะหยุดทำงานเมื่อไม่มีใคร subscribe มันอยู่ และจะเริ่มกลับมาทำงานใหม่อีกครั้ง เมื่อเกิดการ subscribe เกิดขึ้น โดยสังเกตุได้จาก ตัวเลขที่เริ่มนับใหม่ หลังจากที่มีการเรียก subscribe ครั้งที่สองเกิดขึ้น
Share() isEqualTo publish().refCount() == true
หากเรากดเข้าไปดูโค้ดของ share()
สิ่งที่เราจะเจอคือ จริงๆแล้ว share()
ถูกสร้างขึ้นมาจาก publish().refCount()
นั่นเอง
แล้วจะมี share()
ทำไมใน เมื่อใช้ publish().refCount()
ได้ คำตอบคือ เวลาพิมพ์มันสั่นกว่าไหมละครับ และชื่อฟังก์ชั่น share()
ก็ฟังดูเป็นอะไรที่เข้าใจง่ายและชัดเจนกว่า
Warning!!!
จากตัวอย่างด้านบนผู้อ่านเห็นอะไรที่น่าพึงระวังของการ ใช้ connect()
และ autoConnect()
ไหมครับ?
ใช่แล้วครับ Observable มันไม่ตาย! หรือไม่หยุดทำงานนั่นเอง ซึ่งเป็นสิ่งที่เราควรจะระวังเป็นพิเศษ เมื่อต้องการใช้งาน connect()
และ autoConnect()
เพราะเรากำลังสร้าง Hot Observable ขึ้น ซึ่งถ้าหากใครงงว่า Hot Observable คืออะไรให้ไปอ่านต่อที่นี่ครับ
แต่อย่าเพิ่งคิดว่า Hot Observable มันไม่ดีนะครับ ทุกอย่างล้วนมีข้อดีข้อเสีย อยู่ที่ว่าเรากำลังทำอะไร ลองเข้าไปอ่านถึงประโยชน์ของการใช้ Hot Observable ดู และเลือกใช้ให้เหมาะสมกับงานที่จะทำครับ
Conclusion
ก็จบไปแล้วสำหรับบทความชุดนี้ ที่ได้พูดถึงการทำงานของ Operator ที่ชื่อว่า share()
โดยได้อธิบายถึงการทำงานของ Operators ที่เกี่ยวข้องประกอบไปด้วย ทั้ง connect()
, autoConnect()
และ refCount()
ซึ่งหากเราเข้าใจการทำงานของทั้งสาม Operators นี้ เราก็จะเข้าใจการทำงานของ share รวมถึงนำไปประยุกต์ใช้ให้เหมาะกับงานที่จะทำได้อีกด้วย
สุดท้ายนี้ก็เช่นเคยครับ หากผู้อ่านคิดว่าบทความนี้มีประโยชน์ ก็ฝาก share() และกด 👏 เป็นกำลังใจให้ด้วยนะครับ