Swift 3 @escapeing & @nonescape closure

วันก่อนๆ ได้โหลด XCode 8 มาใช้งานซึ่งมันก็มาพร้อมกับ Swift3 แน่นอนว่า ภาษามันเปลี่ยนแปลงไป ไม่ว่าจะเป็น ชนิดของข้อมูลแบบใหม่ หรือว่า syntax ภาษาใหม่ๆ และหนึ่งในนั้นคือ closure

ถ้าหากเคยเขียน โคลเชอร์ ใน Swift2 มา ก็อาจจะเห็น คีย์เวิร์ด @nonescape ผ่านตามาบ้าง

ใน Swift2 พารามิเตอร์ที่เป็นโคลเชอร์จะมีค่าเริ่มต้นเป็น escape closure ถ้าหากเขียน @nonescape กำกับไว้ ก็จะเป็นการบอกว่า closure นี้เป็น nonescape  อย่างไรก็ตาม keyword นี้ได้นำออกไปจาก Swift 3 เป็นที่เรียบร้อย เนื่องจากว่า ใน Swift3 นี้ได้กำหนดไว้ว่า โคลเชอร์มีค่าเริ่มต้นเป็น nonescape

ถึงตรงนี้หลายคนอาจจะเกาหัว แล้วร้องว่า What ? เชี่ยยยย อะไรเนี่ย .. nonescape closure , escape closure มันคืออะไรว่ะ ?

ใจเย็นๆ แป๊ะอย่าร้อง ผมจะอธิบายให้ฟังแบบง่ายๆก็แล้วกัน

closure ที่ถูกส่งเข้าเป็นพารามิเตอร์ในฟังก์ชัน ถ้า closure ถูกเรียกหลังจากทีฟังก์ชันทำงานเสร็จได้ เรียกว่า escape closure

เพื่อให้เห็นภาพง่ายๆ ว่า escape  คืออะไรก็ดูตัวอย่างต่อไปนี้ละกัน

สมมติว่า ผมเขียนฟังก์ชัน download ไฟล์รูป ซึ่งเรียกใช้ฟังก์ชัน loadData  ของ http ที่เป็น asynchronous call ประมาณนี้

เมื่อฟังก์ชัน loadData ทำงานเสร็จ ก็จะเรียกโคลเชอร์ completion  พร้อมกับส่ง image กลับไป

การทำงานของ completion ลักษณะแบบนี้ จะเรียกว่า escape เพราะว่า มันทำงานได้ แม้ว่า ฟังก์ชัน loadProfileImage จะทำการ return (แต่ http.loadData จะยังทำงานต่อไป และแน่นอนว่า completion ก็จะถูกเรียกหลังจากที่ http.loadData ทำงานเสร็จ) สรุปคือว่า แม้ว่า loadProfile จะทำงานเสร็จ มันก็ยังเรียก completion

โค้ดตัวอย่างที่เขียนไป สามารถทำงานได้ปกติใน Swift 2 เพราะ โคลเชอร์ได้กำหนดให้เป็น escape มาตั้งแต่แรกเริ่ม ไม่ต้องเขียนอะไรเพิ่มเติมทั้งสิ้น

แต่ถ้าหาก เอาโค้ดนี้ไปใช้งานกับ swift 3 ตัว  completion จะไม่ถูกเรียก เนื่องจาก ฟังก์ชัน loadProfileImage จะทำการ return ก่อน ที่ฟังชัน loadData จะทำงานเสร็จ (เพราะเป็น asynchronous ไม่ต้องรอให้ทำงานเสร็จก็ return ได้)

เมื่อ @nonescape ได้นำออกไป มันก็ถูกแทนที่ด้วย  @escaping

@escaping ใช้กำหนดให้โคลเชอร์เป็นแบบ escape คือทำงานได้แม้ว่าตัวฟังก์ชันที่เรียกมันจะทำงานเสร็จไปแล้ว

ดังนั้นหากเราต้องการให้มันเรียก completion หลังจากที่ loadData ทำงานเสร็จ ก็ต้องกำหนดให้เป็น @escaping ดังชั่นตัวอย่าง

และจะเห็นว่า @escaping นั้นเป็น Type ประเภทหนึ่ง เช่นเดียวกับ String , Int ไม่ได้เป็น parameter attribute  เหมือนอย่างแต่ก่อน

ทำไมต้อง non escape ?

Swift3 เปลี่ยนมาใช้ non escape closure ก็เพราะเรื่องประสิทธิภาพ รวมไปถึงการจัดการหน่วยความจำ เนื่องจากว่า non escape นั้น จะไม่ทำการเก็บ closure ไว้  ปัญหา retain cycle ก็น้อยลงไป  ข้อดีอีกอย่างคือเราไม่ต้องเขียน self ถ้าหาก closure เรียกฟังก์ชันของตัวเอง

นอกจากนี้แล้ว การเขียน closure ใน Swift3 นั้นจะไม่สามารถกำหนด argument label ได้

ยกตัวอย่างเช่นใน Swift2 เราอาจจะเขียนฟังก์ชั่น แบบนี้

แต่ใน Swift 3 จะแจ้งว่า error

untitled-2

เพราะใน Swift3 นั้น closure ได้กำหนดว่า ไม่ให้มี argument label ดังนั้นแล้ว เราจึงต้องเพิ่ม _ เข้ามาในโค้ด ดังเช่นตัวอย่าง

ลองทำความเข้าใจ และศึกษา Swift3 , Closure กันครับ หวังว่าจะช่วยให้หลายคนเข้าใจ closure มากขึ้น

Leave a Reply